- release.sh health gate runs with MOSAIC_ENSURE_SKIP=1: the gated task run cannot re-enter release self-determination - run-task.sh warns on release drift instead of silently using a stale image - ROADMAP: M20 decision recorded (packages/* monorepo at usurpation, continuity-first); restructure sequenced as M20 phase 1 Closes #39
441 lines
33 KiB
Markdown
441 lines
33 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.
|
||
|
||
---
|
||
|
||
## Phase 12: Conductor loop — self-orchestration (M8)
|
||
|
||
### Entry 12.1 — before
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Intended action: Stand up the poor-man orchestration loop per docs/plans/CONDUCTOR.md: conductor (host, holds git) mirrors the repo into a worker workspace, dispatches a headless pi worker (session worker-1, tools read/write/edit/bash) to implement retry <runId>, reviews the diff, integrates, verifies (Gitea #25, #26, #27).
|
||
- Reason: The owner asked for circular task processing with agent workers; the stack now has every primitive needed — this proves it on the stack itself.
|
||
- Expected result: worker-authored retry merged with suites green and a live retry verified.
|
||
|
||
### Entry 12.2 — after
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Commands run: repo mirror clone; worker dispatch (tasks/worker-retry.json); diff review; apply; live retry; refinement dispatch (tasks/worker-retry-refine.json); second review; reverse+reapply combined patch; conductor interpolation fix; live retry; full sweep.
|
||
- Observed result:
|
||
- Worker round 1: implemented retry correctly per spec in 2m28s; diff reviewed clean.
|
||
- Live retry exposed a spec gap (direct invocation lacks launcher env exports).
|
||
- Worker round 2 (same session, 59s): made spawnEnv self-sufficient, but used PI_* names where compose interpolates MOSAIC_*.
|
||
- Conductor hotfix: 3-line rename to MOSAIC_PROVIDER/MOSAIC_MODEL/MOSAIC_DATA_ROOT (too trivial for a worker round).
|
||
- Final: live retry succeeded (replied REMEMBERED, new run recorded); all suites green.
|
||
- Failure or correction: three rounds total — one spec gap (conductor), one naming mismatch (worker), one trivial rename (conductor). Each was caught by mechanical verification (run record stderr), never by hope.
|
||
- Attribution: feature authored by headless pi worker (glm-5.3-flash) in sessions worker-1; conductor reviewed, integrated, and hotfixed.
|
||
|
||
## Result (M8)
|
||
|
||
Conductor loop proven end-to-end on the stack itself. `main` merged with M8; release 0.0.6 remains active (retry is host-side only, no image change).
|
||
|
||
---
|
||
|
||
## Phase 13: Mission-level capability policy (M9)
|
||
|
||
### Entry 13.1 — before
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Intended action: Missions may declare capabilities.tools as governing constraints (Gitea #30); merge semantics = least-privilege intersection (task narrows, never widens; empty intersection = tool-free run). Host-side only.
|
||
- Reason: First mechanical restriction layer — the trust model becomes enforced, not instructed.
|
||
- Expected result: all four merge cases asserted from run evidence; suites green.
|
||
|
||
### Entry 13.2 — after
|
||
|
||
- Observed: four merge cases verified deterministically via run-record stderr (mission-only, task-only, narrowed, emptied); invalid mission capabilities exit 2; task suite 41/41.
|
||
- Failure or correction: selftest harness could not express ABSENT vs EMPTY fields via its printf helper — fixed with an ABSENT marker; two suite config-leak defects fixed (per-command env scoping). Product unaffected.
|
||
|
||
## Phase 14: Session forking (M11)
|
||
|
||
### Entry 14.1 — before
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Intended action: sessionForkFrom task field branches the source session's newest file (pi --fork) into the target session dir; ancestor untouched; RELEASE -> 0.0.7 with health-gated activation (Gitea #33).
|
||
- Reason: Owner flagged conversation forking from a common ancestor as a desired property; pi JSONL trees make it native.
|
||
- Expected result: forked child recalls ancestor context; ancestor file untouched; suites green; 0.0.7 active.
|
||
|
||
### Entry 14.2 — after
|
||
|
||
- Observed: mock plumbing asserts fork source + target delivery; validation rejects fork-without-target and self-fork (exit 2); ghost source exits 4; live fork: child recalled 'mosaico' from ancestor context while the ancestor session file remained untouched (file-level assertion); suites 58/24/14 + verify green; 0.0.7 packaged and activated via health gate.
|
||
- Failure or correction: retryRun-style self-assignment bug in validation (compared null to target) — caught by negative test, fixed.
|
||
|
||
## Result (M11)
|
||
|
||
Session forking verified. `main` merged with M11, tagged `session-fork-v1`; release 0.0.7 active.
|
||
|
||
---
|
||
|
||
## Phase 15: Interactive TUI agent + TOOLS.md (M13)
|
||
|
||
### Entry 15.1 — before
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Intended action: Add scripts/agent.sh — an interactive TUI launcher (contracts + optional mission + agent identity + named session + optional workspace/tools) — and the pi-adapter interactive branch; remove the fixed compose command; add docs/TOOLS.md as the on-demand reference AGENTS.md routes to; RELEASE -> 0.0.8 (Gitea #35).
|
||
- Reason: The owner's bootstrap model is vanilla pi sessions directed by AGENTS.md, graduating to governed TUI agents — the first the system itself launches.
|
||
- Expected result: TUI agent launches with contracts+identity context; headless paths unchanged; TOOLS.md consolidates the reference.
|
||
|
||
### Entry 15.2 — after
|
||
|
||
- Observed: mock plumbing asserts agent name/session/workspace/mission delivery; identity section asserted in generated prompt; headless hello + suites green (24/58/17/14 + verify); 0.0.8 packaged and health-gated activated.
|
||
- Failure or correction:
|
||
1. Regression: pi adapter rewrite made MOSAIC_AGENT_NAME unconditionally required, breaking headless paths — caught by task suite (empty-stderr exit-nonzero), fixed (optional in headless; identity section simply omitted).
|
||
2. Regression: unquoted $REQUEST_ARG word-split the request into positional args — fixed with positional-argument building (set -- ... "$@").
|
||
3. Mission fixture wording (objective named the agent) invited the model to append its name after the marker, tripping the strict gate — fixture tightened; strict gate kept by design.
|
||
- Conductor session env hygiene: sandbox config exports now scoped per-command after a leak broke cross-suite runs.
|
||
|
||
## Result (M13)
|
||
|
||
Interactive TUI agent launched and verified; TOOLS.md reference shipped. `main` merged with M13, tagged `interactive-agent-v1`; release 0.0.8 active.
|
||
|
||
|
||
|
||
---
|
||
|
||
## Phase 16: Run-record retention (M10) — recorded retroactively
|
||
|
||
- Timestamp of work: 2026-09-03; recorded: 2026-09-03 (back-filled entry; the original phase entry was lost to editor races - see correction note below)
|
||
- Summary: `mosaic-task.mjs prune [--keep=N] [--yes]` - keep newest N run records, dry-run by default, append-only `.pruned.log` receipt, sessions/workspaces/state untouched. 8 suite cases.
|
||
- Observed: dry-run deletes nothing; keep-N honored; newest kept; receipt written; isolation asserted.
|
||
- Correction (recorded): suite-hardening edits (prune section config scoping, duplicate helper removal) were applied in the same phase.
|
||
|
||
## Phase 17: Conductor auto-apply policy (M12) — recorded retroactively
|
||
|
||
- Timestamp of work: 2026-09-03; recorded: 2026-09-03 (back-filled)
|
||
- Summary: `conductor-policy.json` (tracked, strictly validated: enabled switch, path allowlist, gating suites) + `scripts/conductor-apply.sh <runId>` - succeeded-run check, clean target tree, allowlist, syntax gates, apply, suites, attribution commit; any failure reverts; push never automatic. 17 sandbox suite cases.
|
||
- Observed: all gates green; suite-failure auto-revert verified; disabled policy refuses with exit 2.
|
||
- Decision recorded: review moves to after-the-fact (history revertible) for worker patches under the policy; push remains explicit.
|
||
|
||
## Phase 18: Live user context (M14) — recorded retroactively
|
||
|
||
- Timestamp of work: 2026-09-03; recorded: 2026-09-03 (back-filled)
|
||
- Summary: USER.md removed from immutable contracts (wrong owner - user info is user-owned live context); `<dataRoot>/user/*.md` dispatched (sorted) into every agent launch's generated prompt; bootstrap seeds `user/USER.md` once; loader layers now governance -> persona -> identity -> mission -> user.
|
||
- Observed: user edit propagates to next launch (TUI or headless) without rebuilds; contract-only prompts unchanged when no user dir present.
|
||
- Correction (recorded): first implementation pass did not regenerate the container - caught by owner test (edits to repo/mirror copies of USER.md did not propagate; the design, not the test, was the defect).
|
||
|
||
## Phase 19: Agent seats + roles/ convention (M15) — recorded retroactively
|
||
|
||
- Timestamp of work: 2026-09-03; recorded: 2026-09-03 (back-filled)
|
||
- Summary: `agents/<name>/` holds `agent.json` (strict validation: version, name, role?, capabilities?, workspace?, session?) + `SOUL.md` persona; `agent.sh` validates, copies runtime SOUL to `dataRoot/agents/<name>/`, sets `MOSAIC_AGENT_SOUL_FILE`; loader fills the SOUL slot from the seat persona (contract SOUL = default); identity section gains role; per-agent default workspace; compose passthroughs.
|
||
- Observed: launch with seat definition replaces the contract persona in the generated prompt; role in identity; seat record written once; suites green; RELEASE 0.0.10 packaged, health-gated activated.
|
||
- Correction (recorded): RELEASE was not bumped when container content changed - tag r0.0.9 rebuilt with different content (invariant lapse; r0.0.9 was never active). Restored: RELEASE 0.0.10 packaged beside, health-gated, activated.
|
||
- Conventions recorded per owner direction: repository root holds bootstrap-required configuration only; role contracts live in `roles/`.
|
||
|
||
## Backfill note
|
||
|
||
Phases 16-19 were recorded retroactively on 2026-09-03 after editor-session races
|
||
left them unwritten at the time of work. Ground truth for each entry: the git
|
||
history (commit subjects/bodies), the Gitea milestone/issue records (#32, #34,
|
||
#35, #36), and the suite files themselves. No facts were reconstructed from
|
||
memory alone.
|
||
|
||
---
|
||
|
||
## Phase 20: Release self-determination (M16)
|
||
|
||
### Entry 20.1 — before
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Intended action: The system determines what is installed and aligns itself; the user never manually runs release.sh. `release.sh ensure` (align-or-noop) invoked automatically by human-facing launchers (hello, verify, agent); run-task reports drift without auto-aligning (workers/suites must not trigger builds or model gates mid-automation).
|
||
- Reason: Owner direction — intelligence in operation; post-reset pointer loss required a manual release command, which contradicts the self-healing design.
|
||
- Expected result: post-reset pointer loss auto-restores via health-gated ensure; drift warns on run-task; aligned state is a no-op; M20 packages/* decision recorded.
|
||
|
||
### Entry 20.2 — after
|
||
|
||
- Timestamp: 2026-09-03
|
||
- Commands run: ensure with pointer removed (live drift); ensure idempotence; drift-warning demo via RELEASE bump without packaging; full suites.
|
||
- Observed result: drift detected (active: none, desired: 0.0.11) -> health-gated activate -> aligned; second ensure no-op; run-task drift warning fired on desired-version bump without packaging; all suites green (24/68/17/14 + verify).
|
||
- Failure or correction:
|
||
1. First run-task edit anchor missed (inline comment mismatch) — reapplied with exact text.
|
||
2. RELEASE was temporarily bumped to 0.0.12 without packaging for the drift demo — restored to 0.0.11; state pointer remained aligned.
|
||
- M20 decision recorded in ROADMAP.md: v2 adopts packages/* monorepo structure at usurpation (owner, continuity-first).
|
||
- Process note (tool races): mechanism confirmed — batching a file write/edit and a dependent bash command in one parallel block runs the bash before the write flushes. Consequences: lost doc updates, stale anchors, one premature grep. Operating rule: dependent calls sequential; every write verified before claimed complete.
|
||
|
||
## Result (M16)
|
||
|
||
Release self-determination live: the system aligns itself to RELEASE without manual commands. Suites 24/68/17/14 + verify green.
|
||
|
||
## Phase 21: M16 hardening + M20 decision
|
||
|
||
- Recursion guard: release.sh's health-gate task run sets MOSAIC_ENSURE_SKIP
|
||
so the gate's run-task cannot re-enter release self-determination.
|
||
- run-task.sh: drift warning on pointer/RELEASE mismatch (workers and suites
|
||
never trigger builds or model gates mid-automation).
|
||
- ROADMAP M20 decision recorded: v2 adopts packages/* monorepo structure at
|
||
usurpation (owner, continuity-first); restructure sequenced as M20 phase 1.
|
||
- Live drill: drift 0.0.11 -> 0.0.12 detected, health-gated activate, no
|
||
recursion, verify green.
|