docs: row 41 S6 started (filbert): plan, BUILD-LOG before entry, session line

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-09 18:02:55 -05:00
co-authored by Claude Opus 5.5
parent fa8bc5c050
commit b6d2fe2b28
3 changed files with 93 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
# Row 41, slice 1 S6: build plan (Filbert)
Issue #1523. Brief: `docs/plans/2026-10-04_slice-1.md` § Slice 1 S6, blob
`72d11de2`. Base `915e00e5`. Built in a detached worktree; the packet is
`build.patch` plus `candidate-manifest.sha256`, as in Rocko's
`agents/rocko/work/s4-follow-up/`. Filbert doesn't commit the candidate
source and doesn't push.
## Shape
The launcher has to live in the trusted bus host (`packages/cli/src/host.mjs`).
Only the host holds the IPC channel that binds a launch, and only the broker
can authorize `role.launch`. So:
```
PM session --(launch tool: cap, instance, model)--> host launch socket
host --IPC identity/authorize--> broker (action.allowed or refusal evidence)
host: instance list, family capacity, credential status, then spawn
host --IPC bindLaunch--> broker (session.launched)
host writes the cap file; the runner reads it, role.claim
session exit --IPC endLaunch--> broker (session.ended, claim released)
```
A managed session is a runner (`packages/harness/src/runner.mjs`) inside
`unshare --user --map-current-user --pid --fork --kill-child --mount-proc`.
The runner claims the role, loops on `message.receive`, runs one harness
turn per batch through the adapter (persistent session directory), and
sends the turn's answer back to the sender. A wall clock bounds each turn
(S0 line 4).
## Pieces
| Piece | Where | What |
|---|---|---|
| Bundle | `packages/harness/src/bundle.mjs` | prompt, policy, skills list, typed tools, manifest from `resolveInstance` output; per harness files (Pi: extension args; Claude: settings with the wrapped gate, MCP config) |
| Typed tools | `packages/harness/src/tools.mjs` | vocabulary action → tool; only actions the instance holds, plus reads |
| Pi extension | `packages/harness/src/pi-extension.mjs` | registers the typed tools; `tool_call` gate blocks tools outside the policy (S0 lines 1-3) |
| Claude gate and MCP server | `packages/harness/src/claude-gate.mjs`, `mcp-server.mjs` | PreToolUse command hook, wrapped `timeout -k 2 10 node gate || exit 2`, hook timeout 20 (S0 lines 1-4); minimal stdio MCP server for the typed tools |
| Runner | `packages/harness/src/runner.mjs` | the managed process; founder-credential stop (REQ-CRED-2) |
| Claude adapter | `adapters/claude/adapter.sh` | adapter contract, plus the bundle's settings and MCP files |
| Pi adapter | `adapters/pi/adapter.sh` | takes `MOSAIC_EXTENSIONS` (`-e`), still `--no-extensions` |
| Session launcher | `packages/seat/src/session.mjs` | spawn under unshare, registry file, launch log, stop |
| CLI | `packages/seat/src/cli.mjs` | `mosaic talk`, `mosaic stop <run>`, `mosaic launches off|on|status` |
| Host | `packages/cli/src/host.mjs`, `cli.mjs` | launch socket; `mosaic bus start <business> --pm <harness>:<model>` launches the PM without a window |
| Broker | `packages/bus/src/{broker,runtime,process}.mjs` | IPC ops `identity`, `authorize`, `refuse`, `endLaunch`, `credentialStatus`; `Broker.endLaunch` |
## Choices and tradeoffs (consequential)
1. **Outside the brief's file list:** `packages/bus` (three IPC ops and one
trusted method, the counterpart of `bindLaunch`) and `packages/cli`
(host launch socket, `--pm`). The brief says the PM launches "through the
broker", and the bus README leaves spawning, allowlists and capacity to
S6; neither can happen without these. No socket verb is added to the
broker, no event kind and no schema change.
2. **`mosaic launch` name:** `mosaic launch <seat>` stays the T3 seat
launcher. Role sessions are started by the host (the PM at boot, with
`--pm`) and by the PM's `launch` tool. `mosaic stop` and `mosaic talk`
are new verbs; `mosaic launches off` is Jason's one word (the broker's
`launch.revoke`).
3. **Capability handoff:** a 0600 file in the run directory, written after
the bind (the launch record needs the pid first). Weaker than an
inherited fd; same-UID either way.
4. **Lead decision 62 gap:** each session gets its own PID namespace, so a
`setsid -f env -i` child reparents to the runner, which is still under
the launch record's pid, and the human CLI's ancestry check refuses it.
Limits stay: the broker socket is shared, and a same-UID agent can still
ask something outside its tree (a systemd user manager, an existing tmux
server) to run a command. Recorded, not called a wall.
5. **Capacity:** families are the keys of `launch.max`; the family is the
model name containing `opus` or `sonnet`. A model in no listed family is
refused. The count includes every live managed session, the PM's too.
6. **Founder credentials (REQ-CRED-2):** the session environment is an
allowlist, so `GITEA_TOKEN` and friends never pass. The host puts the
broker's credential status (metadata only) for the instance in the
policy. The runner stops before claiming if a service the role needs has
no usable role token, or if a known founder credential variable reached
its environment.
## Gate
- Suites: `packages/harness`, `packages/seat`, every node suite, every
`scripts/test-*.sh`, sequential, teed, Docker pointed at a missing
socket.
- Recorded run: a scratch data root and a test business with fixture
tokens (no tracker), host started with `--pm`, `mosaic talk` asks the PM
to launch a coder, `mosaic agents` shows both claims. Not on Astra (R26),
and not against the live `mosaic-bus@mosaic-stack` unit.
- Darkwing approves on #1523.