Files
stack/BUILD-LOG.md
T

30 KiB
Raw Blame History

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 27 of the required proof path plus acceptance criteria 511.
  • 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 /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//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/ 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/ 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 (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 , 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.