| name | dispatch-subprocess |
| description | The codex/pi arm of `/dispatch` — `doctrine worktree fork --worker` then spawn the worker as a subprocess with its cwd bound to the fork. Reached only from the `/dispatch` router on a codex/pi↔env-marker agreement; do not invoke directly. |
Dispatch — codex/pi arm
Spawn a worker via doctrine worktree fork --worker + subprocess spawn.
Drive loop lives in the /dispatch router.
Spawn — codex arm
doctrine worktree fork --base "$B" --branch "$BR" --dir "$D" --worker \
|| { echo "fork failed: $?" >&2; exit 1; }
env -C "$D" DOCTRINE_WORKER=1 codex exec "<pre-distilled prompt>"
Confined (bwrap); run the fork, check $?, then spawn. The worker inherits the
ambient env and compiles into its own in-tree $D/target (no env contract).
Spawn — pi arm (RPC mode)
doctrine worktree fork --base "$B" --branch "$BR" --dir "$D" --worker \
|| { echo "fork failed: $?" >&2; exit 1; }
cp AGENTS.md "$D/" \
|| { echo "AGENTS.md copy failed: $?" >&2; exit 1; }
PI_FIFO=$(mktemp -u) && mkfifo "$PI_FIFO"
{ printf '%s\n' \
'{"type":"set_auto_retry","enabled":false}' \
'{"type":"prompt","message":"<pre-distilled prompt>"}'
sleep 300
} > "$PI_FIFO" &
timeout 300 env -C "$D" DOCTRINE_WORKER=1 \
pi --mode rpc --thinking off --session-dir "$D/.pi-session" \
--no-extensions --no-skills --no-themes \
--offline --approve --tools read,bash,edit,write,grep,find,ls \
< "$PI_FIFO"
rm -f "$PI_FIFO"
Same confinement as codex arm; fifo keeps stdin open (pi RPC exits on EOF).
sleep 300 keepalive, agent_end gives typed completion. Ignore
extension_ui_request widget events from installed packages.
Boundary recording
At the funnel Record beat (router step 8), after the code commit:
doctrine slice record-delta <SL> PHASE-NN --commit <S> — the safe-default
single-commit mode. It derives [S^, S] and writes the per-phase boundary into the
primary-tree conformance registry (F-5 resolves it from the coord tree; F-6 guard;
upsert).
Pin S at step 7, do not read HEAD at step 8. Step 8 also trails knowledge
commits; if a knowledge commit lands before the record call, HEAD is no longer the
code commit. So capture S=$(git rev-parse HEAD) immediately after the step-7 code
commit and record --commit "$S" before trailing knowledge (or with the pinned
oid regardless of order). git diff S^..S is exactly S's own patch — trailed
knowledge and any refresh-base merge (which sits in S^) are excluded by
construction; this is what the old --start/--end range failed to do.
This arm has no record-boundary and the codex/pi symmetric ledger derive is
deferred (D6/IMP-171), so this record-delta is the arm's registry write —
RETAINED, every landed phase, orchestrator-issued. It is no longer skippable:
dispatch sync --prepare-review's completeness gate bail!s if a completed phase
lacks a registry row, so a forgotten write halts the pre-audit conclude beat instead
of silently passing (the ISS-052 failure mode). The same verb still doubles as the
manual escape hatch — --start/--end remains for a range / bootstrap. Its incoming
Manual is provenance-preserving — it never clears an existing funnel/legacy halt
(D12).
Red Flags
Never: eval; spawn outside env -C "$D"; omit timeout; use a heredoc
for RPC mode (stdin EOF kills pi).
Always: halt on fork failure; carry DOCTRINE_WORKER=1; use a fifo for RPC
stdin; rm -f the fifo after exit; return to the router for the funnel cadence.