About dispatch-spawn
dispatch-spawn is a skill for launching confined workers and receiving their working trees, intended for orchestrating Agents. It fails closed when confinement cannot be established and routes results through import gates and verification.
Key features
- 01Probe confinement and fail closed
- 02Fork an unbound worker branch
- 03Run workers inside a confined environment
- 04Import tree changes through scope checks
- 05Verify, record phases, and reap forks
Use cases
- 01Delegate development in a confined worktree
- 02Review and import worker-produced code changes
- 03Verify imported changes on the coordination branch
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ davidlee/ doctrine --skill dispatch-spawnAfter installing, type/dispatch-spawnin Claude Code to confirm it loaded.
Details
From the project READMEDispatch — spawn a confined worker
One spawn shape, for every harness. The drive cadence lives in the
/dispatch router; this skill is the spawn call plus the
landing mechanics.
See lib:reference/dispatch-mechanics.md § The worker returns a working tree,
not a commit.
Spawn
scripts/spawn-confined.sh <harness> <B> <BRANCH> <DIR> <PROMPT_FILE> [BACKSTOP_SECS]
<harness> is pi or claude. The script does the whole pre-spawn sequence
itself — you do not fork separately:
- Capability probe. Linux asserts
bwrapis present and refusesbwrap-unavailablebefore minting a fork; macOS gets its named refusal fromdoctrine worktree jail-prefix, which fails closed and writes no prefix. There is no unconfined fallback — an unresolvable jail aborts the spawn rather than degrading (an empty prefix is caught by its own guard). - Fork.
doctrine worktree fork --base <B> --branch <BRANCH> --dir <DIR> --worker, orchestrator-classed, run from the project root before any confinement. The fork is deliberately unbound — no--slice/--phase(see The funnel, below). - Confine + exec.
timeout <BACKSTOP> <jail prefix> <harness exec>, delivering<PROMPT_FILE>to the worker (stdin forclaude; one compact RPC line forpi) and capturing the transcript. The jail rw-binds the fork dir and the harness config dir, ro-binds the rest, and setsDOCTRINE_WORKER=1inside the namespace. - Hand back.
claude -pexits when the turn ends — process exit is the completion signal.pi --mode rpcnever self-exits, so that profile alone holds stdin open with a fifo and polls for its typed settle event. That asymmetry is the only harness difference beyond the config dir and the exec line.
See lib:reference/dispatch-mechanics.md § Workers can silently discard their
own work for the prompt-fencing rules (clean checkout, forbidden verbs,
edit-not-discard for a red proof).
Never trust the worker's self-reported success. The tree is the ground truth.
Landing
The delta is an uncommitted working tree that persists after the process exits — nothing auto-reaps it.
- Import.
doctrine worktree import --base <B> --from-worktree <DIR> --slice <N>— gathers the tracked + untracked delta, runs theclassify_importscope belt and the reject-and-halt prove gate perlib:reference/dispatch-mechanics.md— a red delta halts staged, never auto-fixed (land-or-reject, never rewrite). Any refusal is report-and-halt — never import around it. - Commit ONE on the coordination branch — the orchestrator's own act, and the reason import is non-committing.
- Record the boundary.
doctrine slice record-delta <N> PHASE-NN --commit <S>, withSpinned at the code commit, never read fromHEADat record time. Knowledge commits trail the code commit, soHEADmay have moved;--commitrecords exactlyS's own patch, which excludes trailing knowledge and any refresh-base merge by construction. This write is not skippable —dispatch sync --prepare-review's completeness gate halts if a completed phase has no registry row. - Verify and conclude. Run the phase's verification on the coordination
tree (the worker's own report is advisory), then flip the sheet:
doctrine slice phase --status completed <N> PHASE-NN. - Reap the spent fork:
doctrine worktree gc --fork <BRANCH>— the unfunnelled-fork case its patch-id oracle is narrowed to, and idempotent. Nevergit worktree remove --forcean unimported tree: that deletes the sole copy of the delta.--dry-runprints the verdict and destroys nothing.
The funnel: a bound fork's machinery, not this path's
See lib:reference/dispatch-mechanics.md § The funnel record, and when it is
the driver — this path forks unbound, so no row lands and next sits at
spawn.
Red Flags
Never: instruct or expect a worker to commit (its .git is read-only —
there is no self-commit path); spawn outside the script (a hand-rolled exec is
an unconfined worker); omit the backstop timeout; eval the prompt; use a
heredoc for the pi profile's RPC stdin (EOF kills it); import around a scope or
prove-gate refusal (a refusal is a defect or a halt, never a detour); auto-fix a
red delta into the import; force-remove a fork before its delta has landed;
trust the worker's own green/failure labels; treat a spawn prescription from
dispatch next as a stuck funnel.
Always: halt on a non-zero spawn (fail-closed is the whole posture — a
confinement that cannot be established means no worker); pass a pre-distilled,
self-contained prompt; import with --slice so the scope belt has selectors to
check; pin S at the code commit before recording the boundary; return to the
router for the drive cadence.