CODING PLANNER

Documentation · AR Labs

System reference · Part 1

The concepts

How work is broken up, the two entry doors, the three routes, commands, floors and gates, refuse against escalate, worktrees, and wave and deep modes.

How work is broken up

A large piece of work is a campaign. A campaign is split into smaller pieces, and those pieces can be split again, as many times as the work needs. The pieces at the bottom are leaves: real code gets written there. The pieces above them exist only to hold other pieces, and are containers. The campaign itself is a container — the root one.

Every piece, at every level, is tracked as an issue in the issue tracker. The pipeline keeps its own bookkeeping on that issue under a reserved pipeline. namespace: which step the piece has reached, the commit that was reviewed, where the work is meant to land, and a record of each transition. That bookkeeping is the pipeline's entire memory. Nothing important lives in any running process.

A node's kind is derived, never stored. A piece that has recorded a verified fan-out — that is, one that has been split into sanctioned children — is a container. A piece that has not is a leaf. Asking the tracker rather than storing a label means the two can never disagree.

Work enters in one of two shapes, and both carry a bead. A campaign fans out into the tree described above. A lone task is a single bead with no campaign above it: it runs the leaf route on a one-node frontier — the same walk, the same gates and floors, with evidence rigor calibrated per slice rather than per shape. That task-versus-campaign binary is the whole routing model. There is no smaller shape: work that does not warrant a bead does not enter the pipeline at all.

Entry doors

Both shapes enter through a recorded door, and nothing enters silently. A campaign enters through the grill handoff: the interview stages its resolved dial and delivery decisions on the campaign bead (grill.autonomy/grill.delivery), init binds the approved brief, and set-mode --from-staged applies the staged decisions — a deterministic check refuses to proceed into create-prp while a staged decision sits unapplied. A lone task enters through the task door: init-task is the single write that records the routing decision, the PRP-or-waiver (label prp:waived plus a ruling reference — absence is approved, never silent), the autonomy level, and the delivery mode, and stamps pipeline.entry_route=task so the route derivation runs the task root down the leaf route rather than the root route. Pre-door work carries the adoption stamp instead (adopt, pipeline.adopted): honestly recorded as adopted, never dressed as entered, passing the provenance checks loudly by that stamp. Every write verb refuses a bead inside no managed tree, naming the doors.

Work a step finds mid-run enters through the same task door, by discover --from <finder> --parent <epic> --title <t> --description <d>: it refuses a duplicate title among the parent's open children and a parent whose children are the pipeline's (a recorded fan-out or any step), files the bead with a discovered-from link, and runs init-task on it at the finder's effective autonomy and delivery under the standing ruling below. Past the manifest's discovery.max_entries_per_tree for the finder's tree it files the bead without dooring it. A finder with no autonomy level doors the new bead at wave, the entry default. One discover per tree and per parent runs at a time, under two locks, so two finders cannot both pass the duplicate check or the cap; a child listing that fails or cannot be read escalates.

The standing lone-task ruling (2026-09-08): an ordinary lone task waives its PRP under this standing ruling — the bead's own description is the goal authority — so init-task --waive-prp citing this ruling needs no fresh per-task ruling; anything non-default names its real authority.

A task that later proves compound is re-routed, not smuggled: classifying a task-door bead compound (complete --step generate-spec --next to-issues) requires a recorded ruling (--ruling/--authority) and records "task re-routed compound" — an unattended run cannot supply one and rests for a human.

The step table and the three routes

Each node moves through a sequence of steps. The sequence is not the same for every node, because a leaf and a container do different work, so the pipeline declares three routes:

  • root — generate-design, create-prp, refine-design, to-issues, delegate, integrate
  • compound — generate-spec, to-issues, delegate, integrate
  • leaf — generate-spec, implement, review, deliver

A node's route follows from whether it is the root and from its derived kind. The successor of a step is simply its neighbour in the route array, and the last entry is terminal. There is no stored "next step" that could drift out of line with the ordering.

The routes exist as three separate lists because the root's design slot is filled by a different process from every other node's. The root enters through a product requirements document; every node below it gets a spec of its own. That gives the routes genuinely different step sets, with no single global ordering that all three are subsets of. The same step name can therefore have different successors on different routes — generate-spec runs on to to-issues on a container and on to implement on a leaf.

All of this lives in one manifest file, .claude/pipelines/coding-pipeline.json. The scripts contain no step names of their own; they read the manifest. That is deliberate, so that changing the process means editing data rather than editing logic.

The three things a step can declare

This is the idea the rest of the system rests on, and it is worth reading slowly.

A command is the work itself. It names a file of instructions, and running it means spawning an agent that follows those instructions — writing the spec, writing the code, reviewing the diff.

A floor is a machine proof. A script looks at the world — the git history, the forge API, the filesystem — and answers yes or no on evidence. No judgement is involved and no opinion is consulted. The manifest calls these verify entries; each names a predicate such as file_exists, children_terminal, review_verdicts or landed_on_target.

A gate is the checkpoint that must be satisfied before the step counts as done. In the manifest a step declares gate: "human" or gate: null. A human gate is a suspension point: the machine may do the step's work, but it may not declare the step finished. Steps with no declared gate are settled by their floor instead.

The founding rule is that a floor never passes on anybody's say-so. If a floor cannot prove the thing, it must refuse or escalate; it must never assume. The reason is the unattended goal. A floor that can be talked into passing means an unattended run is an unchecked run, and an unchecked run is worse than no run at all, because it produces a confident record of work nobody verified.

Refuse and escalate

When a floor will not pass, it says so in one of two ways, and the difference is operational rather than cosmetic.

Refuse means "this is not done yet." Work is owed. The run carries on, and on a later pass it may spawn an agent to do the owed work.

Escalate means "I cannot answer this." The machinery is missing or broken — the repository cannot be found, the forge cannot be reached, a merge commit has not been fetched. The node is parked for a person with the reason recorded, and the run continues with other work. A dispatcher can escalate too: when it exits non-zero after writing a recognised kind to the attempt result file the driver hands it (the executor writes kind=lookup_failed when it cannot read a bead's integration target or delivery mode, and the review step's failure adapter writes kind=harness_broken when the review harness itself crashed, since re-running a broken script cannot succeed), the driver records an escalation with that reason and counts no failed attempt.

Collapsing these two into a single failure destroys the only signal that tells a person whether to fix the artifact or fix the harness.

Where the code is written

Work is never written in the checkout you normally look at.

A git worktree is one repository checked out into more than one directory at once, each directory on a different branch. A project run through the pipeline has two: a primary checkout kept on main, which is the clean integration point, and a dev worktree sitting on the branch for the piece of work in hand, which is where code is actually written.

The rule is: edit in the worktree, never in the primary. This matters more than it sounds, because git makes the two invisible to each other and nothing errors when you get it wrong. A file written in the primary cannot be seen from the worktree, and the reverse. You can stare at the worktree unable to find your own change while it sits quietly on main in the other directory. The rule is not left to memory — a hook intercepts writes to a guarded project's primary checkout, refuses them, and hands back the corrected path.

The part that governs everything in the next section: when a piece of work starts, its worktree fetches and cuts a fresh branch from some base, and when the piece finishes, its work lands back on that same base. Cut from here, land back here.

A worktree can never sit on main itself, because git refuses to have one branch checked out twice and the primary already holds it. The base is therefore always a remote reference, freshly fetched, spelled origin/<ref>.

Dispatched work gets one worktree per bead. Before a step's session starts, the executor asks the resolver where the bead's codebase is, then looks in that checkout's list of worktrees for the one on branch task/<bead-id>. The first step creates it, at <project_roots.worktrees>/<project-id>/<bead-id>, cut from origin/<target>; every later step of the bead finds it by that branch and reuses it untouched, so the spec, the code and the review all sit on one branch. Reviewers get no worktree at all, since a review only reads.

A project registered in .claude/worktrees.json (today, Salesbot) also keeps one shared dev worktree, with its secrets and installed dependencies in place. A bead with no worktree yet takes the shared one when it is free: nothing uncommitted or untracked in it, no merge or rebase in progress, not locked, its commit already contained in origin/<target>, and no open bead's branch checked out. The executor switches it to task/<bead-id> and never discards anything to do so. When the shared worktree is occupied, it is left exactly as it was and the bead gets its own worktree, as for any other project. Either way, a worktree not yet prepared for the bead is provisioned before the session starts: the gitignored files the project's .worktreeinclude lists are copied from the primary, never over an existing file, and the project's .vox/worktree-setup.sh installs dependencies. A marker in the worktree's git folder records success, so later steps skip it. A project that commits straight to main by standing ruling is refused at dispatch, because main is checked out in the primary and git will not check a branch out twice. No worktree is ever created inside another working tree.

When a piece finishes and its branch has landed, the branch is deleted with the safe form of the delete command, the one that refuses if the work was not actually merged. That refusal is the check, not an obstacle to route around.

Wave and deep — what the branch is cut from

The two modes are one choice: what is the base?

Wave mode. Every piece is cut from main and lands back on main. A piece starts from whatever is on main when it begins, and its work stays invisible to its siblings until it lands. This is right when the pieces are independent. When one piece needs another's work, it must wait for that piece to reach main first.

Deep mode. A person cuts a branch for the campaign, pushes it, and records it on the campaign as the integration target. Every piece is then cut from that branch and lands back on it. The second piece starts with the first piece's finished work already underneath it, the third starts with both, and dependent work stacks up without anything touching main.

The mode is derived, never stored. The campaign either carries an integration branch or it does not; its presence is deep mode and its absence means the target is main, which is wave mode. There is no separate mode word that could contradict the target.

Two consequences deserve to be explicit. Deep mode is what makes long unattended runs safe, because the machine lands work freely onto a branch that is not main, where a mistake costs little and a person still reviews the whole campaign before it reaches main. And a person cuts and pushes that campaign branch — the machine will not create it, and refuses a target that does not already exist on the remote.

How the work lands — a separate axis

Where work lands and how it lands are independent settings, because a branch name cannot tell you whether the landing goes through a pull request or a direct commit.

Through a pull request is the default. The piece is built on its own branch, pushed, and a pull request is opened against the target. A person merges it.

By direct commit means no branch and no pull request: one commit straight onto the target. Some projects are run this way by standing ruling.

So in wave mode with the default, there is exactly one merge per piece of work, done by a person, against main. That merge is what the landing floor waits for: it refuses on every pass until the merge happens, then proves the code arrived and closes the piece.