/substrate:orchestrate
Execute a graphed epic's bead DAG as a parallel worktree fleet. This is substrate's primary
execution door — the default way to run a spec once it's graphed. /substrate:graph-spec produces
the DAG; this skill runs it. It is the "orchestrator" role that
references/docs-core/docs/doctrine/agents-parallel-execution-doctrine.md describes — that doctrine
holds the why; this skill holds the operational loop. Read the doctrine once; do not restate
its rationale here.
Orchestrated (this skill) is the primary strategy; attended (/substrate:execute) is the
single-window alternative. Orchestrated = K context-budget windows, agent-coordinated,
unattended. Attended = one window, one implementing agent, a human co-pilots with phase-gate
pauses. Both are the same partition (§Grouping & windows) at different K; pick attended when you
want to watch/adapt one window or the spec fits one window and you prefer HIL.
Arguments
<epic-or-spec> — either an epic label epic:<slug>, a bare <slug>, or a spec path
docs/tasks/ongoing/<slug>/<slug>-spec.md (its epic label is derived from the directory).
--auto — run all waves unattended (skip the between-wave approval pause). Default is
pause between waves, mirroring /substrate:execute's pause-between-phases ethos.
--pr — review-first landing. Instead of landing one squash commit on trunk (Step 6.3),
push feat/<epic-slug> after every wave (so an open PR accumulates the per-bead commits
live, wave by wave) and suppress the trunk-squash entirely — the PR is the deliverable and
GitHub's Squash and merge becomes the single squasher (it re-authors one clean commit, absorbing
the unsigned per-bead commits). Use it when you want the epic reviewed before it reaches trunk
rather than after; implies no interactive trunk landing. Composes with --auto (the two are
orthogonal: --auto skips the pause, --pr changes the landing). MUST NOT be combined with the
Step 6.3 trunk-squash — they are mutually exclusive by construction.
When to run
- The epic has been graphed:
epic:<slug> beads exist and bash docs/scripts/bead-graph.sh --epic <slug> renders waves.
substrate.yaml declares a gate block (the objective done-signal every bead runs).
- At least one wave is worth fanning out (multiple file-disjoint beads). A single-file or
strictly-serial DAG is fine too, but the win is smaller —
/substrate:execute's sequential path
is equally valid there.
When to REFUSE
| Signal |
Redirect |
No epic:<slug> beads / bead-graph.sh errors |
Graph the spec first: /substrate:graph-spec <spec>. No worktrees created. |
substrate.yaml missing or its gate block absent |
Abort with an explanation — do not probe a toolchain (root CLAUDE.md gate policy). Fix substrate.yaml, then re-run. |
No tracker (tbd) configured |
Single-writer orchestration needs a tracker. Use /substrate:execute sequential instead. |
| Arg empty |
Ask for the epic slug or spec path. |
The dispatch seam (tool-agnostic)
The only tool-coupled seam is subagent dispatch. Everything else (git, tbd, the gate) is portable shell.
|
Claude Code |
OpenCode |
| Portable floor |
Agent tool — one bead-implementer (group-runner) call per ready window in a wave (parallel calls in one message) |
Task tool — one task per ready window (parallel where the runtime supports it; sequential fallback, logged) |
| Fast-path (CC only, v1) |
Workflow tool — deterministic per-wave pipeline, journaled resume, budget control over the same loop contract |
(n/a — NL floor only) |
The two CC paths are behaviorally equivalent on the DAG; the Workflow fast-path adds
resume/determinism, not different semantics. On OpenCode, DAG correctness is preserved even when
Task calls serialize; only concurrency is optional.
Workflow — the operational loop
Step 1. Resolve <slug>
From the arg: epic:<slug> → <slug>; a bare <slug> → itself; a spec path
docs/tasks/ongoing/<slug>/… → the directory name. This is the canonical epic label; it is the join
key, not the parent link.
Step 2. Read the DAG (never re-derive it)
bash docs/scripts/bead-graph.sh --epic <slug> # topological waves — the authoritative schedule
Every id in one wave is safe to run in parallel; waves run in order. Fail-fast if there are no
epic:<slug> beads or the script errors — prefer the machine waves output over eyeballing wave
art; a parse error aborts before any worktree is created (FMEA #7). --format mermaid is available
for a visual sanity check.
Read the partition. Each bead carries a group:<window-N> label (written by
/substrate:graph-spec per agents-parallel-execution-doctrine.md §Grouping & windows) — the
context-budget window it belongs to. Beads in one window are file-adjacent and run sequentially in
one worktree; distinct windows within a wave are file-disjoint and run in parallel. The
dispatch unit is the window, not the bead. The partition is a deviatable prior: you MAY
re-batch it (merge two tiny windows, split an over-full one) when runtime judgment warrants — but if
you do, log the deviation: mint a run-id (<epic>-<YYYYMMDD-HHMM>) and append the reason +
the planned-vs-actual windows to .substrate/runs/<epic>/<run-id>/deviation-log (gitignored). No
deviation → no log entry needed. Absent group: labels (an ungraphed or pre-partition DAG) → fall
back to one bead per window (the classic per-bead behavior).
Step 3. Read substrate.yaml
Read gate.{compile,test,lint}, optional gate.out-of-band, worktree-seed[],
toolchain-pin.{install,env}, and optional execution.resource-envelope. Abort with an explanation if the gate block is missing — do not
probe a toolchain (root CLAUDE.md policy; FMEA #2 phantom-gate mitigation depends on these keys).
Warn on an undeclared seed (no-silent-fallback). If worktree-seed is absent or empty and
the repo's .gitignore names build/dependency paths the gate plausibly needs (node_modules,
.venv/venv, target, dist/build, generated-client/codegen dirs, .env*), do not proceed
silently: warn that a fresh worktree contains only tracked files, so the gate may fail spuriously,
and that seeding will then fall to manual per-run copying. Point the user at substrate.yaml's
worktree-seed[]/toolchain-pin block (populate it — /substrate:adopt can, or edit by hand) and
pause for confirm-to-proceed-unseeded. This is a warning, not an abort: a repo whose gate needs
no gitignored input is free to run with an empty seed.
Note the resource envelope. If execution.resource-envelope is present
({env, cores-budget, floor}), each window's gate runs with <env>=max(floor(cores-budget / K), floor) for the K windows dispatched in that wave (5c.4). Absent it, the gate runs unbounded — fine
at K=1, an oversubscription at K>1 (doctrine §Supporting → A window's gate runs under a declared
resource envelope).
Note the gate-coverage floor. gate.{compile,test,lint} is the minimum re-gate, not
necessarily the whole suite the epic exercises. As you read the DAG (Step 2), collect the distinct
per-bead Gate_i commands: any Gate_i that is not subsumed by gate.* means gate.*
under-covers this epic — e.g. a frontend vitest a bead runs while gate.test only runs the backend
suite, or a tsc-only bead narrower than gate.*. Do not treat gate.* alone as the
composition net: the per-wave re-gate (5e) runs the union of gate.* and the per-bead gates
exercised that wave. Surface this once up front so it's visible that the integrated re-gate is
broader than substrate.yaml's literal gate block. (If a bead's gate is a strict subset of
gate.*, /substrate:graph-spec will have tagged it gate-scope: partial — a reminder that its
green is partial and only the union re-gate authorizes the merge.)
Step 4. Setup — integration branch, run-state anchor, unattended signing
git switch -c feat/<epic-slug> # or: git switch feat/<epic-slug> (reuse if it exists)
git config extensions.worktreeConfig true # once, idempotent: lets each worktree carry its own config
4.1 Cut/reuse feat/<epic-slug> from trunk.
4.2 Initialise the run-state entry — before any worktree exists. Write
.substrate/execution-state.json's <epic> key now:
{ "<epic>": { "run-id": "<epic>-<YYYYMMDD-HHMM>", "partition": { "window-1": ["…"] },
"re-gates": [], "outcomes": {}, "run-log": ".substrate/runs/<epic>/<run-id>/" } }
Every later write (5c.1 dispatched, 5e re-gates[], 5f merged/verified) appends to this
entry. Skip this and each of those is a write into a missing key, so an aborted run leaves no
ledger at all — which is exactly what happened on the 12-window sky-journal run (2026-08-04): real
merged work, a written deviation log, and no execution-state.json entry, because entry creation
lived only in Step 6.1 and the run never reached it.
4.3 Signing is scoped to the worktrees, never to the repo. extensions.worktreeConfig above is
the only signing-related change made to the primary checkout, and it changes no signing behaviour
by itself. Do not flip repo-local commit.gpgsign false: that config is shared with the primary
checkout, so a human's mid-run commit lands silently unsigned and a killed run never reaches the
restore. Per-worktree unsign happens at creation (5c.2); orchestrator-authored merges pass
-c commit.gpgsign=false per invocation (5d, 6.3). Nothing needs restoring at close — see
doctrine §Supporting → Unattended signing.
Step 5. Per wave, in order
For each wave the DAG emits, in order:
5a. Filter to ready windows. Keep beads whose blockers are all closed or merged —
tbd ready / tbd show <id>. Merge, not close, is the unblock signal (doctrine §Policy-4), so a
merged-but-open Policy-4 bead still unblocks its dependents. Then group the ready beads by their
group:<window-N> label — a window is dispatchable once all its beads are ready. The dispatch
unit is the window.
5b. File-disjoint guard (across windows). Pairwise-intersect each ready window's Files
(the union of its beads' Files). Any collision splits the colliding windows into consecutive
sub-waves — merge one, re-gate, then the next branches off the new tip. This is a second net over
graph-spec's partition + edges (doctrine §Supporting → File-disjoint waves; FMEA #3). Never run two
windows that touch the same file in one wave. (Within a window, beads co-edit shared files by
construction — that's why they share one worktree and run sequentially, not in parallel.)
5c. Dispatch — per ready window:
tbd update <id> --status in_progress for each bead in the window (orchestrator-only write), and stamp each bead's run-state outcome: dispatched in .substrate/execution-state.json.
git worktree add <path> -b <window-branch> feat/<epic-slug> — one worktree per window, off the current integration tip, so it already contains merged blockers. Never branch off stale trunk. Then immediately git -C <path> config --worktree commit.gpgsign false, so this window's commits never reach an interactive signer and the primary checkout's config stays untouched.
- Copy every
worktree-seed[] path from the primary checkout into the worktree, then run toolchain-pin.install once for the window (seeding cost is O(K windows), not O(N beads)). Seed before dispatch or the gate fails on a phantom (doctrine §Supporting → Seed …; FMEA #2). toolchain-pin.install must stay idempotent. Seed immutable inputs only — never a handle to shared mutable state (a local dev-deployment dir, a live state cache); regenerate those offline in the worktree, or route the bead to the primary checkout.
- Compute this window's resource share from
execution.resource-envelope — max(floor(cores-budget / K), floor) for the K windows in this wave — and inject it as <env>=<share> in front of the window's gate commands, alongside toolchain-pin.env.
- Dispatch one group-runner (
bead-implementer; Agent tool / Task tool / Workflow stage) with, inlined:
- the window id and its N sequenced bead tuples — each
{Goal_i, Files_i, Gate_i, spec-ref_i}, the gate fully env-resolved (toolchain-pin.env prefix + gate.* literals + any bead override), so it resolves in a worktree with no shell version-manager;
- the window's doctrine digests — push, don't point. Union the
doctrine:<id> labels across the window's beads (stamped by /substrate:graph-spec Step 4.55 from write-scope ∩ the manifest's paths:), dedupe by <id>, and inline the output of bash docs/scripts/doctrine-digest.sh <id> — that doctrine's Binding Rules block — once for the window, not once per tuple, each block labelled with its id and the beads it binds. This is the push tier: cheap, and it provably arrived, where a pointer verifies nothing (docs/doctrine/agents-doctrine.md §2.1 — the reasoning lives there; don't restate it). Escalate to the full doctrine body when a single bead's write-scope majority falls inside one doctrine's paths: — that bead sits squarely in the doctrine, so inline that whole file for its tuple in place of the digest. Bound it:
- At most one escalation per window, the doctrine covering the most write-scope. A full body costs ~6–10× its digest (measure it:
wc -l <doctrine-path> vs bash docs/scripts/doctrine-digest.sh <id> | wc -l), and a window whose beads' majorities land in different doctrines isn't squarely inside either — that's a mis-partition signal, not a licence for two full bodies.
- Never truncate a digest, and never drop one to fit. Half-delivered binding rules are worse than a pointer: the runner acts on a subset of the MUSTs believing it has them all. If the deduped union is outsized (≳200 lines — roughly 4 digests at the 35–65 each this repo measures), drop the escalation to digest-only first; if digests alone are still outsized, the window is bound too widely — narrate it and consider re-batching per Step 2's deviatable prior. The prompt is the symptom; the partition is the fix.
- Degrade silently. No
doctrine: labels on the window's beads, or no docs/scripts/doctrine-digest.sh in the repo → inline nothing, say nothing, compose the dispatch exactly as it would be composed without this bullet. Not a warning, not an abort: most repos have never adopted paths:, and a warning would fire on every window of every one of them. A label whose id the script rejects (non-zero exit — a stale label surviving a manifest edit) is skipped, mentioned once in the wave narration, and never fatal.
- the relevant
CLAUDE.md — repo context, no longer the doctrine channel. It carries orientation and doctrine pointers; when digests are inlined above they are the binding doctrine for this window and already in the runner's context, so the pointers are the fallback for what they don't cover. In a repo that stamps no doctrine: labels, the pointer is all there is and the runner chases it as before;
- the standing rule verbatim — "no tbd, no git push — implement each bead in sequence, run each bead's gate, report a per-bead pass/fail ledger + a diff summary."
- for any bead carrying an out-of-band gate, say so, and ask for that bead's out-of-band checklist + the single swappable seam.
5d. Collect results (read the per-bead ledger). The group-runner returns a per-bead ledger
(pass | fail | unstarted) covering every bead in the window:
- All-pass → merge
<window-branch> → feat/<epic-slug> with git -c commit.gpgsign=false merge … (per-invocation, never a config flip); git worktree remove <path> and git branch -D <window-branch> (hygiene — an unchanged worktree auto-cleans; the branch does not). Newly-unblocked dependents dispatch off the updated tip.
- Stopped mid-window at bead i → the runner leaves clean per-bead commits for the
pass prefix (beads 1..i-1). Merge that green prefix if the ledger shows those beads gated green; keep bead i and the unstarted remainder open, tbd update <id> --notes "<failure>". Block only the transitive dependents of the open beads — sibling windows continue (partial progress is a core DAG win; FMEA — mid-window failure). Fix or escalate the failed bead.
5e. Re-gate the integrated tip — the union gate. After the wave's merges, the orchestrator
runs, once on feat/<epic-slug>, the union of gate.{compile,test,lint} and every distinct
per-bead Gate_i exercised by the beads merged this wave (deduped). This union — never gate.*
alone — is the sole merge-authorizing signal for the wave; the per-bead gates a group-runner ran
are fast pre-checks that fail early inside a window, not the composition net. The union is what
makes the re-gate sound: a per-bead gate can be narrower than gate.* (a tsc-only frontend
bead), and gate.* can be narrower than the suites the wave touched (a gate.test that runs
only the backend suite while a frontend vitest ran per-bead). Re-gate with gate.* alone and a
whole suite the wave exercised never gets composed-checked — so a green-reported wave can sit on a
red integrated tip (doctrine §Supporting → Re-run the gate on the integrated branch; FMEA #4). Two
independently-green branches can still fail composed. Record the wave's re-gate into
.substrate/execution-state.json as it runs — and if the <epic> entry is missing when you go
to append, you skipped Step 4.2: stop and fix that rather than creating the entry late, because
everything the run has already recorded went nowhere — — {wave, commands: [...], result, tip-sha} (Step 6;
the file is written incrementally per wave, not only at close, so an aborted run still leaves the
evidence). Red = composition failure: halt the wave transition, attach notes, fix before any
dependent dispatches. A wave with no recorded re-gate entry is a protocol violation.
5f. Record outcome — defer close (two-stage gate, non-destructive). For each green, merged bead,
advance its run-state outcome, do not tbd close:
- On merge →
outcome: merged; after the wave's green union re-gate (5e) → outcome: verified.
The bead stays open in the tracker — verified is the live done-signal a watcher renders,
and the tbd close is deferred to the terminal batch (Step 6.2). Merge (not close) already
unblocked its dependents, so deferring the close changes no scheduling — and no bead flips
closed mid-run, so a spurious close can't drop it out of the open view.
- Out-of-band gate applies (hardware / paid service / manual) →
outcome: oob-pending +
tbd update <id> --notes "merged; awaiting <out-of-band> gate". It stays open; close waits for
the full gate a human runs later (doctrine §Policy-4).
5f-pr. Push the integration tip (only under --pr). Immediately after the wave's union re-gate
is green (5e), git push origin feat/<epic-slug>. This is what makes the PR update live, in
wave-sized bursts — the per-bead commits merged this wave become visible on the PR the moment the
integrated tip is authorized. On the first wave, ensure the PR exists (gh pr view feat/<epic-slug> || gh pr create -f -H feat/<epic-slug>). A red re-gate pushes nothing (halt per 5e).
5g. Pause for approval with a wave summary (beads merged / left-open / red, re-gate result,
next wave preview) — unless --auto. n/pause stops cleanly so the user can inspect. Under
--pr --auto (fully unattended) there is no pause; the PR is the inspection surface.
5h. The terminal doctrine-reconciliation node (final wave). The epic's last wave is always the
solo kind: doctrine-reconciliation node graph-spec emitted (blocked-by every other bead, so it
runs alone against the fully integrated tip). Dispatch it like any window, with two things to know:
- Its group-runner edits
docs/doctrine/** in its worktree to codify the ratify-only doctrine
change the epic earned. That is an ordinary working-tree change, not a tracker write — the
single-writer invariant is untouched (the runner still runs no tbd, no git push; you merge
and record as always). Doctrine files land inside this epic's diff, co-revertable with the feature.
- Ratify-only is enforced by the re-gate, not a bespoke lint. Its gate is the full union gate on
the integrated tip (5e); since the mutation may only codify what the code already did, a green
re-gate is the proof it was ratify-only. A red re-gate means the doctrine edit introduced a
rule the shipped code violates — that is out of scope for this node: have the runner revert the
doctrine edit (or do it yourself before merge) and note it as follow-up for
/substrate:synthesize-session. There is no amendment queue to fall back on — the node either
applies a green ratify-only change or reverts to a no-op.
Step 6. Epic close
- Finalize
.substrate/execution-state.json — the durable run-state. This file is written incrementally, not once at the end: stamp the run-id + chosen partition at run start, append a re-gates[] entry after every wave's union re-gate (5e), and record each bead's outcome as it merges — so a crash or an aborted run still leaves a partial, truthful ledger (and the re-gate history that makes a composition failure diagnosable after the fact). At epic close, before the squash, finalize it: under the <epic> key record the run-id, the chosen partition (window → bead-ids), any deviations from graph-spec's suggestion (with reasons, mirroring the run-log), the per-wave re-gates ([{wave, commands, result, tip-sha}] — the union-gate proof), the per-bead outcomes (status: dispatched|merged|verified|oob-pending|fail|closed + merged commit sha or null — the non-destructive lifecycle a watcher renders), and the run-log pointer (.substrate/runs/<epic>/<run-id>/). Schema in agents-parallel-execution-doctrine.md §Grouping & windows. This file stays tracked (only .substrate/runs/ is gitignored) and is committed alongside the squash.
- Terminal batch close, then one
tbd sync — orchestrator-only, at epic close (or an explicitly agreed checkpoint). Close every verified bead in a single bulk call (tbd close <id1> <id2> … --reason "gate green", stamping each outcome: closed) — this is the only tbd close in the run — then run the one tbd sync. auto_sync stays off; never sync mid-flight from a worktree (doctrine §Policy-3 → Batch sync). Beads left oob-pending stay open and close later, as their out-of-band gates pass.
- Land the epic — two modes:
- Default (local landing): land
feat/<epic-slug> on trunk as one signed commit (including .substrate/execution-state.json): git switch <trunk> → git -c commit.gpgsign=false merge --squash feat/<epic-slug> → git commit -S -m "...". Squash keeps the unsigned bead commits out of trunk history. This is the run's single interactive-signing moment. Then reap: git branch -D feat/<epic-slug> (its content is in the squash; its history is in execution-state.json).
--pr mode (PR landing): do NOT touch trunk. Commit .substrate/execution-state.json onto feat/<epic-slug>, git push origin feat/<epic-slug> a final time, and ensure the PR is open (gh pr view … || gh pr create -f). The squash-to-trunk is deferred to GitHub's Squash and merge on the PR (the single squasher). The unsigned bead commits are legitimate on the PR branch; GitHub re-authors them into one commit at merge.
- Verify signing posture —
git config --local --get commit.gpgsign must still be true. This is a check, not a restore: signing was never disabled repo-wide, only per worktree, and those overrides died with their worktrees. If the check ever fails, something flipped the shared config and that is the bug (doctrine §Supporting → Unattended signing; FMEA #1).
CC Workflow fast-path (v1, optional at runtime)
On Claude Code, the per-wave dispatch MAY run as a Workflow-tool pipeline: each wave is a stage,
each ready window a bead-implementer (group-runner) within it, with journaled resume and budget control over
the same loop contract (steps 5a–5f). It is a performance/determinism layer, not different
semantics — the portable NL floor (Agent tool, one call per window) is the default and the only path
on OpenCode. Do not hardwire Workflow as the sole mechanism.
Constraints
- MUST read the DAG from
bead-graph.sh --epic <slug>; MUST NOT re-derive it. Fail-fast on a parse error before any worktree exists.
- MUST abort with an explanation if
substrate.yaml's gate block is missing — never probe a toolchain.
- MUST warn (not abort) before dispatch when
worktree-seed is undeclared but .gitignore names build/dep paths the gate plausibly needs, and pause for confirm — never silently dispatch into worktrees that will fail the gate on an unseeded input (no-silent-fallback).
- MUST initialise the
<epic> run-state entry before the first dispatch (Step 4.2). A run that appends re-gates into a missing key leaves no ledger at all.
- MUST honor the single-writer invariant: only the orchestrator runs
tbd update/close/sync or git push. Subagents receive Goal/Files/Gate inlined and return a result.
- MUST push the window's deduped
doctrine:<id> digests into the dispatch prompt when its beads carry those labels (5c.5) — once per window, escalating to at most one full doctrine body — and MUST NOT truncate a digest to fit. MUST degrade to today's composition silently when there are no labels or no doctrine-digest.sh: no digest block, no warning, no abort.
- MUST branch each bead worktree off the current integration tip, merge-on-green, and re-gate the integrated tip each wave with the union of
gate.* and every per-bead gate exercised that wave — the union re-gate (never gate.* alone) is the sole merge-authorizing signal, and each wave's {wave, commands, result, tip-sha} MUST be recorded incrementally in .substrate/execution-state.json. A wave with no recorded re-gate entry is a protocol violation.
- MUST inject each window's
execution.resource-envelope share into its gate when the repo declares one.
- MUST enforce file-disjoint waves (pairwise-Files guard) beyond graph-spec's edges.
- MUST apply the two-stage gate non-destructively: headless-green → merge + unblock dependents + advance run-state
outcome (merged → verified); defer every tbd close to the single terminal batch (Step 6.2) so no bead flips closed mid-run; out-of-band proof → oob-pending, left open + noted until a human runs it. verified (not a mid-run close) is the live done-signal a watcher renders.
- MUST scope unattended signing to the worktrees (
extensions.worktreeConfig + per-worktree commit.gpgsign false, -c commit.gpgsign=false for orchestrator-authored merges) and MUST NOT flip the shared repo-local config. Land trunk as one signed squash commit; at close, verify the primary is still signing rather than restoring it.
- MUST reap debris: window branches on merge,
feat/<epic-slug> after the squash lands.
- MUST pause between waves unless
--auto. Never silently fan out beyond the DAG.
- MUST, under
--pr, push feat/<epic-slug> after each green wave re-gate and suppress the Step 6.3 trunk-squash — the two are mutually exclusive. The PR (squash-merged on GitHub) is the sole landing; the orchestrator MUST NOT create a trunk commit in --pr mode. Signing stays disabled during the run and is restored unconditionally at close exactly as in the default mode.
- MUST stay tool-agnostic — Agent↔Task is the only seam. The CC Workflow fast-path is additive, not required.
- MUST keep this body under ~500 lines — link to the doctrine for rationale, don't restate it.
- SHOULD narrate each wave (dispatched beads, gate results, merges) so the user sees liveness on long epics.
1---2name: orchestrate3description: The PRIMARY execution door: run a graphed bead DAG as a parallel git-worktree fleet, operationalizing agents-parallel-execution-doctrine.md. Invoke with an epic (epic:<slug>) or a spec path whose epic has been graphed by /substrate:graph-spec. Reads the context-budget partition (group:<window-N> labels), cuts a feat/<epic-slug> integration branch, walks the DAG wave-by-wave, dispatches one group-runner per file-disjoint ready WINDOW in its own worktree (off the current integration tip, one seed+install per window), gates each bead in sequence, merges on green, re-gates the integrated tip, pauses between waves (--auto to skip), writes .substrate/execution-state.json, and lands one signed squash commit on trunk. Single-writer tracker (only the orchestrator runs tbd/git push). Tool-agnostic: Agent tool on Claude Code, Task tool on OpenCode; a CC-only Workflow fast-path is layered over the same loop. Consumes the DAG — it does not re-derive it. /substrate:execute is the attended single-window alternative. Fail-sa4---56# /substrate:orchestrate78Execute a graphed epic's bead DAG as a **parallel worktree fleet**. This is substrate's **primary9execution door** — the default way to run a spec once it's graphed. `/substrate:graph-spec` produces10the DAG; this skill runs it. It is the "orchestrator" role that11`references/docs-core/docs/doctrine/agents-parallel-execution-doctrine.md` describes — that doctrine12holds the **why**; this skill holds the **operational loop**. Read the doctrine once; do not restate13its rationale here.1415> **Orchestrated (this skill) is the primary strategy; attended (`/substrate:execute`) is the16> single-window alternative.** Orchestrated = K context-budget windows, agent-coordinated,17> unattended. Attended = one window, one implementing agent, a human co-pilots with phase-gate18> pauses. Both are the *same* partition (§Grouping & windows) at different K; pick attended when you19> want to watch/adapt one window or the spec fits one window and you prefer HIL.2021## Arguments2223- `<epic-or-spec>` — either an epic label `epic:<slug>`, a bare `<slug>`, or a spec path24 `docs/tasks/ongoing/<slug>/<slug>-spec.md` (its epic label is derived from the directory).25- `--auto` — run all waves unattended (skip the between-wave approval pause). Default is26 **pause between waves**, mirroring `/substrate:execute`'s pause-between-phases ethos.27- `--pr` — **review-first landing.** Instead of landing one squash commit on trunk (Step 6.3),28 **push `feat/<epic-slug>` after every wave** (so an open PR accumulates the per-bead commits29 live, wave by wave) and **suppress the trunk-squash entirely** — the PR is the deliverable and30 GitHub's *Squash and merge* becomes the single squasher (it re-authors one clean commit, absorbing31 the unsigned per-bead commits). Use it when you want the epic reviewed before it reaches trunk32 rather than after; implies no interactive trunk landing. Composes with `--auto` (the two are33 orthogonal: `--auto` skips the pause, `--pr` changes the landing). MUST NOT be combined with the34 Step 6.3 trunk-squash — they are mutually exclusive by construction.3536## When to run3738- The epic has been graphed: `epic:<slug>` beads exist and `bash docs/scripts/bead-graph.sh --epic <slug>` renders waves.39- `substrate.yaml` declares a `gate` block (the objective done-signal every bead runs).40- At least one wave is worth fanning out (multiple file-disjoint beads). A single-file or41 strictly-serial DAG is fine too, but the win is smaller — `/substrate:execute`'s sequential path42 is equally valid there.4344## When to REFUSE4546| Signal | Redirect |47|--------|----------|48| No `epic:<slug>` beads / `bead-graph.sh` errors | Graph the spec first: `/substrate:graph-spec <spec>`. No worktrees created. |49| `substrate.yaml` missing or its `gate` block absent | Abort with an explanation — do **not** probe a toolchain (root CLAUDE.md gate policy). Fix `substrate.yaml`, then re-run. |50| No tracker (`tbd`) configured | Single-writer orchestration needs a tracker. Use `/substrate:execute` sequential instead. |51| Arg empty | Ask for the epic slug or spec path. |5253## The dispatch seam (tool-agnostic)5455The **only** tool-coupled seam is subagent dispatch. Everything else (git, tbd, the gate) is portable shell.5657| | Claude Code | OpenCode |58|---|---|---|59| Portable floor | **Agent tool** — one `bead-implementer` (group-runner) call per ready window in a wave (parallel calls in one message) | **Task tool** — one task per ready window (parallel where the runtime supports it; **sequential fallback**, logged) |60| Fast-path (CC only, v1) | **Workflow tool** — deterministic per-wave pipeline, journaled resume, budget control over the same loop contract | *(n/a — NL floor only)* |6162The two CC paths are **behaviorally equivalent on the DAG**; the Workflow fast-path adds63resume/determinism, not different semantics. On OpenCode, DAG *correctness* is preserved even when64Task calls serialize; only *concurrency* is optional.6566## Workflow — the operational loop6768### Step 1. Resolve `<slug>`6970From the arg: `epic:<slug>` → `<slug>`; a bare `<slug>` → itself; a spec path71`docs/tasks/ongoing/<slug>/…` → the directory name. This is the canonical epic label; it is the join72key, not the parent link.7374### Step 2. Read the DAG (never re-derive it)7576```bash77bash docs/scripts/bead-graph.sh --epic <slug> # topological waves — the authoritative schedule78```7980Every id in one wave is safe to run in parallel; waves run in order. **Fail-fast** if there are no81`epic:<slug>` beads or the script errors — prefer the machine `waves` output over eyeballing wave82art; a parse error aborts before any worktree is created (FMEA #7). `--format mermaid` is available83for a visual sanity check.8485**Read the partition.** Each bead carries a `group:<window-N>` label (written by86`/substrate:graph-spec` per `agents-parallel-execution-doctrine.md §Grouping & windows`) — the87context-budget window it belongs to. Beads in one window are file-adjacent and run **sequentially in88one worktree**; distinct windows within a wave are file-disjoint and run **in parallel**. The89dispatch unit is the **window**, not the bead. The partition is a **deviatable prior**: you MAY90re-batch it (merge two tiny windows, split an over-full one) when runtime judgment warrants — but if91you do, **log the deviation**: mint a `run-id` (`<epic>-<YYYYMMDD-HHMM>`) and append the reason +92the planned-vs-actual windows to `.substrate/runs/<epic>/<run-id>/deviation-log` (gitignored). No93deviation → no log entry needed. Absent `group:` labels (an ungraphed or pre-partition DAG) → fall94back to one bead per window (the classic per-bead behavior).9596### Step 3. Read `substrate.yaml`9798Read `gate.{compile,test,lint}`, optional `gate.out-of-band`, `worktree-seed[]`,99`toolchain-pin.{install,env}`, and optional `execution.resource-envelope`. **Abort with an explanation if the `gate` block is missing** — do not100probe a toolchain (root CLAUDE.md policy; FMEA #2 phantom-gate mitigation depends on these keys).101102**Warn on an undeclared seed (no-silent-fallback).** If `worktree-seed` is absent or empty **and**103the repo's `.gitignore` names build/dependency paths the gate plausibly needs (`node_modules`,104`.venv`/`venv`, `target`, `dist`/`build`, generated-client/codegen dirs, `.env*`), do **not** proceed105silently: warn that a fresh worktree contains only *tracked* files, so the gate may fail spuriously,106and that seeding will then fall to manual per-run copying. Point the user at `substrate.yaml`'s107`worktree-seed[]`/`toolchain-pin` block (populate it — `/substrate:adopt` can, or edit by hand) and108**pause for confirm-to-proceed-unseeded**. This is a warning, not an abort: a repo whose gate needs109no gitignored input is free to run with an empty seed.110111**Note the resource envelope.** If `execution.resource-envelope` is present112(`{env, cores-budget, floor}`), each window's gate runs with `<env>=max(floor(cores-budget / K),113floor)` for the K windows dispatched in that wave (5c.4). Absent it, the gate runs unbounded — fine114at K=1, an oversubscription at K>1 (doctrine §Supporting → *A window's gate runs under a declared115resource envelope*).116117**Note the gate-coverage floor.** `gate.{compile,test,lint}` is the *minimum* re-gate, not118necessarily the *whole* suite the epic exercises. As you read the DAG (Step 2), collect the distinct119per-bead `Gate_i` commands: any `Gate_i` that is **not** subsumed by `gate.*` means `gate.*`120under-covers this epic — e.g. a frontend `vitest` a bead runs while `gate.test` only runs the backend121suite, or a `tsc`-only bead narrower than `gate.*`. Do **not** treat `gate.*` alone as the122composition net: the per-wave re-gate (5e) runs the **union** of `gate.*` and the per-bead gates123exercised that wave. Surface this once up front so it's visible that the integrated re-gate is124broader than `substrate.yaml`'s literal `gate` block. (If a bead's gate is a strict subset of125`gate.*`, `/substrate:graph-spec` will have tagged it `gate-scope: partial` — a reminder that its126green is partial and only the union re-gate authorizes the merge.)127128### Step 4. Setup — integration branch, run-state anchor, unattended signing129130```bash131git switch -c feat/<epic-slug> # or: git switch feat/<epic-slug> (reuse if it exists)132git config extensions.worktreeConfig true # once, idempotent: lets each worktree carry its own config133```134135**4.1 Cut/reuse `feat/<epic-slug>`** from trunk.136137**4.2 Initialise the run-state entry — before any worktree exists.** Write138`.substrate/execution-state.json`'s `<epic>` key now:139140```json141{ "<epic>": { "run-id": "<epic>-<YYYYMMDD-HHMM>", "partition": { "window-1": ["…"] },142 "re-gates": [], "outcomes": {}, "run-log": ".substrate/runs/<epic>/<run-id>/" } }143```144145Every later write (5c.1 `dispatched`, 5e `re-gates[]`, 5f `merged`/`verified`) **appends to this146entry**. Skip this and each of those is a write into a missing key, so an aborted run leaves no147ledger at all — which is exactly what happened on the 12-window sky-journal run (2026-08-04): real148merged work, a written deviation log, and no `execution-state.json` entry, because entry creation149lived only in Step 6.1 and the run never reached it.150151**4.3 Signing is scoped to the worktrees, never to the repo.** `extensions.worktreeConfig` above is152the *only* signing-related change made to the primary checkout, and it changes no signing behaviour153by itself. Do **not** flip repo-local `commit.gpgsign false`: that config is shared with the primary154checkout, so a human's mid-run commit lands silently unsigned and a killed run never reaches the155restore. Per-worktree unsign happens at creation (5c.2); orchestrator-authored merges pass156`-c commit.gpgsign=false` per invocation (5d, 6.3). Nothing needs restoring at close — see157doctrine §Supporting → *Unattended signing*.158159### Step 5. Per wave, in order160161For each wave the DAG emits, in order:162163**5a. Filter to ready windows.** Keep beads whose blockers are all closed *or merged* —164`tbd ready` / `tbd show <id>`. **Merge, not close, is the unblock signal** (doctrine §Policy-4), so a165merged-but-open Policy-4 bead still unblocks its dependents. Then **group the ready beads by their166`group:<window-N>` label** — a window is dispatchable once *all* its beads are ready. The dispatch167unit is the **window**.168169**5b. File-disjoint guard (across windows).** Pairwise-intersect each ready *window's* **Files**170(the union of its beads' Files). Any collision splits the colliding windows into **consecutive171sub-waves** — merge one, re-gate, then the next branches off the new tip. This is a second net over172graph-spec's partition + edges (doctrine §Supporting → *File-disjoint waves*; FMEA #3). Never run two173windows that touch the same file in one wave. (Within a window, beads co-edit shared files *by174construction* — that's why they share one worktree and run sequentially, not in parallel.)175176**5c. Dispatch — per ready window:**1771781. `tbd update <id> --status in_progress` for **each bead in the window** (orchestrator-only write), and stamp each bead's run-state `outcome: dispatched` in `.substrate/execution-state.json`.1792. `git worktree add <path> -b <window-branch> feat/<epic-slug>` — **one worktree per window**, off the **current integration tip**, so it already contains merged blockers. Never branch off stale trunk. Then immediately `git -C <path> config --worktree commit.gpgsign false`, so this window's commits never reach an interactive signer and the primary checkout's config stays untouched.1803. Copy every `worktree-seed[]` path from the primary checkout into the worktree, then run `toolchain-pin.install` **once for the window** (seeding cost is O(K windows), not O(N beads)). Seed **before** dispatch or the gate fails on a phantom (doctrine §Supporting → *Seed …*; FMEA #2). `toolchain-pin.install` must stay idempotent. Seed **immutable inputs only** — never a handle to shared mutable state (a local dev-deployment dir, a live state cache); regenerate those offline in the worktree, or route the bead to the primary checkout.1814. Compute this window's resource share from `execution.resource-envelope` — `max(floor(cores-budget / K), floor)` for the K windows in this wave — and inject it as `<env>=<share>` in front of the window's gate commands, alongside `toolchain-pin.env`.1825. Dispatch **one group-runner** (`bead-implementer`; Agent tool / Task tool / Workflow stage) with, inlined:183 - the window id and its **N sequenced bead tuples** — each `{Goal_i, Files_i, Gate_i, spec-ref_i}`, the gate fully **env-resolved** (`toolchain-pin.env` prefix + `gate.*` literals + any bead override), so it resolves in a worktree with no shell version-manager;184 - **the window's doctrine digests — push, don't point.** Union the `doctrine:<id>` labels across the window's beads (stamped by `/substrate:graph-spec` Step 4.55 from write-scope ∩ the manifest's `paths:`), **dedupe by `<id>`**, and inline the output of `bash docs/scripts/doctrine-digest.sh <id>` — that doctrine's Binding Rules block — **once for the window**, not once per tuple, each block labelled with its id and the beads it binds. This is the push tier: cheap, and it provably arrived, where a pointer verifies nothing (`docs/doctrine/agents-doctrine.md` §2.1 — the reasoning lives there; don't restate it). **Escalate to the full doctrine body** when a *single* bead's write-scope majority falls inside one doctrine's `paths:` — that bead sits squarely in the doctrine, so inline that whole file for its tuple in place of the digest. Bound it:185 - **At most one escalation per window**, the doctrine covering the most write-scope. A full body costs ~6–10× its digest (measure it: `wc -l <doctrine-path>` vs `bash docs/scripts/doctrine-digest.sh <id> | wc -l`), and a window whose beads' majorities land in *different* doctrines isn't squarely inside either — that's a mis-partition signal, not a licence for two full bodies.186 - **Never truncate a digest, and never drop one to fit.** Half-delivered binding rules are worse than a pointer: the runner acts on a subset of the MUSTs believing it has them all. If the deduped union is outsized (≳200 lines — roughly 4 digests at the 35–65 each this repo measures), drop the *escalation* to digest-only first; if digests alone are still outsized, the window is bound too widely — narrate it and consider re-batching per Step 2's deviatable prior. The prompt is the symptom; the partition is the fix.187 - **Degrade silently.** No `doctrine:` labels on the window's beads, or no `docs/scripts/doctrine-digest.sh` in the repo → inline nothing, say nothing, compose the dispatch exactly as it would be composed without this bullet. Not a warning, not an abort: most repos have never adopted `paths:`, and a warning would fire on every window of every one of them. A label whose id the script rejects (non-zero exit — a stale label surviving a manifest edit) is skipped, mentioned **once** in the wave narration, and never fatal.188 - the relevant `CLAUDE.md` — **repo context, no longer the doctrine channel.** It carries orientation and doctrine *pointers*; when digests are inlined above they are the binding doctrine for this window and already in the runner's context, so the pointers are the fallback for what they don't cover. In a repo that stamps no `doctrine:` labels, the pointer is all there is and the runner chases it as before;189 - the standing rule verbatim — *"no tbd, no git push — implement each bead in sequence, run each bead's gate, report a per-bead pass/fail ledger + a diff summary."*190 - for any bead carrying an out-of-band gate, say so, and ask for that bead's out-of-band checklist + the single swappable seam.191192**5d. Collect results (read the per-bead ledger).** The group-runner returns a per-bead ledger193(`pass | fail | unstarted`) covering every bead in the window:194195- **All-pass** → merge `<window-branch>` → `feat/<epic-slug>` with `git -c commit.gpgsign=false merge …` (per-invocation, never a config flip); `git worktree remove <path>` **and** `git branch -D <window-branch>` (hygiene — an unchanged worktree auto-cleans; the branch does not). Newly-unblocked dependents dispatch off the updated tip.196- **Stopped mid-window at bead *i*** → the runner leaves clean per-bead commits for the `pass` prefix (beads 1..i-1). Merge that green prefix if the ledger shows those beads gated green; keep bead *i* and the `unstarted` remainder **open**, `tbd update <id> --notes "<failure>"`. Block only the transitive dependents of the open beads — **sibling windows continue** (partial progress is a core DAG win; FMEA — mid-window failure). Fix or escalate the failed bead.197198**5e. Re-gate the integrated tip — the union gate.** After the wave's merges, the *orchestrator*199runs, once on `feat/<epic-slug>`, the **union of `gate.{compile,test,lint}` and every distinct200per-bead `Gate_i` exercised by the beads merged this wave** (deduped). This union — never `gate.*`201alone — is the **sole merge-authorizing signal** for the wave; the per-bead gates a group-runner ran202are *fast pre-checks* that fail early inside a window, not the composition net. The union is what203makes the re-gate sound: a per-bead gate can be **narrower than `gate.*`** (a `tsc`-only frontend204bead), *and* `gate.*` can be **narrower than the suites the wave touched** (a `gate.test` that runs205only the backend suite while a frontend `vitest` ran per-bead). Re-gate with `gate.*` alone and a206whole suite the wave exercised never gets composed-checked — so a green-reported wave can sit on a207red integrated tip (doctrine §Supporting → *Re-run the gate on the integrated branch*; FMEA #4). Two208independently-green branches can still fail composed. **Record the wave's re-gate into209`.substrate/execution-state.json` as it runs** — and if the `<epic>` entry is *missing* when you go210to append, you skipped Step 4.2: stop and fix that rather than creating the entry late, because211everything the run has already recorded went nowhere — — `{wave, commands: [...], result, tip-sha}` (Step 6;212the file is written **incrementally per wave**, not only at close, so an aborted run still leaves the213evidence). **Red = composition failure: halt the wave transition, attach notes, fix before any214dependent dispatches.** A wave with no recorded re-gate entry is a protocol violation.215216**5f. Record outcome — defer close (two-stage gate, non-destructive).** For each green, merged bead,217**advance its run-state `outcome`, do not `tbd close`**:218219- On merge → `outcome: merged`; after the wave's green union re-gate (5e) → `outcome: verified`.220 The bead **stays `open` in the tracker** — `verified` is the live done-signal a watcher renders,221 and the `tbd close` is deferred to the terminal batch (Step 6.2). Merge (not close) already222 unblocked its dependents, so deferring the close changes no scheduling — and no bead flips223 `closed` mid-run, so a spurious close can't drop it out of the open view.224- **Out-of-band gate applies** (hardware / paid service / manual) → `outcome: oob-pending` +225 `tbd update <id> --notes "merged; awaiting <out-of-band> gate"`. It stays open; close waits for226 the *full* gate a human runs later (doctrine §Policy-4).227228**5f-pr. Push the integration tip (only under `--pr`).** Immediately after the wave's union re-gate229is green (5e), `git push origin feat/<epic-slug>`. This is what makes the PR update **live, in230wave-sized bursts** — the per-bead commits merged this wave become visible on the PR the moment the231integrated tip is authorized. On the first wave, ensure the PR exists (`gh pr view feat/<epic-slug>232|| gh pr create -f -H feat/<epic-slug>`). A red re-gate pushes nothing (halt per 5e).233234**5g. Pause for approval** with a wave summary (beads merged / left-open / red, re-gate result,235next wave preview) — **unless `--auto`**. `n`/`pause` stops cleanly so the user can inspect. Under236`--pr --auto` (fully unattended) there is no pause; the PR is the inspection surface.237238**5h. The terminal doctrine-reconciliation node (final wave).** The epic's last wave is always the239solo `kind: doctrine-reconciliation` node graph-spec emitted (`blocked-by` every other bead, so it240runs alone against the fully integrated tip). Dispatch it like any window, with two things to know:241242- Its group-runner **edits `docs/doctrine/**` in its worktree** to codify the ratify-only doctrine243 change the epic earned. That is an **ordinary working-tree change**, not a tracker write — the244 single-writer invariant is untouched (the runner still runs **no `tbd`, no `git push`**; you merge245 and record as always). Doctrine files land inside this epic's diff, co-revertable with the feature.246- **Ratify-only is enforced by the re-gate, not a bespoke lint.** Its gate is the full union gate on247 the integrated tip (5e); since the mutation may only codify what the code already did, a green248 re-gate *is* the proof it was ratify-only. **A red re-gate means the doctrine edit introduced a249 rule the shipped code violates** — that is out of scope for this node: have the runner revert the250 doctrine edit (or do it yourself before merge) and note it as follow-up for251 `/substrate:synthesize-session`. There is **no** amendment queue to fall back on — the node either252 applies a green ratify-only change or reverts to a no-op.253254### Step 6. Epic close2552561. **Finalize `.substrate/execution-state.json`** — the durable run-state. This file is written **incrementally**, not once at the end: stamp the `run-id` + chosen `partition` at run start, append a `re-gates[]` entry after every wave's union re-gate (5e), and record each bead's `outcome` as it merges — so a crash or an aborted run still leaves a partial, truthful ledger (and the re-gate history that makes a composition failure diagnosable after the fact). At epic close, before the squash, finalize it: under the `<epic>` key record the `run-id`, the **chosen `partition`** (window → bead-ids), any `deviations` from graph-spec's suggestion (with reasons, mirroring the run-log), the per-wave `re-gates` (`[{wave, commands, result, tip-sha}]` — the union-gate proof), the per-bead `outcomes` (`status: dispatched|merged|verified|oob-pending|fail|closed` + merged `commit` sha or null — the non-destructive lifecycle a watcher renders), and the `run-log` pointer (`.substrate/runs/<epic>/<run-id>/`). Schema in `agents-parallel-execution-doctrine.md §Grouping & windows`. This file stays **tracked** (only `.substrate/runs/` is gitignored) and is committed alongside the squash.2572. **Terminal batch close, then one `tbd sync`** — orchestrator-only, at epic close (or an explicitly agreed checkpoint). Close every `verified` bead in a **single bulk call** (`tbd close <id1> <id2> … --reason "gate green"`, stamping each `outcome: closed`) — this is the *only* `tbd close` in the run — then run the one `tbd sync`. `auto_sync` stays off; never sync mid-flight from a worktree (doctrine §Policy-3 → *Batch sync*). Beads left `oob-pending` stay open and close later, as their out-of-band gates pass.2583. **Land the epic — two modes:**259 - **Default (local landing):** land `feat/<epic-slug>` on trunk as **one signed commit** (including `.substrate/execution-state.json`): `git switch <trunk>` → `git -c commit.gpgsign=false merge --squash feat/<epic-slug>` → `git commit -S -m "..."`. Squash keeps the unsigned bead commits out of trunk history. This is the run's single interactive-signing moment. Then reap: `git branch -D feat/<epic-slug>` (its content is in the squash; its history is in `execution-state.json`).260 - **`--pr` mode (PR landing):** do **NOT** touch trunk. Commit `.substrate/execution-state.json` onto `feat/<epic-slug>`, `git push origin feat/<epic-slug>` a final time, and ensure the PR is open (`gh pr view … || gh pr create -f`). The squash-to-trunk is deferred to GitHub's *Squash and merge* on the PR (the single squasher). The unsigned bead commits are legitimate on the PR branch; GitHub re-authors them into one commit at merge.2614. **Verify signing posture** — `git config --local --get commit.gpgsign` must still be `true`. This is a *check*, not a restore: signing was never disabled repo-wide, only per worktree, and those overrides died with their worktrees. If the check ever fails, something flipped the shared config and that is the bug (doctrine §Supporting → *Unattended signing*; FMEA #1).262263## CC Workflow fast-path (v1, optional at runtime)264265On Claude Code, the per-wave dispatch MAY run as a **Workflow-tool pipeline**: each wave is a stage,266each ready window a `bead-implementer` (group-runner) within it, with journaled resume and budget control over267the *same* loop contract (steps 5a–5f). It is a performance/determinism layer, **not** different268semantics — the portable NL floor (Agent tool, one call per window) is the default and the **only** path269on OpenCode. Do not hardwire Workflow as the sole mechanism.270271## Constraints272273- MUST read the DAG from `bead-graph.sh --epic <slug>`; MUST NOT re-derive it. Fail-fast on a parse error before any worktree exists.274- MUST abort with an explanation if `substrate.yaml`'s `gate` block is missing — never probe a toolchain.275- MUST warn (not abort) before dispatch when `worktree-seed` is undeclared but `.gitignore` names build/dep paths the gate plausibly needs, and pause for confirm — never silently dispatch into worktrees that will fail the gate on an unseeded input (no-silent-fallback).276- MUST initialise the `<epic>` run-state entry before the first dispatch (Step 4.2). A run that appends re-gates into a missing key leaves no ledger at all.277- MUST honor the **single-writer** invariant: only the orchestrator runs `tbd update`/`close`/`sync` or `git push`. Subagents receive Goal/Files/Gate inlined and return a result.278- MUST **push** the window's deduped `doctrine:<id>` digests into the dispatch prompt when its beads carry those labels (5c.5) — once per window, escalating to **at most one** full doctrine body — and MUST NOT truncate a digest to fit. MUST degrade to today's composition **silently** when there are no labels or no `doctrine-digest.sh`: no digest block, no warning, no abort.279- MUST branch each bead worktree off the **current integration tip**, merge-on-green, and **re-gate the integrated tip each wave with the union of `gate.*` and every per-bead gate exercised that wave** — the union re-gate (never `gate.*` alone) is the sole merge-authorizing signal, and each wave's `{wave, commands, result, tip-sha}` MUST be recorded incrementally in `.substrate/execution-state.json`. A wave with no recorded re-gate entry is a protocol violation.280- MUST inject each window's `execution.resource-envelope` share into its gate when the repo declares one.281- MUST enforce **file-disjoint** waves (pairwise-Files guard) beyond graph-spec's edges.282- MUST apply the **two-stage gate non-destructively**: headless-green → merge + unblock dependents + advance run-state `outcome` (`merged` → `verified`); **defer every `tbd close` to the single terminal batch** (Step 6.2) so no bead flips `closed` mid-run; out-of-band proof → `oob-pending`, left open + noted until a human runs it. `verified` (not a mid-run close) is the live done-signal a watcher renders.283- MUST scope unattended signing to the worktrees (`extensions.worktreeConfig` + per-worktree `commit.gpgsign false`, `-c commit.gpgsign=false` for orchestrator-authored merges) and MUST NOT flip the shared repo-local config. Land trunk as one signed **squash** commit; at close, *verify* the primary is still signing rather than restoring it.284- MUST reap debris: window branches on merge, `feat/<epic-slug>` after the squash lands.285- MUST pause between waves unless `--auto`. Never silently fan out beyond the DAG.286- MUST, under `--pr`, push `feat/<epic-slug>` after each green wave re-gate and **suppress the Step 6.3 trunk-squash** — the two are mutually exclusive. The PR (squash-merged on GitHub) is the sole landing; the orchestrator MUST NOT create a trunk commit in `--pr` mode. Signing stays disabled during the run and is restored unconditionally at close exactly as in the default mode.287- MUST stay **tool-agnostic** — Agent↔Task is the only seam. The CC Workflow fast-path is additive, not required.288- MUST keep this body under ~500 lines — link to the doctrine for rationale, don't restate it.289- SHOULD narrate each wave (dispatched beads, gate results, merges) so the user sees liveness on long epics.