# 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 `, `mosaic launches off|on|status` | | Host | `packages/cli/src/host.mjs`, `cli.mjs` | launch socket; `mosaic bus start --pm :` 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 ` 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.