30 KiB
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.mdof the locally installed@earendil-works/pi-coding-agentpackage - Read
docs/containerization.md, excerpts ofdocs/models.mdanddocs/providers.md grep '"version"' .../pi-coding-agent/package.jsonnpm view @earendil-works/pi-coding-agent version- Inspected host auth store structure (keys only, values never printed)
- Read
- Observed result:
- Package documentation: full docs ship inside the installed package (
README.mdplus adocs/directory includingdocs/containerization.md). - Current package version:
0.84.4— the locally installed version and the npm registry latest are identical. Selected version to pin:0.84.4. - 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). - 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.mdfile conventions exist but the CLI flag is the documented noninteractive path.) - Documented container authentication:
docs/containerization.mddocuments the "Plain Docker" pattern: run the wholepiprocess 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 isauth.jsonwritten by/loginunder the pi agent directory (~/.pi/agent/auth.json).
- Package documentation: full docs ship inside the installed package (
- 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-inzaiprovider) 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, pinnedpackage.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-scriptsto generate the lockfile from the pinned dependency. - Observed result: All files created;
package-lock.jsonpins@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 imagemosaic-poc-agent:0.84.4fromnode:24-bookworm-slimwith 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-slimis the maintained base image used in Pi's own documented containerization example. - Expected result:
docker compose buildexits 0 and the image contains the contracts, the runner scripts, and the pinnedpibinary, 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;idvia--entrypoint; contract listing; credential file scan. - Observed result:
- Build exit 0; image tagged
mosaic-poc-agent:0.84.4. pi --versioninside the image reports0.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/contractswith read-only permissions (0555). - Credential scan: no
auth.jsonor other auth files exist in the image;/home/node/.pi/agent/is empty in the image.
- Build exit 0; image tagged
- Failure or correction:
- First build failed: Docker Compose expects
Dockerfileby default; fixed by settingbuild.dockerfile: Containerfileincompose.yaml. - Second build failed:
useraddexit 4 (uid 1000 already exists) because the maintained node image ships anodeuser at uid/gid 1000. Fixed by reusing the built-innodeuser (same 1000:1000 host mapping) instead of creating a duplicatemosaicuser; container paths updated from/home/mosaic/...to/home/node/...inContainerfile,compose.yaml,README.md,.env.example.
- First build failed: Docker Compose expects
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."), thenscripts/verify.sh(exact-match gate againstMOSAIC_HELLO_OK), then the negative test (EXPECTED_MARKER=MOSAIC_NOT_OK scripts/verify.shmust exit nonzero), then thescripts/reset.shsafety 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.shafter reset;docker compose configmount inspection. - Observed result:
hello.sh: stdout exactlyMOSAIC_HELLO_OK— a real model request (providerzai, modelglm-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(expectedMOSAIC_NOT_OK, actualMOSAIC_HELLO_OK), exit 1. reset.shrefusal 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~/.mosaicor~/.config/mosaicmounts, no Docker socket.
- Failure or correction:
- 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.
- 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 (
- 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-drivenscripts/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.jsonexclusively; 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:
- Selftest harness bug:
cfghelper invoked without a body for the symlink case ($2: unbound variable). Fixed in the harness; product code unaffected. - E2E rerun-after-reset failure:
verify.shdid 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 callingbootstrap_runtime_dirinverify.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.
- Selftest harness bug:
- 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/,expectExactgating, 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.jsonsucceeds with exactlyMOSAIC_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/runon 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 exactlyMOSAIC_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:
- release.sh initially failed with missing state/ directory (no mkdir before pointer/log writes); fixed.
- 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:
- Selection authority settled: load_config always exports MOSAIC_ADAPTER from config; environment overrides for scripts are therefore not a supported selection path (by design).
- 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
exportwhich this dash prints asexport K='v'— switched toenv; (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:
- 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).
- Regression: unquoted $REQUEST_ARG word-split the request into positional args — fixed with positional-argument building (set -- ... "$@").
- 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.logreceipt, 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/*.mddispatched (sorted) into every agent launch's generated prompt; bootstrap seedsuser/USER.mdonce; 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>/holdsagent.json(strict validation: version, name, role?, capabilities?, workspace?, session?) +SOUL.mdpersona;agent.shvalidates, copies runtime SOUL todataRoot/agents/<name>/, setsMOSAIC_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.