CODING PLANNER

Documentation · AR Labs

System reference · Part 2

The leaf route

The four steps where code is written: generate-spec, implement, review (Gate B) and deliver (Gate C), and what each one proves.

One execution rule spans every step in this part and the container routes after it: the session that orchestrates a campaign never authors its artifacts inline. Design records, PRPs, specs, code, and reconciliations are each drafted by a fresh dispatched context — the named agents in .claude/agents/ where one fits the role: the architect authors designs, PRPs, and specs; the engineer implements; the reviewer stays a different model entirely, deliberately not a local agent; the orchestrating session owns the decisions between steps, routes drafts through the human gates, and invokes the recording verbs. A fresh window drafts better than one full of run state, and the gate-runner must not author what it gates. Each command file states this at its top; the one sanctioned exception is a live co-working loop the owner has asked for explicitly. Dispatched authors also pre-load the writing standards their draft will be reviewed against — the general readability rubric (owned by the write-well skill) and the document type's review rubric — read at authoring time from the reviewer's own files, so author and reviewer aim at one target and a draft can pass its readability review the first time.

A leaf is where code is written. It has four steps.

generate-spec

An agent writes down what this piece should do, producing a spec file under the project's spec directory. The floor is file_exists plus spec_frontmatter: the spec opens with a machine-read frontmatter block naming its bead, the sha it was written against, and the files it expects to touch — which the driver later uses to keep siblings out of each other's way. The gate is human: a person reads the spec and approves it.

Who writes it is a per-bead choice. By default the agent is Claude: the session dispatched for this step executes generate-spec.md and does the research and drafting itself. A bead labelled spec:codex is authored instead by Codex, through .claude/scripts/spec/vox-spec.sh — a harness that assembles the bead's context together with the full text of generate-spec.md as the authoring discipline, has Codex draft inside the project checkout, and validates the returned document's frontmatter before writing it anywhere. spec:claude is the label's explicit opposite. The choice is resolved nearest-ancestor-wins by a read-only verb, pipeline-transition.sh spec-author --bead <id>, which prints author=<claude|codex> from=<node|default>: the bead's own label, else the first ancestor carrying either, else the default. The opposite label is what keeps the inheritance from being a one-way ratchet — a campaign-wide choice is one write on the root, and a sub-tree can still opt out. A node carrying both labels is refused rather than guessed at.

The choice changes only who writes the document, never what the document must satisfy: the human gate, the spec_frontmatter floor and the branch point below are identical on both routes, and a Codex run that fails is an ordinary failed attempt — it never falls back to Claude, because a silent fallback would hide that the chosen author failed. A human sets it — no pipeline verb writes the label, and bd update --add-label is outside the guard's remit, so nothing mechanically stops a session from setting one. That makes this a convention stated here rather than a constraint the machinery enforces, and it is worth knowing which it is.

This step also holds the pipeline's one branch point — is this piece a leaf or a container? — and the branch point is machine-passable on the spec's own verdict. The command's complexity assessment already classifies the node as part of authoring the spec, and it records that verdict in the spec frontmatter (classification: leaf | compound, value-validated by the floor, never required — a spec without one simply rests here for a human). When the dial relaxes this gate and no successor is named, complete resolves the way taken from the recorded verdict, so an unattended run flows through classification instead of resting at it. The one exception is the task door's compound branch: re-routing a task-entered bead compound always takes a human's recorded ruling (Part 1, entry doors), which no verdict and no dial can supply — leaf-ward flows free, compound-ward rests for a person.

implement

An agent writes the code, in the dev worktree, on a branch cut from origin/<target>. The gate is human. The agent that writes the code is never the session that runs the campaign's gates: the orchestrating session dispatches a builder for this step and verifies what comes back, the same separation the integrate step already requires. This holds in interactive runs as much as unattended ones — for a while it was stated only for integrate and enforced for implement only by the accident of the unattended path's mechanics, and an interactive session following the instructions honestly would implement inline. The instruction sheet (implement.md) now states the rule at its top. The step declares no floor at all, which is worth naming honestly rather than glossing: there is no deterministic evidence check at implement, so it advances by dispatched work plus a person, never by the machine's own hand.

The builder reviews its own diff before anyone else does. After the last build step and before manual verification, the builder runs the reviewer agents of Anthropic's pr-review-toolkit plugin over its committed diff: a correctness and coverage wave (bugs, swallowed errors, untested behaviour, and type design when the diff adds or changes types), then a readability wave (stale comments, needless complexity). It launches them itself, as its own subagents in fresh contexts, whether it was spawned by the driver or dispatched as a subagent by an attended session. Each reviewer runs once and only reports; the builder fixes what it agrees with under the usual test-first discipline and records every declined finding, with its reason, in the implementation walkthrough. This quality pass is advisory and separate from Gate B: it has no verdict, floor or bead metadata, and it is the builder's own step, not an independent check. It sits before review rather than after because Gate B refuses a review of any commit but the current HEAD and allows one fix round, so polish made after review would invalidate the review and spend that round. Manual verification and the recorded suite run come after the pass, so both cover its fixes. The method is Step 4.6 of implement.md.

This step owns the push and the pull request. They are not part of delivery. The reason is concrete: a separate unattended dispatch script runs the implement instructions directly and never reaches a driver-dispatched delivery step, so moving pull request creation downstream would silently stop that workflow producing pull requests at all.

review — Gate B

A second agent reviews the code. It runs in a different command-line tool under a different model family, so it shares no context and no reasoning with the agent that wrote the code. This is the hardest context boundary in the system and the reason review is its own step.

Review declares no human gate. Its floor is review_verdicts, and the floor is specific. First, the review script must have written the file: each time it publishes a review it appends a provenance note to the piece's record, naming the file and a sha256 fingerprint of its bytes, and the floor refuses the most recent review file unless the most recent such note names that file with that fingerprint. A review file written or edited by hand never passes, and the refusal names the remedy, which is to run the review script. Then the most recent review file for this piece, under the project root resolved from the spec, must carry parseable frontmatter with all four axis verdicts present, none of them a failure — a warning passes, named — and the reviewed commit must equal the project repository's current head. A review of other code is not a review of this code. On a pass the floor records the review: the artifact, the reviewed commit, and the axis verdicts. A named waiver label can excuse the step, and the waiver records a reviewed commit too, so that waived and reviewed pieces arrive at the next step in the same shape.

The whole sequence of review files is named in every pass and every refusal, because fail-then-fix history is audit material rather than noise.

A blocking finding cannot vanish between rounds. Each round is a fresh look, so a serious finding raised in round one could simply go unmentioned in round two, and silence would read as resolved. The review script therefore keeps a findings ledger in each review's frontmatter. One list names the round's open CRITICAL findings, each under an id the script mints once and copies forward unchanged. A second list answers every finding carried in from the round before with one of three words: fixed, stands or withdrawn. The next round is handed that ledger, the previous review's path and its reviewed commit, not the previous review's prose. The script refuses to write a round that leaves a carried finding unanswered, or whose answers contradict its own axis verdicts; a standing finding keeps its axis failing. The floor checks the same thing independently, comparing the previous review's open findings with the latest review's answers. It refuses an unanswered finding, an unknown word, any finding still standing at a passing close-out, and a review that claims a different predecessor than the one the floor sees. A withdrawn finding passes, and the pass names it. A review written before the ledger existed carries none, so it hands nothing forward and nothing is enforced against it.

deliver — Gate C

Gate C is landing, not publishing. A pull request existing is not the end of a leaf; the reviewed code being on the integration target is. This distinction caused real confusion when the instructions and the design record disagreed about it, and the instructions were the ones that were wrong.

Delivery declares no human gate. Its floor is landed_on_target, which accepts either of two proofs:

  1. The commit that review recorded is an ancestor of origin/<target>.
  2. The forge, asked which pull requests contain that commit, names one whose merge timestamp is non-null and whose merge commit is an ancestor of the target.

The second proof exists because of squashing. When a forge squashes a merge it discards the original commit and creates a new one, so the reviewed commit is not on the target; a different commit with the same content is. A check that looked only for the reviewed commit would fail on every squashed merge. Note that the merge timestamp is the only sound test — the forge reports a merged pull request's state as "closed", so state alone cannot distinguish a merge from an abandonment.

If neither proof carries, the floor refuses. Where the question is genuinely unanswerable because machinery is missing, it escalates instead. One case deliberately refuses rather than escalating: a repository with no forge remote can hold no pull request at all, so the first proof is the whole test there.

A landing must also reach the machinery that runs. The executor loads hooks and settings from the primary checkout ($VOX_ROOT), which no merge updates, so after the proof and before the record the deliver verb fast-forwards that checkout to origin/main when the landing is on main and the leaf's change touched the enforcement surface (.claude/scripts/lib/primary-sync.sh). The primary is shared with other sessions' uncommitted work, so the verb only fast-forwards, and only when the primary is on main, has not diverged, has no uncommitted tracked surface files, and git's --ff-only accepts. Otherwise it refuses with an owed=human token naming the reason, leaving the primary untouched and the leaf open, and the driver asks again on every pass. Which files count as surface is the guard's own list (--print-surface), and a guard that cannot report it rests the leaf the same way. Ordinary landings and deep-mode landings onto an integration branch leave the primary alone. The one exception fails closed: a leaf with no recorded review base that lands by a fast-forward cannot show where its change began, so it counts as a surface landing, and the accepted cost is that such a leaf may rest on a dirty primary even when it changed nothing the executor loads.

The floor never reads the stored pull request link as an input. It stamps that link, after the proof succeeds, from the pull request the proof resolved. This ordering is the point. The link had previously been written only by prose instruction — several command files told an agent to run the update and no code anywhere enforced it — with the result that of eighteen pieces that should have carried one, three did. A machine proof cannot rest on a bookkeeping step an agent is asked to remember.

What delivery does then depends on the target. In wave mode a person merges; this step confirms the branch is pushed and the pull request is open, mergeable and pointed at the target, and stops. The floor's refusal until that merge lands is the wave boundary, and it is a stronger boundary than a declared human gate would be, because it refuses on evidence rather than on a word in a manifest. In deep mode no person sits at this boundary, so the step merges the task branch into the integration branch and pushes, preferring a merge commit because a squash lands but can then only be proved through the forge lookup.

There is no code path in this step that merges to main. Not in wave mode, not under any argument. The end-of-campaign merge onto main belongs to a person, behind a checkpoint that does not exist yet.

Delivery is terminal on the leaf route, and on a pass it closes the piece. That is a real change in who may close finished work, and Part 5 returns to it.