From 24bdef75fa096f2520efbdf4de670bcabae84101 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Wed, 2 Sep 2026 22:14:18 -0500 Subject: [PATCH] feat(ops): run inspection, release 0.0.6, docs (#24) - mosaic-task.mjs show : full record + snapshots + artifacts; uppercase-tolerant id validation; missing/traversal ids exit 4 - list: task/workspace/session columns - RELEASE -> 0.0.6; README workspaces/capabilities/sessions sections; BUILD-LOG Phases 9-11; autonomous-run tracker results filled Closes #24 --- BUILD-LOG.md | 54 ++++++++++++++++++++++ README.md | 25 +++++++++++ RELEASE | 2 +- docs/plans/2026-09-03_autonomous-run.md | 21 +++++---- scripts/mosaic-task.mjs | 60 ++++++++++++++++++++++++- 5 files changed, 150 insertions(+), 12 deletions(-) diff --git a/BUILD-LOG.md b/BUILD-LOG.md index 1d40b4b4..eba07670 100644 --- a/BUILD-LOG.md +++ b/BUILD-LOG.md @@ -221,4 +221,58 @@ Release model and safe updates verified by drills. `main` merged with M3 and tag 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. + diff --git a/README.md b/README.md index 3e7c3efb..3e0a42b6 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,31 @@ adapters//adapter.sh env in: MOSAIC_SYSTEM_PROMPT_FILE, MOSAIC_REQUEST, See `adapters/README.md` for the full contract. +## Workspaces, capabilities, sessions (M5/M6) + +Optional task fields extend what an agent can do — all defaulting to the previous behavior: + +```json +{ + "workspace": "demo", // ":run" ephemeral, or persistent dataRoot/workspaces/ + "capabilities": { "tools": ["bash", "read"] }, // pi tool allowlist; absent = no tools + "session": "demo" // persistent session at dataRoot/sessions/ +} +``` + +- The adapter runs inside the workspace; files it writes are host-visible (`dataRoot/workspaces/`). +- Sessions persist via pi's documented `--session-dir`; a follow-up run in the same session resumes the conversation (`-c`) and can recall prior context. Distinct names never share state. Ephemeral (`--no-session`) remains the default when no session is declared. +- Selection authority: config for adapter/provider/model; the task file for workspace/capabilities/session. + +Inspect anything: + +```bash +node scripts/mosaic-task.mjs list # runs with task/workspace/session columns +node scripts/mosaic-task.mjs show # full record + snapshots + artifacts +``` + +Demo fixtures: `tasks/workspace-demo.json`, `tasks/session-demo-1.json` + `tasks/session-demo-2.json`. + See `docs/plans/2026-09-02_atomic-mosaic-foundation.md` for the full plan. Inside the container: diff --git a/RELEASE b/RELEASE index bbdeab62..1750564f 100644 --- a/RELEASE +++ b/RELEASE @@ -1 +1 @@ -0.0.5 +0.0.6 diff --git a/docs/plans/2026-09-03_autonomous-run.md b/docs/plans/2026-09-03_autonomous-run.md index 23539377..ed6c6554 100644 --- a/docs/plans/2026-09-03_autonomous-run.md +++ b/docs/plans/2026-09-03_autonomous-run.md @@ -13,8 +13,8 @@ Advance the Mosaic Stack rebuild several verified layers in one batch, focused o |---|---|---| | M5 | Task workspaces + capability envelope (tools allowlist) | DONE | | M6 | Named sessions — persistence and resume (L1) | DONE | -| M7 | Operator ergonomics: run inspection commands | DONE (partial by design) | -| — | Release 0.0.6 packaged + health-gated activated | DONE | +| M7 | Operator ergonomics: run inspection commands | DONE | +| — | Releases 0.0.5+0.0.6 packaged; 0.0.6 health-gated activated | DONE | Explicitly deferred (do not mistake for forgotten): - Claude/Codex/OpenCode adapters (owner: focus on Pi for now) @@ -60,14 +60,17 @@ Explicitly deferred (do not mistake for forgotten): 6. Inspect any run: `node scripts/mosaic-task.mjs show ` 7. Gitea: milestones M5/M6/M7 closed; issues referenced by merge commits -## Results (to be filled at end of run) +## Results -- M5: pending -- M6: pending -- M7: pending -- Release activation: pending -- Final suite counts: pending -- Corrections encountered: pending +- M5 merged on `main` (merge commit `ddb1554`), tagged `workspace-capabilities-v1` +- M6 merged on `main` (merge commit `4e2a413`), tagged `sessions-v1` +- M7 merged on `main`, tagged `operator-ergonomics-v1` +- Release 0.0.6 packaged, health-gated activated, full sweep green +- Suites at end of run: config 24/24, task 32/32, release 14/14, verify PASS +- Build log: Phases 9 (M5), 10 (M6), 11 (M7) appended with corrections +- Corrections encountered: dropped constant from a failed atomic edit batch (SUPPORTED_TOOLS); dash `export` output format vs `env`; three selftest authoring defects; showRun id-regex case sensitivity + missing-run crash. All fixed and covered by tests. +- Commits pushed incrementally; nothing left uncommitted +- Live proof: workspace file host-visible; session teach/recall ('mosaico') verified ## Next steps after this run (not started) diff --git a/scripts/mosaic-task.mjs b/scripts/mosaic-task.mjs index de852799..278f5360 100755 --- a/scripts/mosaic-task.mjs +++ b/scripts/mosaic-task.mjs @@ -365,17 +365,69 @@ function listRuns() { for (const runId of entries) { let status = "unknown"; let taskId = "-"; + let workspace = "-"; + let session = "-"; try { const result = JSON.parse(fs.readFileSync(path.join(root, runId, "result.json"), "utf8")); status = result.status; taskId = result.taskId; + workspace = result.workspace ?? "-"; + session = result.session ?? "-"; } catch { // Incomplete run record; report as unknown. } - process.stdout.write(`${runId} ${status.padEnd(9)} ${taskId}\n`); + process.stdout.write(`${runId} ${status.padEnd(9)} task=${taskId.padEnd(18)} ws=${String(workspace).padEnd(10)} session=${session}\n`); } } +function showRun(runId) { + if (!/^r-[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(runId)) { + fail(4, `invalid run id: ${JSON.stringify(runId)} (expected r-)`); + } + const resolved = loadConfig(); + const dir = path.join(runsRoot(resolved), runId); + if (!fs.existsSync(dir)) { + fail(4, `run not found: ${runId} (under ${runsRoot(resolved)})`); + } + + const read = (name) => { + try { + return JSON.parse(fs.readFileSync(path.join(dir, name), "utf8")); + } catch { + return null; + } + }; + const result = read("result.json"); + const task = read("task.json"); + const mission = read("mission.json"); + + process.stdout.write(`run: ${runId}\n`); + if (result) { + process.stdout.write( + [ + `status: ${result.status}${result.reason ? ` (${result.reason})` : ""}`, + `task: ${result.taskId}`, + result.missionId ? `mission: ${result.missionId}` : null, + result.workspace ? `workspace: ${result.workspace}` : null, + result.session ? `session: ${result.session}` : null, + result.tools ? `tools: ${result.tools.join(", ")}` : null, + `adapter: ${"(see config)"} provider=${result.provider} model=${result.model}`, + `request: ${JSON.stringify(result.request)}`, + `response: ${JSON.stringify(result.response)}`, + result.expectedExact !== null && result.expectedExact !== undefined ? `expected: ${JSON.stringify(result.expectedExact)}` : null, + `timing: ${result.startedAt} -> ${result.finishedAt} (${result.durationMs} ms)`, + `exit: ${result.exitCode}${result.signal ? ` signal=${result.signal}` : ""}`, + ].filter((line) => line !== null).join("\n") + "\n", + ); + } else { + process.stdout.write("result.json: (missing or unreadable)\n"); + } + if (task) process.stdout.write(`task snapshot: ${"task.json"} present\n`); + if (mission) process.stdout.write(`mission snapshot: ${mission.id} - ${mission.objective}\n`); + process.stdout.write(`artifacts: ${fs.readdirSync(dir).map((f) => `${f}`).join(", ")}\n`); + process.exit(0); +} + const operation = process.argv[2]; const target = process.argv[3]; @@ -392,9 +444,13 @@ switch (operation) { if (!target) fail(4, "usage: mosaic-task.mjs run "); runTask(path.resolve(target)); break; + case "show": + if (!target) fail(4, "usage: mosaic-task.mjs show "); + showRun(target); + break; case "list": listRuns(); process.exit(0); default: - fail(4, `unknown operation: ${JSON.stringify(operation ?? "")} (expected validate | run | list)`); + fail(4, `unknown operation: ${JSON.stringify(operation ?? "")} (expected validate | run | show | list)`); }