Collaboration record (conductor loop, docs/plans/CONDUCTOR.md):
- round 1 (worker session worker-1, 2m28s): retry implemented per spec
- conductor live test exposed spec gap: direct invocation lacked
launcher env exports
- round 2 (same worker session, 59s): spawnEnv made self-sufficient,
but used PI_* where compose interpolates MOSAIC_*
- conductor hotfix: 3-line rename to MOSAIC_PROVIDER/MOSAIC_MODEL/
MOSAIC_DATA_ROOT
Final: node scripts/mosaic-task.mjs retry <runId> re-executes a run's
task snapshot as a new run; live retry replied REMEMBERED; all suites
green (24/32/14 + verify).
Known limitation: retrying a run whose task used a RELATIVE mission path
resolves it against the temp dir; lineage tracking deferred.
Closes#25, closes#26, closes#27
- task schema: optional workspace (absent | :run ephemeral | named
persistent under dataRoot/workspaces) and capabilities.tools (pi
documented tool allowlist); strict validation, traversal-proof names
- runner: creates host workspace, passes MOSAIC_WORKSPACE (container
path) + MOSAIC_TOOLS; result.json records both
- pi adapter: cds into workspace; --tools when allowlist present else
--no-tools
- mock adapter: logs delivered MOSAIC_* vars to stderr as deterministic
plumbing evidence (dash prints 'export K=v', so use env not export)
Closes#20
- load-contracts.sh: MOSAIC_MISSION_FILE (readable) appends a MISSION
(runtime) section — objective + directives — after the immutable
contracts; unreadable path is a hard error, absent env changes nothing
- mosaic-task.mjs: exports MOSAIC_MISSION_FILE as the run snapshot's
container path (/var/lib/mosaic/runs/<id>/mission.json), with an
outside-dataRoot guard; also exports the configured adapter
Verified: contract-only prompt has no mission section; mission-bearing
run shows objective + directives in the generated prompt, snapshot
recorded, real provider returns exactly MOSAIC_HELLO_OK.
Closes#17
- live hello failure dumps latest run result.json + stderr tail before
sandbox cleanup destroys them
- wrong-expectExact case asserts reason == expect-mismatch (was: any
exit 1, which masked compose-level failures)
- repair dangling if/else from the docker-guard refactor
Closes#15
M3 made MOSAIC_IMAGE_TAG required in compose, but run-task.sh never
called load_release — direct task runs failed in compose before any
model call. release.sh paths masked it by exporting the tag to children.
Found by owner-run test-task.sh; failure receipts were in the run
records' stderr.txt.
Closes#14
14 cases: RELEASE validation (valid/invalid/missing), tag consistency,
status on empty state, fault-injected refusal with no pointer + single
valid refusal log line, healthy activation, pointer fields, repeat
activation append-only log, rollback-without-previous refusal.
Harness fix learned the hard way: restore RELEASE from backup inline
after the missing-file case (mv-back restored the mutated file); single
exit trap self-heals the repo state.
Closes#12
- activate: image-presence pre-check + M2 task-runner health gate
(tasks/hello-marker.json exact marker) before atomic pointer replace
(tmp+rename); every attempt appended to activation-log.jsonl
- --fault-injection flips the health expectation to prove the refusal path
- rollback: health-gated re-activation of the previous activated imageTag
from the log; refuses when the image is gone or no previous exists
- status: release, tag, pointer, recent log; safe on empty state
- state lives under <dataRoot>/state/ (config-independent, reset-scoped)
Verified: activate OK; fault-injected refuse with pointer unchanged;
rollback-without-previous refuse.
Closes#11
- RELEASE file: single source of release version (0.0.X until declared stable)
- common.sh load_release(): validates version, derives
MOSAIC_IMAGE_TAG=mosaic-poc-agent:<pi>-r<release> from the pinned pi dep
- compose.yaml: image tag is required env; build/hello/verify call load_release
- verify.sh derives the image name instead of hardcoding it
- package.json version aligned to the same 0.0.X line
Closes#10
- scripts/mosaic-task.mjs: validate | run | list
- Strict v1 schemas: unknown keys rejected; ids/prompt/expectExact/
timeoutSeconds bounds enforced; optional mission file resolved against
the task file and validated too
- run: executes through the config-driven container path with stdin
detached (issue #5 class), SIGKILL timeout (default 120s), trimmed
response capture
- Immutable run records under <dataRoot>/runs/r-<utcstamp>-<rand>/:
task.json + mission.json snapshots (write-once), stderr.txt, result.json
- expectExact gate: mismatch -> status failed, exit 1; result.json is
always written
- scripts/run-task.sh: load_config + bootstrap_runtime_dir before exec
- M2 scope: mission directives are snapshotted for provenance, not yet
injected into the runtime prompt (later policy layer)
Closes#6, closes#7
pi print mode reads piped stdin until EOF; an attached terminal stdin
blocked the one-shot run forever. Automated contexts (closed stdin)
never exposed it. Request text comes from the compose command.
Proven: tail -f /dev/null | scripts/hello.sh now returns MOSAIC_HELLO_OK
in ~4s (previously timed out at 30s); verify.sh remains green.
Closes#5
- verify.sh now calls bootstrap_runtime_dir after load_config; previously a
reset-then-verify flow let Docker auto-create a root-owned mount source
- common.sh: fail with clear guidance when data root exists but is not writable
- README: configuration section, bootstrap usage, selftest entry point
- BUILD-LOG: Phase 5 entries with corrections
E2E (clean slate): 20/20 selftests; bootstrap idempotent; config-driven
hello/verify MOSAIC_HELLO_OK; negative marker exit 1; reset + rerun green;
config checksum unchanged across the entire flow.
Closes#4