Purpose
Stage: cross-cutting engine (the autonomous span). The human owns Ideate + Spec + Plan — all the thinking. Once they sign the Spec and author the plan, something has to actually build it: run every slice through Implement → Verify → Review → Ship with no human babysitting, in parallel where the dependency graph allows, and stop at a state a human can review async. That something is this skill. It exists because the alternative — the controlling agent hand-running slices one at a time, checking in between each — throws away the parallelism the slice DAG was designed for and reintroduces the human halt deliberately removed. The orchestrator is the only skill that reads the whole board and moves slices across it.
When to use / when to skip
Use when: the Spec gate is signed, preflight-readiness is green, and STATE.md holds a feature
carrying slice rows whose Blocked by column forms a DAG — and you want the build run autonomously to
open PRs. That feature reads feature: plan on a first run, which is where plan-breakdown leaves
it, or feature: building on a resumed one, which is where you left it. Being invoked is the
human's Plan sign-off: the rows are born at gate you precisely because a person has to read the plan
first, and a person starting the run is that reading. Flipping plan → building is this skill's own
first write — Process step 3. This is the default executor; sequential execution is just the degenerate
case (a wave of one).
Skip when: the feature has no slice rows yet — it sits at feature: spec, or at feature: plan
with nothing under it, and the human still owns Spec or Plan; you are doing a single one-off edit with
no DAG; or preflight-readiness is red/amber (fix the environment first — the wave must not start).
Slice rows, not the feature token, are what say Plan is finished. A board reading feature: plan
with rows under it is the normal starting board, not a skip: nothing writes building before you do,
so refusing that board refuses every board plan-breakdown has ever produced.
Escape hatch (depth: lite): a single ready slice with no siblings still runs the full
Implement → Verify → (aggregate) Review loop and the barriers — do not "just do it inline." For a
wave of one the aggregate review is simply that one slice's diff, so it costs the same as the old
per-slice review; the barriers, the worktree, and the three gates are the point even here.
Inputs
Refuse to run (CRISPY refuse-to-run) unless ALL are present:
STATE.md(repo root, created byproject-setup) — the two-level board. TheBlocked bycolumn IS the slice DAG. There must be a feature atfeature: planwith at least one slice row inimpl— the boardplan-breakdownhands over — or atfeature: building, which is a run you already started and are resuming. Absent, no feature carrying slice rows, or a feature still atfeature: spec→ refuse. Theplan → buildingflip is yours and happens in step 3; do not require the board to already saybuilding, because nothing else writes that token.preflight-readinessverdict = GREEN — everyenvironment.mdrow provisioned. Red or un-attested amber → refuse to start the wave.plan.md+plan/(docs/features/<slug>/) — the slice table with each slice's declaredRegression surface,Design ref,Steps, andFiles (owned)ownership, plus oneplan/<slice-id>.mdper slice holding its concrete steps and exact tests. Missing file-ownership on a slice that shares a wave → refuse (the disjoint-file guard cannot run blind).acceptance.md(status: signed) — the frozen behavioral oracle. The orchestrator FREEZESacceptance.md+ the RED tests + each slice'sRegression surfacefor that slice's retry loop; it never edits them. Editing any of the three is a loosening: one rule, Changing what judges the work, governs this and every other prohibition below.
One more input is required per slice rather than per run, so it gates a slice instead of the run:
the design contract each slice's Design ref names, at status: signed, for every slice whose
Design ref is not —. It is read at dispatch, before that slice's implementer runs; absent or
status: draft halts that slice, with the halt reason naming the contract by path, while the rest
of the wave dispatches normally. Fully specified in Design-ref gate at dispatch below.
Bulk artifacts move as files, never pasted into a dispatch prompt (subagent-driven- development §File Handoffs): a slice dispatch carries the slice brief path + its frozen contract paths, not the session history.
Process
Resume cold. Read
STATE.mdanddocs/progress.mdfirst. The board says which slices aredone/ship— never re-dispatch one (subagent-driven-development §Durable Progress: re-dispatching completed work is the single most expensive failure). The run record says what the last attempt actually executed and what came back, and a heading in it with nothing under it is a slice that was dispatched and died before returning. Trust those two plusgit logover recollection after compaction: the board answers what state a slice is in, the record answers what was run, and neither answers the other's question.Build the DAG. Parse every slice's
Blocked byinto edges; topologically sort into waves (each wave = one topological level). Verify no cycles — a cycle blocks the run; surface it and stop (the human must reorder dependencies).Open the run — flip the feature to
building. The board still readsfeature: plan: that is whereplan-breakdownleaves it, and this flip is yours and nobody else's (references/write-ownership.md, Who writes what:a feature block's feature: state,orchestrator, flip status). Do it once the three things that warrant it are true and not before — the feature carries slice rows whoseBlocked byDAG steps 1–2 just read and found acyclic, thepreflight-readinessverdict is green, and the signedacceptance.mdplus theplan.mdslices exist. Those three are the Run-start gate, which is the one gate in the lifecycle this skill owns; flipping the token is what opening it looks like on the board, and a run whose board never saysbuildingis a run no reader can tell started. In the same write, move each slice row'sGatefromyoutoagent: the rows are bornyoubecause a person signs the plan, and the run is what takes them over. Flip the state token only. The feature block, itsorigin:line, its rows, their titles, and theirDesign refcells belong toplan-breakdownand are not yours to rewrite — the write table grants youflip statusthere, which is one token and no more. A board already atfeature: buildingis a resumed run: nothing to flip, and a row already pastyoukeeps the gate it has.Select the ready wave, then order it. A slice is ready when every blocker is
done. Apply the disjoint-file guard (below) to the ready set, then put what survives into the order Dispatch order (below) defines — deepest downstream chain first, transitive dependent count as the second key,plan.mdorder last. Selection decides which slices run; the order decides which one starts first, and that is what sets the run's wall clock the moment the guard splits the wave into sub-waves.Open each slice's entry, provision isolation, then run the design-ref gate. The moment a slice enters the wave — before its brief is assembled, before any gate can stop it — append its stub to
docs/progress.md: the entry heading, and nothing under it (see The run record). Then each ready slice gets its own clean worktree (theworktreemechanism this skill owns). Platform-adaptive (below). While assembling the brief, read the slice row'sDesign ref— it travels with the slice from here on. If it is not—, open thedesign-contract.mdit names and readstatus:before dispatching anything for that slice (see Design-ref gate at dispatch). Absent orstatus: draft→ that slice ishaltedhere, its halt reason naming the unsigned contract by path; no implementer runs, so no partially built interface exists to reach Verify or Review.Run Implement + Verify per slice for every ready slice — in parallel (one dispatch call per slice, all in one response = concurrent execution):
incremental-implementation(appliestest-driven-development) →quality-verification(Verify, fresh code-cold). Verify stays per-slice — behavioral acceptance is a property of the individual slice, not the wave.Carry the design ref into both briefs. Copy
Design reffrom the slice row into the implementer's brief and the verifier's brief. Dispatch is the only channel that reaches both: the implementer must open the prototype before it builds, and the verifier is code-cold — it may not readplan.md, so anything left only in the plan never arrives. A—is delivered explicitly as—, not omitted: it tells the verifier this slice builds no UI, rather than leaving it to infer that from work it did not do. Where the ref names a path and the repo has adocs/design.md, carry that path in both briefs too: a contract axis markedinherits: docs/design.mdis graded against that file, so a verifier handed only the contract is handed half its oracle.Verify barrier. Wait for every ready slice to reach
verifygreen or a terminal state (a slice that halts at Verify never enters the review). This barrier is what lets the next step review the wave as one changeset instead of N.Aggregate Review over the whole wave. Run the four floor axes (
code-review+code-simplification+security-and-hardening+performance-optimization) — plus any reviewer the trigger table below adds — as fresh code-cold subagents (one axis each, in parallel), once over the union of the verify-green slices' diffs — 4+k subagents per wave, not 4 × N, where k is however many reviewers the trigger table added (usually zero). The win is in the per-wave denominator, not in the roster: never budget off the literal 4 and drop a triggered reviewer to hit it — that is a loosening. Attribute every finding to its owning slice by file: the disjoint-file guard guarantees each file belongs to exactly one slice, so attribution is unambiguous. A finding routes only its owning slice back toincremental-implementation, which climbs The escalation ladder against it; after that slice re-passes Verify, re-review only its diff, never the whole wave again.Evaluator floors + DRAFT PR per slice — still per-slice (each slice owns its plan steps and regression surface). A slice whose attributed review findings are clear and whose floors are met opens its own DRAFT PR. A slice that fails instead climbs The escalation ladder — five rungs, each a different tactic, and it halts only when they are spent. Budget: 2 attempts per gate, 3 implement→verify→review cycles per slice, spent across the ladder rather than on repeating one approach.
TERMINAL barrier. Wait for EVERY slice in the wave to reach a TERMINAL state (
done | halted | blocked) — neversuccess. Write every transition + gate flip toSTATE.mdas it happens. Complete each slice's entry indocs/progress.mdfrom what the slice returned — the commands, their real output, the files it changed, and what it did not run and why (see The run record). A slice that returned nothing keeps the stub it was given and is completed with that fact. In the same pass, carry over any lessons entry the slice handed back, into the same checkout (see The lessons a slice hands back). Then advance to the next wave.Integration gate. After a connected DAG component's slices are all green, run the merged-union suite once in an integration worktree before presenting. Union-fail → the component's PRs go DRAFT + a blocker is recorded.
Terminate on exactly one predicate (see Verification). Append the inverted risk report; leave risk-banded OPEN PRs for the human.
Wave executor & the TERMINAL barrier
The DAG → topological-wave structure is the whole point: independent branches drain in
parallel, dependent branches serialize, and the barrier between waves is what makes the run
resumable from STATE.md alone. The barrier waits for TERMINAL, not SUCCESS
(safety rail 5, references/safety-rails.md): a halted or blocked slice still satisfies the barrier — the run
does not stall waiting for a slice that will never pass. Its dependents transitively flip to
blocked; every other independent branch keeps draining.
The run record
Appends to docs/progress.md — one entry per slice, per dispatch. An entry carries the commands that
were run in the form they were run, their real output, the files that changed, and what was not run
and why. It exists because the alternative account of a run is the run's own summary, and a summary is
written by the party with the most reason to round up.
Where the write lands, and this is the part that has gone wrong before. The entry is appended in the
checkout you hold — the branch the run was started from — never inside a slice's worktree. A worktree
is a separate branch that may never be merged: a write there succeeds, reports success, and reaches no
reader on the main line. Slices run in worktrees; the record does not. That is why the orchestrator holds
the pen for every slice in an orchestrated run rather than letting each slice write its own entry, and it
is the whole reason adapter B exists for the hand-run case (incremental-implementation, which has no
worktree to be stranded in).
Two writes per slice, at two moments:
- The stub, at dispatch. The moment a slice enters the wave, append its heading —
## <date> — <SLICE-ID> — <title>— with nothing under it. It carries the slice id and nothing else: no state, no gate, no owner. A stub is not a status; it is the fact that this slice started. - The completion, at the TERMINAL barrier. Fill the fields under that heading from what the slice returned.
Writing only at the barrier cannot record a slice that died on the way there, and a slice that dies has to be distinguishable from one that was never dispatched. The stub is what makes the difference visible: a heading with nothing under it started and did not finish; a slice never dispatched has no heading at all. It also survives you — a run that ends between dispatch and barrier leaves the stubs behind, which is the case a barrier-only write loses entirely.
Completing a stub adds lines under it. It never rewrites the heading, and never touches an entry that is already complete. A retried slice is dispatched again, so it gets its own stub and its own entry — the same failure twice is two entries, and the second never replaces the first. An attempt to edit, re-order, re-date, or remove an existing entry is a STOP: the work ends there and the violation is reported, naming the entry and what would have changed. Refusing quietly is not enough, because a silent refusal reads as a silent success.
Verify's result belongs to the slice's entry, not a second one. quality-verification hands back its
commands and their output with qa.md; those go into the fields under that slice's heading. One slice,
one entry per dispatch — a second entry for the same attempt would let a reader count one slice twice.
Two lines nothing checks, and they are the ones that matter. Never write that a command was run when
it was not — put it under "Not run" with the reason. And withhold any credential appearing in output,
saying that you withheld it rather than dropping the line. No hook, no validator, and no CI enforces
either; the entry shape in docs/progress.md states them where the writer can see them, and that is the
whole mechanism.
The record answers what ran. It never answers who acts next. No entry carries a stage, a state, or an
owner — STATE.md is the board and this is the evidence. Two files answering one question is one answer
too many, and the one that goes stale is the one nobody is driving.
The lessons a slice hands back
One other record reaches the main line through you, and for the same reason as the one above.
Appends to docs/lessons.md — one entry per lesson a slice hands back, at the TERMINAL barrier,
into the checkout you hold and at the moment you complete that slice's run-record entry. Whoever
root-causes a defect inside a worktree, and whoever closes a Critical review finding there, both owe the
lessons record an entry and neither can land one where a reader will find it. They hand the finished entry
back and say it is still owed. You are its courier, not its author — append only, and every entry
arrives complete. A handed-back entry you do not carry over is a lesson this repository never learned,
and nothing downstream notices it went missing.
The two records answer different questions and are not interchangeable. The run record says what this
slice executed; the lessons record says what a defect turned out to be and names the guard that would
catch it coming back, for whoever meets the same mistake in a repository this run will never touch. That
is why it outlives the slice, and why dropping one costs more than it looks like it costs. A repository
that keeps no docs/lessons.md was never set up for one: there is nothing to carry, and the slice saying
so is the whole of it.
Verify barrier & wave-aggregate review
Verify and Review sit at different granularities on purpose. Verify is a property of an individual slice — does this slice's behavior satisfy its signed acceptance scenarios? — so it stays per-slice and runs inside each slice's worktree. Review asks cross-cutting questions (correctness, simplicity, security, performance) that a reviewer answers better seeing the wave as one changeset, and running it once per slice was the run's dominant token cost (4 code-cold subagents × N slices). So the loop inserts a verify barrier: once every ready slice is verify-green (or terminal), the review axes run once over the union of those slices' diffs — 4+k per wave, not 4 × N, where k is the reviewers the trigger table below adds. A wave that fires two rows runs six passes once, rather than six per slice; the saving is the denominator, and it survives intact however many reviewers the roster holds.
Attribution stays clean because the disjoint-file guard already holds: every file in the wave
belongs to exactly one slice, so every review finding (which cites a file) maps to exactly one
owning slice. A finding routes only its owning slice back to incremental-implementation; the
other slices, whose files it never touched, are unaffected and keep their clean review. After the
flagged slice re-passes Verify, re-review only its diff — re-running the whole-wave review on
every single-slice fix would hand the token cost right back. The wave advances when every slice's
attributed findings are clear (or the slice is terminal).
This preserves every safety property the per-slice fan-out had: four independent code-cold axes, no role-play, and the security circuit-breaker — a CRITICAL/HIGH in the wave-scoped security pass hard-halts its owning slice (never a PR), while a repo-wide committed secret still freezes the next barrier for the whole run.
The four axes are a floor, not a list. Every wave gets all four. On top of that, check the wave's changed paths and diff content against the table below; every row that matches adds a code-cold reviewer to the fan-out. Nothing in the table can remove one, and a wave matching no row still gets all four.
| The fact that fires the row | Adds |
|---|---|
| The diff changes a symbol, route, or schema that a file outside the wave imports or calls | api-design — is this addition rather than modification, and does any existing caller break? |
| The diff deletes or renames a file, exported symbol, or persisted field, and something outside the diff still names the old one | deprecation-and-migration — is there a replacement and a migration path, or are callers stranded? |
| The diff changes CI, build, or deploy configuration (workflow files, pipeline or container config) | ci-cd — the pipeline is itself part of what judges the work; a step removed here is a loosening (see Changing what judges the work) |
| The diff adds an error branch, retry, background job, or outbound call that emits no log, metric, or trace | observability-and-instrumentation — a production failure on that path leaves no evidence |
Each row's first column is a fact about the diff you can check by reading it: the caller exists or it does not; the old name is still referenced or it is not; the new branch logs or it does not. That is the point — a row you cannot check is a row that fires on impression, and a router built out of impressions is not a router. If the fact cannot be established, the row does not fire and the floor four still run.
UI is deliberately absent from the table. Fidelity to the signed design contract and the
accessibility pass belong to quality-verification, which grades a running interface; Review
reads a diff and cannot see one.
The drop half is deferred, and this is the evidence it waits on. The symmetric rule — removing an
axis from a wave that has nothing for it — needs a per-axis finding rate measured over real runs, and
no such journal exists yet. docs/progress.md is not it and does not become it: it records what each
slice executed, not which axis's finding changed which diff. Until a run journal records, wave by wave,
which axis produced findings
that changed a diff, dropping an axis is a loosening with no measurement behind it and is refused on
exactly those grounds (Changing what judges the work). The deferral ends when that journal holds
enough waves to compute the rate — not when the fan-out starts to feel expensive.
Disjoint-file guard
Same-wave slices parallelize only if their declared file ownership is disjoint — never two write subagents on one file (dispatching-parallel-agents §Shared state; safety rail 5). On overlap: serialize them into sub-waves (or merge them into one slice). This is consistent with worktree-level parallelism — same-level slices are independent by construction, so serializing an overlap is not a cohesion violation, it is the guard doing its job.
Dispatch order
Among the ready slices, dispatch in this order:
- Longest downstream chain first. For each ready slice, measure the depth of the longest
chain hanging off it — the number of steps in the longest path of slices that cannot start until
it is
done. Greatest depth starts first. That depth is the slice's critical path. - Transitive dependent count as the second key. Equal depth → the slice whose whole transitive dependent set is larger. Among slices that free chains of the same length, the one that frees more work is the better first move.
plan.mdorder as the last tie-break. Equal on both keys → whichever slice appears first inplan.md.
Depth, not volume — they come apart on any fan-out-versus-chain graph. Say C is blocked by A,
D by C, E by D, F by E — one chain hanging off A. A's transitive dependent set is
{C, D, E, F}: count 4, depth 4. Now say G, H, I, J, K are each blocked by B and nothing
else — a fan of leaves. B's set is {G, H, I, J, K}: count 5, depth 1. Ordering by count starts B
and parks the four-deep chain behind leaves that could have run at any point; at unit cost the run
takes 6 steps where 5 was available. Volume tells you how much work a slice unblocks; only depth
tells you how much of the run is waiting in line behind it, and the wall clock is made of the line,
not the pile.
Where the rule bites: the sub-waves the disjoint-file guard creates. When every ready slice dispatches at once, order is invisible — they all start together. The moment two ready slices declare an overlapping file, the guard serializes them into sub-waves, and something has to choose which sub-wave runs first. Unstated, that choice is arbitrary, and an arbitrary choice can park the slice ten others are waiting on behind a leaf slice that unblocks nothing.
Why chain length is the first key. A run's wall clock is set by its longest dependency chain, so starting the slice at the head of that chain is the one scheduling choice that actually shortens the run — every step of the chain has to happen in sequence no matter what else is going on, so any delay at its head moves the finish line by the same amount. Some ordering rule has to exist here regardless; making it the critical-path rule costs nothing and buys the only speed available at this layer.
This outranks the planner's fail-fast ordering, and that is a real trade. plan.md line order is
not arbitrary: the planner deliberately puts high-risk work early so a bad assumption fails while it
is still cheap to change. Demoting that to the last tie-break gives some of that up, so it is worth
saying what is actually lost. Not much, as it happens — every ready slice in the wave runs before the
barrier releases, so a risky slice is never skipped, only started later, and it still fails inside
the same wave. What the depth key protects against is different in kind: an arbitrary first pick can
park the run's entire critical path behind a leaf slice, and that cost lands on every wave after it,
not just this one. Risk ordering keeps its effect at the tie-break, where it costs nothing.
Determinism. All three keys are read off durable state: the chain depths and the dependent counts
are both computed from the Blocked by column, the last tie-break from plan.md line order. No
clock, no randomness, no "whichever brief finished assembling first". The same plan and the same
git state produce the same dispatch order, so a resumed run dispatches identically to the first
attempt — which is what makes a run reproducible, and a failure inside it reproducible with it.
Speed at this layer comes from ordering and parallelism. It never comes from running fewer checks — see Changing what judges the work.
Design-ref gate at dispatch
A slice whose Design ref names a contract builds UI, and that contract is the thing Verify grades
the built UI's fidelity against. So the signature has to exist before the UI does. While assembling
each brief, open the design-contract.md the ref names and read its status:; a slice whose contract
is absent or status: draft is halted right there, and the halt reason names the unsigned contract
by path — the human needs to know which signature is missing, not merely which slice failed.
Catching this at Verify instead is too late by construction. Verify runs on a built interface, so
by the time a design gate could refuse, an agent has already built a surface against a contract no
human signed, and the rework is the entire slice. Halting at dispatch is what keeps a partially built
interface from ever reaching Review. quality-verification keeps its own refusal on an unsigned or
absent contract, but that is the second line of defence for a slice that reached Verify some other
way — it is not this gate, and it cannot substitute for it.
The gate is per-slice: an unsigned contract halts only the slices referencing it (their dependents
flip to blocked transitively, as with any halt), and every other slice in the wave dispatches
normally. A Design ref of — has nothing to check — the planner already recorded that this slice
builds no UI — so it passes the gate untouched and is carried into both briefs verbatim as —.
Platform adaptivity
The wave model is substrate-agnostic; only the dispatch primitive changes:
- Claude Code → the Workflow feature (
pipeline/parallel+isolation: 'worktree'). - Codex → parallel subagents (each in its own worktree). Pick the substrate at run start; the DAG, barrier, gates, and guard are identical either way.
The three agent-internal gates (none human)
Per slice, AND-combined — SHIP requires all three plus the circuit-breaker floors:
quality-verification/ Verify — behavioral acceptance tests + the design gate.- Review fan-out (wave-scoped) —
code-review+code-simplification+security-and-hardening+performance-optimizationas the floor, plus whatever the trigger table adds, each a fresh code-cold subagent on an independent axis (maker≠checker; personas DISSOLVE into skills — no role-play). Runs once over the whole wave's combined diff, not per slice; each finding is attributed to its owning slice by file. A slice passes this gate only when its own attributed findings are clear. - Evaluator floors — correctness≥8, testing_strategy≥7, plan_adherence≥8,
regression_surface≥9.
SHIP = qa_green ∧ review_clean ∧ floors_met ∧ preflight_green ∧ tests_green ∧ build_clean ∧ diff ≤400 LOC. Circuit-breakers override any averaging: security CRITICAL / secret-in-diff = hard STOP; regression_surface < 9 = fail; qa-loop exhaustion ⇒halted.
Silent-false-green defenses (the core danger — the agent grades its own work)
With no mid-run human gate, bounded retries pressure the agent to flip the gate (weaken a test, reinterpret acceptance) rather than fix the code (Goodhart; AP1–AP2). Defeated by mechanical invariants, not a human halt:
- Frozen artifacts under retry, and the reward-hack tripwire — safety rail 4
(
references/safety-rails.md), coveringacceptance.md, the RED tests, the declaredRegression surface, and the read-onlydocs/design.md. What the run adds: each of these is also an instance of Changing what judges the work — a loosening, so it needs a measurement and a human, and mid-retry it has neither. That is why the halt is immediate rather than a request. - Fail-closed ship + code-cold promotion — a passing slice's terminal state is a
DRAFT PR. Promotion to ready-to-merge is by a fresh code-cold verifier with NO
test-write access (maker≠checker); a NEW checker each round sees only what the brief hands it —
the human-anchored oracle (the signed
acceptance.md) plus the running build — and never the implementer's reasoning, so the oracle never drifts. - Integration gate — the merged-union suite on a connected DAG component (above).
- Inverted risk report — every SHIPPED slice carries the risk band
pull-requestcomputed for it, reproduced here alongside the halts and highest band first, to draw the human's scarce attention both to what the wave's diffs touched and to the quiet greens where unattended defects actually ship. The rule that produces a band ispull-requestStep 2, and it is not restated here — read it there, including which diffs raise a band and how high. Only two facts about it belong at this layer: a band has two inputs, the diff's blast radius and the slice's own record, and it is the higher of them — so a slice can band HIGH on a spotless first-round record, and a run report that sorts by how cleanly slices passed is sorting by the wrong input. Carry the bands over; never recompute or assign one yourself. Two parties computing one number is how the number stops meaning anything, and this is the number a person triages the merge queue by. A HIGH band is not a stop condition and never halts a slice — nothing in a run pauses for high-risk work (High-risk work is not one of these, below), which is precisely why the band has to reach the human unchanged.
Changing what judges the work
Changes to the checking apparatus are governed by their direction, not their size:
| Direction | What it looks like | Rule |
|---|---|---|
| Tightening | add a reviewer, add a guard, add a halt category | Proceeds. No measurement needed first. |
| Loosening | drop a reviewer, delete a guard, deactivate a scenario (remove it from the contract), weaken a check | Refused until a measurement supporting it exists and a human approves it. |
Propose a tightening freely — it costs nothing and you should make the case whenever you see one. Deactivation is the mirror image, already covered by the Loosening row above: it needs both the measurement and the human, and mid-run it has neither.
Deactivating a scenario is not the same act as reporting one not-reachable, and only the first is
a loosening. Deactivating removes the scenario from the contract: nothing has to prove it, now or
ever, and no one is told. Reporting not-reachable at Verify leaves the scenario where it was — in
acceptance.md — unproven, and escalates it to a human through the required PR ack line; the scenario
still has to be settled, just not by this slice. The test is whether the scenario survives the act.
Still in its contract with a human named → honest reporting; gone from its contract → a loosening,
gated as above. Verify's not-reachable path is the suite's mandated reporting channel and is never
refused, never a halt, and never needs a measurement.
Evidence means a measurement, not an argument. "This reviewer never finds anything" is an argument. "Across the last 20 waves this axis produced zero findings that changed a diff" is a measurement. A loosening proposal carrying only reasoning is refused exactly as one carrying nothing.
The refusal must name which measurement is missing. Refusing with "needs evidence" gives the proposer nothing to act on, so it turns into an argument about whether the evidence is really necessary. Refuse with the specific number that would settle it — "refused: dropping the performance axis needs that axis's finding rate over the last 20 waves, and that number does not exist" — so the next move is to go measure, not to argue harder.
Ambiguous direction defaults to loosening. Replacing three narrow checks with one broad one, merging two reviewers, generalizing a guard: if you cannot tell which way it moves, it is a loosening and it needs the measurement and the human.
Scope: any change, at any time, to anything that judges the work. Not only a slice's retry loop —
also a plan-time edit to a Regression surface, a change to which reviewers run, and an edit to these
skill files themselves. A gate weakened at plan time is weakened for every run after it, so the retry
loop is the narrowest place this rule has to hold, not the only one.
Refusing is not waiting. Mid-run there is no human to approve anything, so a loosening proposed during a run is refused on the spot and its slice halts with the missing measurement in the halt reason. The run does not stall, does not check in, and does not park the slice pending an answer — every other branch keeps draining, exactly as with any other halt.
Why the asymmetry, given that it is not fair. A guard added wrongly costs time, and you find out: something fails that should not have. A guard dropped wrongly costs the property the whole system exists to provide, and it costs it silently — nothing fails, work simply stops being checked, and the run looks faster. The cheapest way to run faster is always to check less, so an agent that can drop its own reviewers can hit any speed target by judging itself less. Dropping checks is the shortest path to "make the run faster", which is why the structure forbids it outright instead of trusting the agent to weigh the trade carefully each time. Speed lives in Dispatch order, not here.
What the router may still do alone. A trigger row that does not fire adds no reviewer — that is the router reading the diff, not a skip. A gate is never skipped: the four floor axes run on every wave even when the diff looks trivial, because "provably nothing to review" is a claim about a diff nobody has reviewed yet.
The escalation ladder — what a run does before it gives up
A run's job is to finish the graph, not to report that it could not. Most slice failures are ordinary: an import that points at the wrong path, a fixture missing a row, an API whose shape the survey read slightly wrong. The agent that hit those can fix them, and a run that halts on the first red test hands a person work it was standing right next to.
What makes a retry worth spending is that it changes tactic. Three attempts at one approach is one attempt and two extra bills — the model does not get luckier on the third pass. So a failure climbs a ladder, and a rung is only spent when the approach actually changed:
| Rung | The move | Spend it when |
|---|---|---|
| 1 | Retry with the failure in context. Hand the implementer the command that ran, its real output, and the diff that produced it — not "the tests failed". | Always first. The commonest cause of a red slice is an agent that never saw the error text. |
| 2 | Root-cause it. Stop editing and route the slice into debugging-and-error-recovery: reproduce it, form one hypothesis, prove it before anything changes. |
Rung 1 came back with the same failure, or traded it for a different one. |
| 3 | Change the route, not the destination. The plan step named one way there; the failure says that way does not work here. Hold the step's done_when and the signed contract exactly as they are, and reach them differently. |
The root cause is the approach itself — a library that does not do what the survey read it as doing, a seam that will not take the adapter. |
| 4 | Shrink the slice. Split what passes from what does not: finish and ship the passing part, then halt on the remainder, naming in the halt the contract it still owes and the failure that stopped it. Cutting that remainder into a row of its own is plan-breakdown's write, not the run's. |
Part of the slice is green, the rest is not, and the two do not have to ship together. |
| 5 | Surface it. The slice halts, its gate flips to you, and the run report carries every rung that was tried and why each one did not work. | The ladder is exhausted, or the next rung needs something only a person has — a credential, a product call, a decision the ADRs do not contain. |
The no-progress guard promotes; it does not halt. An identical failure signature or an identical diff twice means that rung is finished, not that the slice is. Advance and spend the next rung's budget. Halting on rung 1's second identical failure throws away four rungs of repair the run was allowed to attempt, and hands a person a slice nobody has actually debugged yet.
Rungs 3 and 4 change how, never what. The signed acceptance.md is the oracle and it is frozen on
every rung. An approach that reaches a different outcome is not a repair, it is a loosening, and
Changing what judges the work governs it exactly as it governs one proposed any other way. Rung 4's
remainder still owes the same contract: a slice cut small enough that the scenarios it was failing no
longer apply to it is that same loosening wearing a planner's hat.
Some failures skip the ladder entirely. A Critical or High security finding, a secret in a diff, an attempt to edit a frozen artifact, or a check about to be weakened is not a defect to repair — it is the gate doing its job. Those land on rung 5 on contact, with no rungs spent, per What stops a run below.
Climbing is per slice, and the wave does not wait for it. A slice on rung 3 holds up nothing: the barrier waits for TERMINAL states
…(truncated)