A draft spec for the owner's review and 13 offline tasks with their given tests: shared tool arguments and host rules in proto, the sealed fetch target (M3a finding 14), the toolkit tools and SOCKS5 egress proxy, and brokerd's [runner], podman argument lists, runtime and proxy lifecycle. Each task's tests were run against a reference at that task's end state (560 to 638 tests, clippy clean); the reference is not in the repository. Adds the runner-unavailable runbook entry and tip T23 (ETXTBSY in script tests). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
M3b implementation plan: the runner and the tools
For the implementing model: do not work from this file. The owner gives you one task file at a time (
01-…to13-…). This file is the index for the owner and the reviewer.
Goal: an allowed tool call runs in a fresh rootless Podman container, with no network unless
it is http_fetch, which reaches the grant's hosts only through an egress proxy of our own. The
four tools and the proxy are programs in toolkit; brokerd builds the podman argument lists
and runs them.
Architecture: shared definitions move to proto first (01), the fetch target is sealed (02),
and grant paths that cannot be mounted are refused (03). toolkit gets its four tools (04 to 06),
the public-address check (07) and the proxy (08). brokerd gets [runner] (09), the argument
lists (10), the runtime (11), the proxy's lifecycle (12) and the wiring in serve (13).
Spec: docs/specs/2026-09-22-m3b-runner.md. Brief: docs/design.md. Every fail-closed
message ends with a pointer into docs/runbook.md; the runner-unavailable entry is already
there.
No task needs Podman, Nix or a network. Everything runs against fakes: fake curl and
podman scripts, a fake resolver, UnixStream::pair(). The image and the checks on straylight
are done by the design model afterwards.
Global constraints
- Everything in
AGENTS.md, including "Lessons from earlier reviews". - No new dependency.
toolkitgainsserdeandserde_json, both already vetted (task 04). - Branch
m3b. One task, one fresh OpenCode session, one commit. Runcargo fmt --allbefore the gate. Review happens once, after task 13. - Tests that write a script and run it take a lock (
serial()): otherwise another test's fork can make running it fail with "text file busy". Keep that pattern in anything you add.
Tasks
The last column is how the given tests were checked before hand-over (decision of 2026-09-18, tip T17). For M3b every task had a reference implementation: the tests of each task were run against it at that task's end state, then the reference was deleted so it cannot be read (tip T18).
| # | File | Delivers | Tests | Check |
|---|---|---|---|---|
| 01 | 01-proto-tools-hosts.md |
proto::tools, proto::hosts; brokerd::args uses them |
proto/tests/tools.rs, hosts.rs |
reference; the given test pinned an accepted serde weakness, removed |
| 02 | 02-brokerd-fetch-url.md |
ToolArgs::HttpFetch(FetchUrl), sealed (M3a finding 14) |
brokerd/tests/args.rs (changed), a compile_fail doctest |
reference |
| 03 | 03-brokerd-grant-mount-rule.md |
grant paths with : or , are invalid |
brokerd/tests/grants_mount.rs |
reference |
| 04 | 04-toolkit-files.md |
the toolkit program, read_file, write_file |
toolkit/tests/files.rs, support/mod.rs |
reference |
| 05 | 05-toolkit-shell.md |
shell |
toolkit/tests/shell.rs |
reference |
| 06 | 06-toolkit-fetch.md |
http_fetch through curl |
toolkit/tests/fetch.rs |
reference; found the ETXTBSY race (1 in 7 runs), fixed with serial() |
| 07 | 07-toolkit-addr.md |
is_public |
toolkit/tests/addr.rs |
reference; found ::/96 missing from the spec, added |
| 08 | 08-toolkit-egress-proxy.md |
the SOCKS5 egress proxy | toolkit/tests/egress.rs |
reference; 20 runs clean |
| 09 | 09-brokerd-runner-config.md |
[runner] |
config_runner.rs, 5 fixtures, support/rig.rs (changed) |
reference |
| 10 | 10-brokerd-podman-args.md |
the podman argument lists; RunSpec gains session and call |
podman_args.rs, 6 golden files |
reference |
| 11 | 11-brokerd-container.md |
the Podman runtime | container.rs, support/fake_podman.rs |
reference; 20 runs clean |
| 12 | 12-brokerd-egress.md |
the proxy's lifecycle for http_fetch |
container_egress.rs |
reference; 20 runs clean |
| 13 | 13-brokerd-serve-runner.md |
brokerd serve uses the runtime |
serve_runner.rs |
reference |
At the end: make gate prints gate: ok with about 638 tests.
Running it
BOXMAKER_MODEL=straylight/ornith-1.5-35b-a3b tools/run-plan.sh docs/plans/M3b
Keep the OpenCode TUI closed while it runs.