build.patch dc346027…4454, candidate-manifest 5b3a934d…27f3, base915e00e5, 41 files, +4302/-55. Gate at2855e628in out/: every node suite and every test-*.sh green except test-task's live recall (needs Docker; the base fails the same two). The recorded PM launch waits on the Q14 cutover. Co-Authored-By: Claude Opus 5.5 <[email protected]>
9.2 KiB
Row 41 (#1523): slice 1 S6, candidate packet, round 1
Author: Filbert. Reviewer: Darkwing. Brief: docs/plans/2026-10-04_slice-1.md,
section "Slice 1 S6", blob 72d11de2. Plan: PLAN.md here (b6d2fe2b).
Rulings: lead decisions 77 and 78. Base: 915e00e5 (base.txt). None of
the 41 files changed between the base and 2855e628, where the gate ran.
The candidate source is uncommitted. There are no pushes. No token, private
binding or tracker host was read or written, and nothing touched the live
mosaic-bus@mosaic-stack unit or ~/.mosaic-dev/bus/.
Files (files.txt, 41)
build.patch is git diff --cached --binary 915e00e5 over those files,
+4302/−55. It applies cleanly to 2855e628 with git apply --index, and
sha256sum -c candidate-manifest.sha256 passes in that tree.
| Area | Files | What |
|---|---|---|
| Harness (new) | packages/harness/ |
bundle, typed tools, gate, Pi extension, Claude Code gate and MCP server, runner, tests, README |
| Adapters | adapters/claude/adapter.sh (new), adapters/pi/adapter.sh, adapters/README.md |
Claude Code adapter; Pi takes MOSAIC_EXTENSIONS as -e, and --no-approve |
| Sessions | packages/seat/src/session.mjs, proc.mjs (new), tests, README |
spawn under unshare, registry, launch log, stop |
| Launcher and verbs | packages/cli/src/launcher.mjs (new), host.mjs, cli.mjs, tests, README |
the launch socket in the trusted host; mosaic talk, stop, launches off|on|list; bus start --pm |
| Broker | packages/bus/src/{broker,process,runtime}.mjs, tests/end-launch.test.mjs, README |
trusted IPC launch ops; Broker.endLaunch |
| Other | docs/TOOLS.md, scripts/mosaic, scripts/agent-host-dev.sh |
verb reference; host seats pass --no-approve (the DEFERRED entry) |
Against the brief
- Bundles, Pi then Claude Code (
packages/harness/README.md, "The bundle"): prompt, policy, typed tools and a manifest from one resolved instance. Each manifest names the S0 lines it relies on (reliesOn). Skills: resolution runs without a skills source, so bundles list none (README "Limits"). - S0 lines (README table): Pi blocks in a
tool_callhandler, a throw blocks, a missing-erefuses to start, and a hang is bounded by the runner's wall clock plus theagent_endturn marker. Claude Code uses a command hook run astimeout -k 2 10 <gate> || exit 2with hook timeout 20;--bareis never passed.--restrictedis defence in depth only. Line 5 (bash) is the tool limit and is written as a limit. - The PM launches through the broker, within
launch: the host's launch socket checks the instance list, that the instance isn't already running, the model family againstlaunch.max(every running session counts, the PM's too), then the broker's ownrole.launchauthorization. Every launch, refusal and end is a launch-log line and a broker event.mosaic launches offis Jason's one word (launch.revoke).mosaic stop <run>ends a session. - Founder credentials (REQ-CRED-2): the session environment is an
allowlist (
ENV_ALLOW). The runner exits 20 before claiming if a known founder variable reached it, or if a service the role needs has no usable role token (the broker's credential status, metadata only). - The PM moves off T3:
mosaic bus start <business> --pmlaunches the business'slaunch.byinstance without a window, andmosaic talkreaches it. The handover of Sage's T3 thread is the acceptance run below.
Changes from the plan
- The capability goes to the runner as one stdin line, not a 0600 file. It is written after the bind and is never on disk, in argv or in the environment.
- Verbs live in
packages/cli, notpackages/seat(lead decision 77).packages/seatkeeps the session module and the moved/prochelpers (proc.mjs). --pmtakes no argument. The PM is the business file'slaunch.byinstance; its harness and model come from resolution.- Runner exit 23 covers a
role.claimwith no answer as well asbrokerRetriesfailed polls. - The adapter runs as
/bin/sh <adapter.sh>, because the repository keeps adapters 0644 and only the image sets the mode.
Broker surface
No socket verb, no event kind and no schema change. The new launch ops are
trusted IPC from the host to the broker process: identity,
authorizeLaunch, refuse, endLaunch, credentialStatus, beside the
existing bindLaunch. bindLaunch records the run only after its checks,
and a rebind of an ended run refuses with run-ended, also across a broker
restart. The host gains op(message) and beforeClose(fn).
Process control
- Early signals. The runner installs its SIGTERM and SIGINT handlers
before anything else, because pid 1 of a PID namespace ignores a signal
with no handler. Node's own startup still leaves a window, so the
launcher and
mosaic stopresend SIGTERM every second. isRunner(pid)iscmdline[1] === RUNNER, which skipsunshare's forked child before its exec (its argv still names the runner).- Host lost. A starting host reads the last host's
sessions.json. Before the launch socket opens, for each entry of this business it SIGKILLs the session if it still runs, rebinds the run with the recorded identity and ends it ashost-lost, which releases the role. A rebind that refuses withrun-endedlogs "was already ended". An unreadable or malformedsessions.jsonrefuses the host start with exit 3. - PID namespace limits are in
packages/harness/README.md, "Limits": same UID, shared broker socket and/tmp, network not confined, and a same-UID process can still ask something outside its tree (a systemd user manager, an existing tmux server) to run a command. The README no longer says the namespace blocksptracein general; it hides other sessions by pid.
--no-approve (DEFERRED: "Host seats launch Pi with --approve")
scripts/agent-host-dev.sh and adapters/pi/adapter.sh now pass
--no-approve. Pi 0.85.1 (trust-manager.js) gates project .pi/
resources on trust: settings.json, extensions, skills, prompts, themes,
SYSTEM.md, APPEND_SYSTEM.md. -e paths load in the "temporary" scope,
which trust doesn't gate. A scratch probe against a workspace saved as
trusted, with a mock Messages API, showed:
| argv | project SYSTEM.md |
explicit --skill |
generated prompt | -e extension |
|---|---|---|---|---|
host-seat, --approve |
loaded | loaded | loaded | loaded |
host-seat, --no-approve |
ignored | loaded | loaded | loaded |
adapters/pi/adapter.sh |
ignored | loaded | loaded | loaded |
The skill appears only when the session has a tool to read it; the probe's
first run used --no-tools and showed no skill in any case, the
--approve control included.
agent-host-dev.sh keeps --append-system-prompt, not the entry's
"explicit system prompt", because its comment preserves Pi's built-in coding
prompt on purpose, and --no-approve already closes the project
SYSTEM.md. I didn't edit docs/plans/DEFERRED.md: it holds another
seat's uncommitted changes. The entry can close when this lands.
scripts/test-goal-native.py still passes --approve on purpose (it tests
project extensions) and is untouched.
Gate
Run at 2855e628 with build.patch applied, in a detached worktree,
sequentially, each output in out/ (out/summary.txt). TMPDIR on the
scratch disk; DOCKER_HOST=unix:///nonexistent.sock, so nothing reaches
Docker.
| Suite | Pass | Fail |
|---|---|---|
| node: bus, business, cli, control-board | 74, 60, 77, 124 | 0 |
| node: conversation, discord, harness, ledger | 152, 178, 45, 78 | 0 |
| node: mosaic, queue, seat, tasks, webui | 69, 148, 27, 51, 14 | 0 |
| test-auth, conductor, config, discord | 15, 17, 24, 66 | 0 |
| test-extension-package, foundation, queue, release | 18, 44, 27, 4 | 0 |
| test-task | 26 | 2 |
The two test-task failures are "user recall run succeeds (exit 1)" and
"recalled user name". That check runs a live worker and needs Docker. The
unpatched base fails the same two (out/base-test-task.txt, identical
PASS/FAIL lines), so they are the environment, not this candidate.
An earlier gate over this candidate without the --no-approve edits had
test-queue fail F1 once ("a plain commit -e whose guard ran before
update-ref fails at its own HEAD update": only the stderr match). It passed
in two patched reruns, in the base run, and in this gate. See the
follow-ups below.
Acceptance run: waiting
The recorded run (the host started with --pm, mosaic talk asks the PM
to launch a coder, mosaic agents shows both claims) waits on the Vikunja
move to tasks.woltje.com (Q14), as Sage ruled: the integration commit and
the acceptance run wait for that cutover; build and review continue. It
will not run on Astra (R26) or against the live unit. The tracker host
comes only from vars.tracker.baseUrl; no code, test or launcher config
names one, and fixtures use 127.0.0.1 and example.test.
Follow-ups (not in this row)
packages/queue/tests/commit.test.mjs,pausedCommit, awaits the child'sexitrather thanclose, so its stderr can be unread whentest-queueF1 matches it.scripts/test-task.sh(:514-518): the live user-recall check needs Docker and isn't skipped when Docker is unavailable.