- mosaic-task.mjs show <runId>: full record + snapshots + artifacts; uppercase-tolerant id validation; missing/traversal ids exit 4 - list: task/workspace/session columns - RELEASE -> 0.0.6; README workspaces/capabilities/sessions sections; BUILD-LOG Phases 9-11; autonomous-run tracker results filled Closes #24
279 lines
21 KiB
Markdown
279 lines
21 KiB
Markdown
# BUILD-LOG
|
||
|
||
Append-only build log for the Minimal Mosaic Stack container proof of concept.
|
||
Each phase records the plan before it runs and the observed result after it runs.
|
||
No credentials are recorded in this file.
|
||
|
||
---
|
||
|
||
## Phase 1: Pi package discovery
|
||
|
||
### Entry 1.1 — before
|
||
|
||
- Timestamp: 2026-02-02 (session start, local)
|
||
- Intended action: Locate the current package documentation for `@earendil-works/pi-coding-agent`, determine the current version, the supported noninteractive command, the custom system prompt mechanism, and the documented container authentication method.
|
||
- Reason: The brief forbids guessing CLI flags, authentication paths, or SDK methods; all runtime commands must be derived from the package documentation.
|
||
- Expected result: Documented answers for all five discovery questions, recorded below, with the Pi package version pinned in the project.
|
||
|
||
### Entry 1.2 — after
|
||
|
||
- Timestamp: 2026-02-02
|
||
- Commands run:
|
||
- Read `README.md` of the locally installed `@earendil-works/pi-coding-agent` package
|
||
- Read `docs/containerization.md`, excerpts of `docs/models.md` and `docs/providers.md`
|
||
- `grep '"version"' .../pi-coding-agent/package.json`
|
||
- `npm view @earendil-works/pi-coding-agent version`
|
||
- Inspected host auth store structure (keys only, values never printed)
|
||
- Observed result:
|
||
1. **Package documentation**: full docs ship inside the installed package (`README.md` plus a `docs/` directory including `docs/containerization.md`).
|
||
2. **Current package version**: `0.84.4` — the locally installed version and the npm registry latest are identical. Selected version to pin: `0.84.4`.
|
||
3. **Supported noninteractive command**: `pi -p` / `pi --print` — "Print response and exit". Documented in the CLI Reference. Print mode also merges piped stdin into the initial prompt (not used here).
|
||
4. **Custom system prompt**: documented CLI flag `--system-prompt <text>` — "Replace default prompt (context files and skills still appended)". Because the generated contract prompt must fully control behavior, the run also passes the documented discovery-off switches (`--no-context-files`, `--no-skills`, `--no-extensions`, `--no-prompt-templates`, `--no-themes`) so nothing is appended from ambient files. (`APPEND_SYSTEM.md` / `.pi/SYSTEM.md` file conventions exist but the CLI flag is the documented noninteractive path.)
|
||
5. **Documented container authentication**: `docs/containerization.md` documents the "Plain Docker" pattern: run the whole `pi` process in the container and pass provider API keys at runtime via environment variables (example: `docker run -e ANTHROPIC_API_KEY ...`). The documented alternative credential store is `auth.json` written by `/login` under the pi agent directory (`~/.pi/agent/auth.json`).
|
||
- Decision: supply the credential at runtime as a **read-only mounted credential file** (host `auth.json`, which contains a static API-key entry for the built-in `zai` provider) mounted at the container pi agent directory, and additionally allow the documented environment-variable path (`ZAI_API_KEY` / `ANTHROPIC_API_KEY`) as an alternative. Provider and model are non-secret settings supplied via `.env` (`PI_PROVIDER=zai`, `PI_MODEL=glm-5.3-flash`).
|
||
- Failure or correction: none. Host check confirmed no API-key environment variables are exported on the host, so the read-only auth.json mount is the working runtime credential path for this experiment.
|
||
|
||
---
|
||
|
||
## Phase 2: Project scaffold
|
||
|
||
### Entry 2.1 — before
|
||
|
||
- Timestamp: 2026-02-02
|
||
- Intended action: Create the contract fixtures (exact brief contents), the contract loader (`src/load-contracts.sh`), the one-shot agent runner (`src/run-agent.sh`), the four required scripts (`scripts/build.sh`, `hello.sh`, `verify.sh`, `reset.sh`), `Containerfile`, `compose.yaml`, pinned `package.json` + `package-lock.json`, `.gitignore`, `README.md`, `LAYERS.md`.
|
||
- Reason: Implement exactly the file set the brief requires, with no extra machinery (no schemas, overlays, manifests, or policy loading).
|
||
- Expected result: A complete project whose only remaining unknown is whether the pinned image builds and the real model request returns `MOSAIC_HELLO_OK`.
|
||
|
||
### Entry 2.2 — after
|
||
|
||
- Timestamp: 2026-02-02
|
||
- Commands run: file creation; `npm install --package-lock-only --ignore-scripts` to generate the lockfile from the pinned dependency.
|
||
- Observed result: All files created; `package-lock.json` pins `@earendil-works/[email protected]` (exact, no range).
|
||
- Failure or correction: none.
|
||
|
||
---
|
||
|
||
## Phase 3: Container image build
|
||
|
||
### Entry 3.1 — before
|
||
|
||
- Timestamp: 2026-02-02
|
||
- Intended action: Run `scripts/build.sh` (Docker Compose build) to produce image `mosaic-poc-agent:0.84.4` from `node:24-bookworm-slim` with the pinned Pi, the four contract fixtures at `/opt/mosaic/contracts`, and a non-root user (uid/gid 1000).
|
||
- Reason: Phase 1 of the required proof path; `node:24-bookworm-slim` is the maintained base image used in Pi's own documented containerization example.
|
||
- Expected result: `docker compose build` exits 0 and the image contains the contracts, the runner scripts, and the pinned `pi` binary, with no credentials baked in.
|
||
|
||
### Entry 3.2 — after
|
||
|
||
- Timestamp: 2026-02-02
|
||
- Commands run: `scripts/build.sh`; `docker run --rm mosaic-poc-agent:0.84.4 --version`; `id` via `--entrypoint`; contract listing; credential file scan.
|
||
- Observed result:
|
||
- Build exit 0; image tagged `mosaic-poc-agent:0.84.4`.
|
||
- `pi --version` inside the image reports `0.84.4` (and this run also executed the contract loader successfully, writing `/var/lib/mosaic/system-prompt.md`).
|
||
- Container user is `uid=1000(node) gid=1000(node)` — non-root.
|
||
- All four contract files present at `/opt/mosaic/contracts` with read-only permissions (0555).
|
||
- Credential scan: no `auth.json` or other auth files exist in the image; `/home/node/.pi/agent/` is empty in the image.
|
||
- Failure or correction:
|
||
1. First build failed: Docker Compose expects `Dockerfile` by default; fixed by setting `build.dockerfile: Containerfile` in `compose.yaml`.
|
||
2. Second build failed: `useradd` exit 4 (uid 1000 already exists) because the maintained node image ships a `node` user at uid/gid 1000. Fixed by reusing the built-in `node` user (same 1000:1000 host mapping) instead of creating a duplicate `mosaic` user; container paths updated from `/home/mosaic/...` to `/home/node/...` in `Containerfile`, `compose.yaml`, `README.md`, `.env.example`.
|
||
|
||
---
|
||
|
||
## Phase 4: Runtime verification
|
||
|
||
### Entry 4.1 — before
|
||
|
||
- Timestamp: 2026-02-02
|
||
- Intended action: Run `scripts/hello.sh` (one-shot request: "Return your startup marker and nothing else."), then `scripts/verify.sh` (exact-match gate against `MOSAIC_HELLO_OK`), then the negative test (`EXPECTED_MARKER=MOSAIC_NOT_OK scripts/verify.sh` must exit nonzero), then the `scripts/reset.sh` safety tests and a final rerun after reset.
|
||
- Reason: Phases 2–7 of the required proof path plus acceptance criteria 5–11.
|
||
- Expected result: hello prints only the marker; verify exits 0; negative test exits nonzero; reset refuses unsafe paths and succeeds on the real path; rerun after reset reproduces the success.
|
||
|
||
### Entry 4.2 — after
|
||
|
||
- Timestamp: 2026-02-02
|
||
- Commands run: `scripts/hello.sh`; `scripts/verify.sh`; `EXPECTED_MARKER=MOSAIC_NOT_OK scripts/verify.sh`; `scripts/reset.sh` (refusal tests: missing marker, symlink with canary file, then real reset, then missing dir); `scripts/build.sh && scripts/verify.sh` after reset; `docker compose config` mount inspection.
|
||
- Observed result:
|
||
- `hello.sh`: stdout exactly `MOSAIC_HELLO_OK` — a real model request (provider `zai`, model `glm-5.3-flash`, auth via the read-only mounted auth.json credential file). The request string contains no marker.
|
||
- `verify.sh`: `PASS: response matches expected marker`, exit 0.
|
||
- Negative test: `FAIL: response does not match expected marker` (expected `MOSAIC_NOT_OK`, actual `MOSAIC_HELLO_OK`), exit 1.
|
||
- `reset.sh` refusal tests: missing marker → exit 1, nothing deleted; symlink (with canary file at the target) → exit 1, canary survived; real path with marker → removed, exit 0; missing dir → "nothing to remove", exit 0.
|
||
- Rerun after reset: build + verify → PASS, exit 0 (criterion 11).
|
||
- Resolved compose mounts: only `/home/jwoltje/.mosaic-dev → /var/lib/mosaic` (rw) and `~/.pi/agent/auth.json → /home/node/.pi/agent/auth.json` (read-only). No `~/.mosaic` or `~/.config/mosaic` mounts, no Docker socket.
|
||
- Failure or correction:
|
||
1. First hello run: the contract loader's status line was printed on stdout, mixing runtime data into the model response stream and contaminating the exact-match capture. Fixed by sending the loader's status message to stderr (`src/load-contracts.sh`), rebuilt the image, reran: stdout is exactly the model response.
|
||
- Credential check: no credential material appears in this log, in hello/verify output, or in the image (image scan found no auth files).
|
||
|
||
## Result
|
||
|
||
All 11 acceptance criteria demonstrated. The real model request passed.
|
||
|
||
---
|
||
|
||
## Phase 5: Configuration-driven Hello World (M1)
|
||
|
||
### Entry 5.1 — before
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Intended action: Make the container POC configuration-driven. Baseline committed and tagged `poc-container-hello-v0`. Milestone M1 tracked in Gitea (issues #1-#4): (T1) config module with idempotent bootstrap and strict v1 validation; (T2) wire scripts and compose to config.json with fail-closed behavior; (T3) sandboxed config selftests; (T4) E2E verification and documentation.
|
||
- Reason: Per docs/plans/2026-09-02_atomic-mosaic-foundation.md — config.json must be the sole discovery entry point; updates and runs must never corrupt or invent configuration.
|
||
- Expected result: All M1 acceptance criteria pass; Hello World reproducible from configuration alone.
|
||
|
||
### Entry 5.2 — after
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Commands run: `scripts/test-config.sh` (20 cases); fail-closed checks (compose without launcher env, verify/reset with missing config); `scripts/bootstrap.sh`; config-driven `scripts/hello.sh`, `scripts/verify.sh`, negative marker test, sandboxed reset symlink refusal (canary survived), real reset + bootstrap + build + verify; config checksum comparison across the entire flow.
|
||
- Observed result:
|
||
- Config selftests: 20 passed, 0 failed.
|
||
- Fail-closed confirmed: compose exits 1 without launcher env; verify/reset exit 1 on missing config before any mutation.
|
||
- Bootstrap created `~/.config/mosaic-dev/config.json` exclusively; second run validated without rewriting (content + mtime unchanged).
|
||
- Config-driven hello/verify returned exactly `MOSAIC_HELLO_OK`; verify exit 0; negative marker test exit 1.
|
||
- Reset refused symlinked dataRoot; canary file survived; real reset removed only the configured data root.
|
||
- config.json checksum unchanged across hello/verify/reset/bootstrap/build/verify.
|
||
- Failure or correction:
|
||
1. Selftest harness bug: `cfg` helper invoked without a body for the symlink case (`$2: unbound variable`). Fixed in the harness; product code unaffected.
|
||
2. E2E rerun-after-reset failure: `verify.sh` did not ensure the configured data root existed before the container mount. With the data root absent, Docker auto-created the host path as root:root, and the container's uid-1000 user could not write the generated system prompt. Fixed by calling `bootstrap_runtime_dir` in `verify.sh`; also hardened it to fail with a clear message when the data root exists but is not writable (root-owned leftover). Clean-slate E2E rerun: all steps green.
|
||
- Credential check: no credential material in config, scripts, logs, or test output.
|
||
|
||
## Result (M1)
|
||
|
||
Configuration-driven Hello World verified. `main` merged with M1 and tagged `config-hello-v1`.
|
||
|
||
---
|
||
|
||
## Phase 6: Mission and task abstraction (M2)
|
||
|
||
### Entry 6.1 — before
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Intended action: Add the first mission/task layer, host-side only (Gitea milestone M2, issues #6-#9): strict v1 schemas for missions and tasks, a task runner executing through the proven config-driven container path, immutable write-once run records under `<dataRoot>/runs/`, `expectExact` gating, timeouts, selftests, fixtures, and docs.
|
||
- Reason: The foundation plan's following layer — mission (objective + directives), task (bounded unit), run (one attempt), result (immutable evidence) — must exist as data and records before any policy or multi-agent work.
|
||
- Expected result: `scripts/run-task.sh tasks/hello-marker.json` succeeds with exactly `MOSAIC_HELLO_OK`; wrong expectations fail; every run leaves an immutable record; configuration remains untouched.
|
||
|
||
### Entry 6.2 — after
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Commands run: `scripts/test-task.sh` (18 cases incl. live runs); `scripts/run-task.sh validate` / `run` on committed fixtures; `node scripts/mosaic-task.mjs list`; config checksum comparison across runs.
|
||
- Observed result:
|
||
- Selftests: 18 passed, 0 failed (schema negatives; live exact-marker success; wrong expectExact fails; distinct run dirs; result.json contents; list).
|
||
- Fixture run: status `succeeded`, response exactly `MOSAIC_HELLO_OK`, mission snapshot recorded.
|
||
- Run records written once under `<dataRoot>/runs/r-<utcstamp>-<rand>/`; reruns never clobber.
|
||
- Failure or correction: none this phase.
|
||
- Credential check: no credential material in task data, run records, or logs.
|
||
|
||
## Result (M2)
|
||
|
||
Mission/task layer verified end-to-end. `main` merged with M2 and tagged `mission-task-v1`.
|
||
|
||
---
|
||
|
||
## Phase 7: Release model and safe updates (M3)
|
||
|
||
### Entry 7.1 — before
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Intended action: Add the release substrate (Gitea milestone M3, issues #10-#13): RELEASE file single-sources the version (0.0.X line per owner direction), image tags derive from it, scripts/release.sh provides package/activate/rollback/status, activation is health-gated by the M2 task runner, pointer + append-only log under <dataRoot>/state/.
|
||
- Reason: The owner's top invariant — updates must never corrupt a working installation — needs a mechanism, not a convention: gate-then-flip with recorded history and rollback.
|
||
- Expected result: Update, refusal, and rollback drills all green with config checksums unchanged.
|
||
|
||
### Entry 7.2 — after
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Commands run: scripts/test-release.sh (14 cases); recorded drills: update (0.0.3 -> 0.0.4 package+activate+verify), fault-injected refusal, rollback to 0.0.3.
|
||
- Observed result:
|
||
- Selftests: 14 passed, 0 failed.
|
||
- Update drill: packaged and activated r0.0.4 after exact-marker health gate; verify green under the new tag; config checksum unchanged.
|
||
- Refusal drill: health-gate fault injection -> activation refused (exit 1), pointer untouched, refusal appended to the log.
|
||
- Rollback drill: health-gated rollback to r0.0.3; pointer restored; log records package/activate/refused/rollback history append-only.
|
||
- Failure or correction:
|
||
1. release.sh initially failed with missing state/ directory (no mkdir before pointer/log writes); fixed.
|
||
2. Selftest harness mutated the repo RELEASE and restored the mutated copy (mv-back bug) plus a second trap replacing the first; fixed with inline backup restore and one self-healing exit trap. Product code unaffected.
|
||
- Credential check: no credential material in release state, logs, or drills.
|
||
|
||
## Result (M3)
|
||
|
||
Release model and safe updates verified by drills. `main` merged with M3 and tagged `release-model-v1`.
|
||
|
||
---
|
||
|
||
## Phase 8: Runtime adapter seam (M4)
|
||
|
||
### Entry 8.1 — before
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Intended action: Formalize the harness boundary (Gitea milestone M4, issues #16-#19): documented adapter contract under /opt/mosaic/adapters/<name>/adapter.sh; run-agent.sh becomes a dispatcher; pi extracted unchanged; deterministic mock adapter for provider-free seam tests; config gains optional execution.adapter (default pi, configVersion unchanged); mission directives gain their sanctioned injection point via the run snapshot; RELEASE bumps to 0.0.5 with a health-gated activation.
|
||
- Reason: Future harnesses (Claude, Codex, OpenCode) must be additive — one directory each — and mission content needs a single sanctioned path into the runtime.
|
||
- Expected result: All suites green including new deterministic seam cases; 0.0.5 activated by health gate; mission-bearing run recorded.
|
||
|
||
### Entry 8.2 — after
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Commands run: scripts/test-config.sh; scripts/test-task.sh; scripts/test-release.sh; manual seam drills (mock verbatim, unknown/traversal adapter refusal); mission injection checks; release package + activate for 0.0.5.
|
||
- Observed result:
|
||
- Config suite 24/24 (adapter default/validation/env export).
|
||
- Task suite 24/24 including deterministic mock cases (gate pass, expect-mismatch with reason, unknown adapter fail-closed) and mission injection asserted by prompt content.
|
||
- Release suite 14/14; image mosaic-poc-agent:0.84.4-r0.0.5 packaged and activated via exact-marker health gate.
|
||
- Mission directives now flow: task -> run snapshot -> container env -> generated prompt MISSION (runtime) section.
|
||
- Failure or correction:
|
||
1. Selection authority settled: load_config always exports MOSAIC_ADAPTER from config; environment overrides for scripts are therefore not a supported selection path (by design).
|
||
2. Selftest harness: three authoring defects fixed (helpers used before definition; one config file reused across cases leaking adapter state; a static mission fixture asserted against distinctive seam directives; plus an accidentally duplicated live block removed).
|
||
- Credential check: no credential material in adapters, prompts, run records, or logs.
|
||
|
||
## Result (M4)
|
||
|
||
Adapter seam verified; harness boundary is now additive by construction. `main` merged with M4 and tagged `adapter-seam-v1`.
|
||
|
||
---
|
||
|
||
## Phase 9: Workspaces + capability envelope (M5)
|
||
|
||
### Entry 9.1 — before
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Intended action: Optional task workspace (absent / ":run" ephemeral / named persistent) and capabilities.tools allowlist (pi built-ins); runner plumbing via MOSAIC_WORKSPACE/MOSAIC_TOOLS; pi adapter maps to cwd + --tools; mock adapter logs delivered MOSAIC_* vars for deterministic assertions (Gitea #20, #21).
|
||
- Reason: Agents that only answer text cannot do work; the workspace+tools pair is the smallest real capability step, bounded by the container.
|
||
- Expected result: plumbing asserted via run-record stderr; live pi writes a host-visible file.
|
||
|
||
### Entry 9.2 — after
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Commands run: build; mock plumbing run; validate negatives; full task suite; live workspace demo.
|
||
- Observed result: task suite 32/32; MOSAIC_WORKSPACE/MOSAIC_TOOLS asserted in run record; dataRoot/workspaces/<name> created host-side; live pi used bash to write proof.txt into the demo workspace (host-visible).
|
||
- Failure or correction: (1) batch edit dropped SUPPORTED_TOOLS const (runtime ReferenceError, exit 1 instead of 2) — restored; (2) mock env dump used `export` which this dash prints as `export K='v'` — switched to `env`; (3) selftest fed a mismatching mock response to the plain-task case — test bug, fixed.
|
||
|
||
## Phase 10: Named sessions (M6)
|
||
|
||
### Entry 10.1 — before
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Intended action: Optional task.session name -> persistent session dir dataRoot/sessions/<name> via pi --session-dir; resume most recent with -c when present; isolation per name; teach/recall demo fixtures (Gitea #22, #23).
|
||
- Reason: L1 persistence is the prerequisite for any multi-step agent work.
|
||
- Expected result: session dir populated after first run; second run recalls taught context.
|
||
|
||
### Entry 10.2 — after
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Commands run: build; mock plumbing run; live teach/recall E2E; task suite.
|
||
- Observed result: task suite 32/32; teach run replied REMEMBERED and session JSONL persisted host-side; recall run resumed (-c) and answered exactly 'mosaico'; single continued session file (no duplicate sessions).
|
||
- Failure or correction: none. Design note: ephemeral (--no-session) remains the default when no session is declared.
|
||
|
||
## Phase 11: Operator ergonomics (M7)
|
||
|
||
### Entry 11.1 — before
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Intended action: mosaic-task.mjs show <runId> (full record + snapshots + artifacts, traversal-safe), list with workspace/session columns, RELEASE -> 0.0.6, package + health-gated activate, docs (Gitea #24).
|
||
- Reason: Run records are only as valuable as they are inspectable; release activation closes the loop on container-content changes.
|
||
- Expected result: show works for real/missing/traversal ids; suites green; 0.0.6 active.
|
||
|
||
### Entry 11.2 — after
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Commands run: build; show on real/missing/traversal ids; full sweep; release package + activate.
|
||
- Observed result: config 24/24, task 32/32, release 14/14, verify PASS; 0.0.6 packaged and activated via exact-marker health gate.
|
||
- Failure or correction: showRun initially rejected valid run ids (lowercase-only regex vs uppercase timestamp) and crashed on missing ids (uncaught readdir) — both fixed and covered.
|
||
|
||
## Autonomous run result
|
||
|
||
M5 tagged `workspace-capabilities-v1`, M6 tagged `sessions-v1`, M7 tagged `operator-ergonomics-v1`; release 0.0.6 active. Tracker: docs/plans/2026-09-03_autonomous-run.md.
|
||
|
||
|