Imported verbatim from /Users/rob/repos/agent-fanout-generic.md on 2026-08-14.
This copy is authoritative for this repo. Do not summarise, compress or adapt it here — when the source changes, every consuming repo is re-imported in the same change.
Prompting a coding agent for long-running fan-out workflows
Use this sourcebook when writing a launch prompt and goal file for a long-running agent campaign. It is intentionally project-neutral. Copy only the contracts and checks that apply to the run; unrelated history and generic ceremony make a goal harder to re-read after compaction.
The durable unit of work is a goal Markdown file on disk. The launch message is a short pointer to that file. The root coordinates the campaign and owns integration; bounded children receive complete, self-contained lane briefs and the cheapest route that can reliably satisfy them.
This document is harness-neutral and deliberately names no model. The body talks in roles — RETRIEVAL, MAPPING, GATE, EXECUTION, JUDGMENT+EXECUTION, REVIEW, DESIGN+INTEGRATION, SECURITY — and in capabilities: how much context a spawn inherits, how many lanes may run at once, how deep delegation may go. A harness profile resolves those into concrete models, reasoning depths and spawn mechanics:
- Appendix A — Codex profile. Complete: model and effort routes,
fork_turns, the thread pool. - Appendix B — Claude Code profile. Deliberately thin. It carries the role mapping and the ways Claude Code’s dispatch surface differs structurally from Codex’s, and defers routing itself to the always-loaded global rules rather than keeping a second copy that can drift out of step.
The run contract names the harness once. Every lane then states its role and the route the profile resolves it to — a lane brief carrying only a role name leaves the choice to whoever reads it next.
The harnesses do not differ by a lookup table of model names. Context forking, reasoning effort, concurrency limits, delegation depth and the return path for a child’s deliverable differ in kind, and a lane written against the wrong one fails in ways its acceptance check will not catch. Read the profile before writing lanes, not after.
1. Define the run contract first
Every goal begins with an explicit run contract:
Run mode: daytime | front-loaded | unattended
Human availability: available | reachable but not to be asked | unavailable for the whole run
Terminal condition: stop after the listed lanes | continue into the fallback queue
Current layer: research | design | implementation | review | live verification | deployment
External-write authority: [exact trackers, hosts, deployments, databases or workflows]
Harness: [the harness this run launches on; its profile resolves every route below]
Root role / resolved route: [role, plus the exact values the profile resolves it to]
Launch rationale: [one sentence]
Selected topology: solo | single auxiliary | campaign | campaign + security
Topology rationale: [the independent bottleneck or risk that justifies this shape]
Run-end report: written to codex/report-[date]-[run-id].md as the final action, unprompted
The report line belongs in the contract rather than only in §6’s report section, because the contract is what an agent reads first and re-reads after compaction. §10 explains why stating it once, as a format, reliably fails to produce one.
Reconcile the tracker and other drift-prone starting state before selecting the topology; read-only
preflight is allowed before the declaration. No spawn or mutation happens until the topology and
its task-specific rationale are recorded. solo is the default when an auxiliary would only repeat
work the root must do anyway. A later declaration may escalate the topology when new evidence exposes
an independent bottleneck or material risk; never silently downgrade or add a reviewer by habit.
Daytime means the root may return a genuinely material decision that neither the goal nor a durable source resolves. Children still return uncovered decisions to the root; they do not ask the user.
Unattended means no human will answer. The goal must provide defaults for expected forks and an ordered fallback queue if the terminal condition says to continue after a lane parks. Do not infer availability from the time of day or the expected duration.
Front-loaded is a third mode, and it is usually the right one
Front-loaded means the human is awake and reachable, and precisely because of that every fork was put to them before the goal was written. The run then behaves like an unattended one — nobody is asked anything mid-run — but questions are batched into the final report rather than defaulted silently into a fallback queue.
It is worth naming as its own mode because the two obvious modes both waste the human. Daytime invites a long day of interruptions over decisions that could all have been taken in one sitting beforehand. Unattended is honest about not interrupting but forces the goal to guess at forks the human was sitting right there to answer.
Three rules make it work:
- Extract the forks before writing the goal, and write the answers in as frozen decisions with the date and the person. A fork answered in chat and not written into the goal did not get answered.
- State what a lane does with an uncovered decision: take the goal’s default; if there is none, take the narrowest reversible option, implement it, and record both the question and the choice. Say explicitly that a decision you had to make yourself is not a blocker — otherwise lanes park on ambiguity and the run delivers nothing while the human sits available and uninterrupted.
- Require a dedicated questions section in the final report, separate from everything else. That section is the entire point of the mode: it is the batch. Say it must not be merged into another section and must not be omitted because nothing felt important enough.
Root role and route
Every generated goal and handoff must tell the operator what to launch — as a role, and as the exact values that role resolves to on the named harness:
Harness: [name]
Root role: DESIGN+INTEGRATION
Resolved route: [the profile's values for that role at standard depth]
Why: this wave coordinates independent lanes, owns integration and may encounter uncovered seams.
The shape of the whole wave picks the root’s role and how much reasoning depth it needs:
| Shape of the whole wave | Root role | Depth |
|---|---|---|
| Normal multi-repository or multi-lane campaign; the root integrates bounded children | DESIGN+INTEGRATION | standard |
| Unresolved architecture, unknown-cause debugging or several interacting decisions | DESIGN+INTEGRATION | raised |
| Authentication, authorisation, privilege, migration, secret or data-loss risk | SECURITY | raised; the highest tier only for exceptional risk or ambiguity |
| Execution wave whose seams, dependencies, ownership and acceptance are fully frozen | EXECUTION | standard |
| Execution wave with a stated acceptance check but material context, judgement or blast radius inside the implementation | JUDGMENT+EXECUTION | raised |
| Bounded read-only audit with a fixed evidence schema and no product decisions | EXECUTION, with RETRIEVAL lanes | standard |
Never launch an implementation or integration wave on a RETRIEVAL route — it is the cheapest route precisely because it is not asked to decide anything. Do not select the maximum reasoning depth by default. If the shape is uncertain, put DESIGN+INTEGRATION at standard depth at the root and push bounded work down after the root has frozen it.
2. Make the goal file the unit of work
A goal file survives compaction and can be re-read before every lane. A long chat prompt cannot be relied on to preserve routing, authority, traps and corrections across a multi-hour campaign.
Put these in the goal file:
- the run contract, outcome and measurable success criteria;
- independently verified starting state, timestamps, repository heads and exact SHAs;
- root, child and optional grandchild authority;
- a dependency-aware lane table with role, resolved route, context scope, ownership and acceptance;
- applicable constraints, corrections, false-pass traps and external side-effect boundaries;
- validation, blocker defaults, terminal condition and required final report.
Do not paste the entire sourcebook into a run. Include everything needed to reconstruct the current run and nothing that cannot change the outcome. Keep stable phrasing stable between runs.
At the top of every goal say:
This file is your goal. Re-read it in full after compaction. Within one context, cite the section
you need rather than re-reading the file.
Re-read after compaction, not per lane. Everything a context reads stays in it and is re-sent on
every subsequent turn, so re-reading a goal five times leaves five copies of it in the root’s
context for the rest of the run. After compaction the goal is genuinely gone and must come back;
inside one context it is already there, and the instruction to consult it is satisfied by citing the
section. Where the launch message hands a goal to a fresh session, @-mention its path rather than
telling the agent to read it — the file is attached to the first request and costs no tool call.
Prefer one immutable goal file per run. A correction or new phase gets a new file that explicitly supersedes the old one. Do not silently rewrite the instructions an earlier run received.
Re-check the STATE of every tracker item a goal names, not just its content
A goal file is copied forward, and a stale fact inside one is invisible because it reads exactly like a current one. One goal said an interface change would be cut “together with” two sibling items as a single revision rather than three. That was true when the note was written on the tracker. By then both siblings had shipped, eleven and twenty-one runs earlier — the cluster had dissolved and only one item survived it. The line was copied into three consecutive goal drafts, into a decision comment posted back to the tracker, and into a question put to the operator, before anyone queried the item’s state.
Before carrying any tracker reference from an old goal into a new one, query its state, not its body. One loop covers a whole goal, and it costs seconds against a run that costs hours.
The failure is asymmetric and that is what makes it dangerous: a closed item you believe is open produces confident work on something already delivered, and nothing in the repository contradicts you — the code is there, the tests pass, and the only signal is a tracker you did not read. Correct it on the item with the framing intact rather than quietly fixing the next goal; the stale version is what the previous goals said, and the next reader finds those first.
Where the run’s artefacts live: a gitignored codex/ in the repository
Every repository driven this way gets a codex/ directory at its root, listed in .gitignore,
holding one set of files per wave. The name is historical and it is load-bearing — keep it whatever
harness runs the wave. codex-sync.sh mirrors run artefacts between machines by matching that exact
directory name, so renaming it to something harness-neutral silently stops the syncing rather than
failing loudly. Read codex/ as “run artefacts”, not as “Codex’s directory”.
codex/goal-<date>-wave<N>.md the goal file
codex/launch-<date>-wave<N>.txt the launch message, copy-paste ready
codex/report-<date>-wave<N>.md the run-end report the agent writes (§10)
Where a repository has adopted a real tracker, two of these three change. With per-item state
living in tracked tasks — see backlogconfig.md alongside this file for the configuration proven on
one project — the goal file stops enumerating the work and points at a query instead, and the report
stops being a file: task state carries what landed, what parked and why, and the run’s closing
message goes to the terminal as a covering note. The codex/ directory still earns its place for
the goal file, the launch message, and expensive-to-re-derive source assessments — but it stops
being the durable record of what happened, which is the job it was never good at. A report file is
only read by whoever happens to open it; a parked task is read by the next run automatically.
Three reasons this beats a scratch path outside the repo. The artefacts sit next to the code they
describe, so an agent given only the repository can find the last three waves’ goals and reports
without being told where they are. The whole history of what was asked and what came back is one
ls. And gitignoring the directory keeps run scaffolding out of the project’s history, which is the
same rule that applies to plans and specs — they are working state, not deliverables.
Gitignore the directory, not a filename pattern, so a new artefact type cannot leak by being named something the pattern did not anticipate.
Fresh launch message
Launch this run with <root route, resolved from the harness profile>. Read <absolute goal path> in
full and adopt it as your goal. Start with the run contract and routing table. Do not begin a lane
until its ownership and dependencies are satisfied.
Mid-run replacement
Do not pivot on receipt. Finish and durably record the in-flight atom first. Then read <absolute goal
path> in full; it supersedes <old goal>. [State exactly what changed underneath the session and what,
if anything, is fenced off.]
The changed-underneath statement matters even when nothing changed: say that no file, branch, worktree, commit or external resource was touched when that is true.
3. Copy the routing contract into every goal
## AGENT ROUTING CONTRACT
The root owns architecture, uncovered decisions, integration, tracker and other external mutations,
commits, pushes, final gates and final synthesis unless a lane explicitly delegates an authority.
Every spawn MUST state its role, the route the harness profile resolves that role to, and its
context scope. Write the resolved values into the lane — a brief carrying only a role name leaves the
choice to whoever reads it next. A spawn that inherits the parent's context normally inherits its
route too, so inherit only when that route is exactly right for the lane.
- RETRIEVAL: deterministic retrieval, inventories, extraction, CI or log reduction and exact lookups.
Read-only unless a narrowly specified write is explicitly authorised.
- MAPPING: read-only code mapping, issue or document synthesis and structured summaries whose
completeness the root can check.
- GATE: deterministic gate execution, mechanical transforms and bounded validation. Runs one named
gate once against one resolved state; reports failures verbatim and does not repair source.
- EXECUTION: implementation against a frozen seam, with explicit file ownership and a written
acceptance check. The packet is fully specified and the parent can verify the result directly.
- JUDGMENT+EXECUTION: implementation whose acceptance check is known but whose local choices need
broader context, material judgement, risk control or coordination across a wider blast radius.
- REVIEW: bounded complex debugging, or correctness, regression and concurrency review across several
sources, where the result is still externally checkable.
- DESIGN+INTEGRATION: ambiguous design, freezing shared seams, integration, wiring and unknown-cause
debugging.
- SECURITY: authentication, authorisation, permissions, migrations, data-loss risk, security design
and adversarial review of those changes.
Use the cheapest route that reliably satisfies the lane. Before raising it, check whether the brief
lacks a success criterion, frozen decision, dependency, tool route or verification loop.
Give a lane the narrowest context that lets it finish: a self-contained brief for a frozen lane, the
recent orchestration context only where those decisions bear on the work, full inherited history only
where the child genuinely needs it. The harness profile says how each is expressed, and whether the
middle option exists at all.
Every role except DESIGN+INTEGRATION and SECURITY returns uncovered decisions to the root. None of
them invents an answer, widens scope, commits, pushes or mutates external state unless the lane
grants that exact authority.
The first routing question is: can the acceptance check be stated now? If not, use DESIGN+INTEGRATION to freeze the seam. If yes, use RETRIEVAL or MAPPING for read-only work, EXECUTION for fully specified bounded implementation, and JUDGMENT+EXECUTION only when the implementation itself still needs material context, judgement or risk control.
The second is whether to spawn at all: how much of the lane’s output gets discarded? A spawn runs in its own context, so it re-reads what the parent already had and pays for its own turns, and only its final message comes back. That trade wins when the job generates a lot of material nobody needs to keep — a log to reduce, a broad inventory sweep, CI output, mass file reads — and loses when the answer is a line or two, where doing it in the parent is cheaper than standing up a fresh context. Route by how much is thrown away, not by how cheap the route is.
Require a one-sentence reason for every JUDGMENT+EXECUTION, DESIGN+INTEGRATION and SECURITY child. Difficulty, a long log or prior use of that route is not by itself a reason. Before every follow-up, reclassify the work that remains. When the design route has settled the decision and only bounded execution, evidence or validation is left, start a fresh EXECUTION or RETRIEVAL lane carrying the frozen facts instead of automatically continuing the design thread. Use JUDGMENT+EXECUTION rather than retrying EXECUTION when the first result proves the packet was misclassified as fully specified.
Do not put token budgets, cost targets, model-allocation quotas or artificial output allocations in the goal. Route by the shape and risk of the remaining work.
4. Authority, ownership and the thread pool
Every harness caps how many lanes may run at once and how deep delegation may go; Appendix A and Appendix B give the exact numbers, and they are not the same number or even the same kind of limit. Whatever the cap, it provides runway — it does not authorise delegation, and a deep tree is not desirable merely because it is permitted.
- The root freezes shared seams, assigns ownership, resolves decisions, integrates, performs authorised external writes, commits and pushes, owns the integrated gate and synthesises the run.
- A child owns one bounded lane. It does not commit, push or mutate external state by default.
- Auxiliary work substitutes for root work; it does not duplicate it. The root verifies load-bearing claims and the integrated result in proportion to risk, but does not repeat a successful mechanical lane merely to perform the same work twice.
- A child may launch a grandchild only when its brief explicitly permits delegation and the child can supply a complete independent lane brief. The child checks and synthesises the grandchild’s result.
- Every lane says
Delegation: forbiddenor grants exact bounded grandchild authority. - One file has one owner. Shared and generated integration files belong to the root or a named wiring pass. Do not put two writers on the same file.
- Name resource mutexes such as a simulator, package manager, database migration lock or integration test environment. Name one integrated gate owner rather than having every worker repeat it.
Flat, non-delegating fan-out may use the whole pool. If any child may delegate, the root starts at roughly two-thirds of the pool as direct children and reserves the rest for grandchildren, replacement lanes and urgent investigation. Read that as a ratio rather than a count — the pool size is a harness fact, and on some harnesses excess spawns queue rather than fail, which hides saturation instead of surfacing it. Never spawn merely to occupy a slot.
Append-only registries — the contention case one-owner does not solve
One file, one owner handles files a lane can own outright. It does not handle the single registry function every lane must append to: a migration registrar, a dependency-injection container, a route or command table, a plugin list, a generated manifest. Those are one file by construction, so assigning them to the root creates a queue.
The tempting answer — lanes state their entry in their report and the root applies them all at integration — is wrong for an unattended run. A lane that cannot register its own entry cannot exercise its own code, so it either sits blocked for hours or validates against a state that does not exist. Both fail quietly overnight.
Split the registry before fan-out instead. In the pre-fan-out pass the root:
- Creates one empty stub file per lane, each exporting a single registration function whose name is frozen in the goal.
- Reduces the shared file to a call list invoking those functions in a frozen order, and never edits it again.
- Pre-assigns every ordering-sensitive identifier in a table in the goal — migration numbers or names, route paths, capability or permission names, generated-artifact keys.
Each lane then owns exactly one file, is testable in isolation, and blocks nobody. A lane that wants an identifier other than its assigned one stops and says so rather than choosing its own; that is the point of pre-assigning them.
Assigning an identifier to a lane does not assign the work to it — the owned-files list does, and that is the line that gets it wrong. One wave pre-assigned both halves of a new read surface to a lane in the identifier table, then wrote that lane’s ownership as its own front-end files and a registration stub. Nothing owned the server handlers. Every lane passed its acceptance check, the gate was green, and the feature shipped as a truthful “unavailable” page — the gap stayed invisible until a human opened the console. Cross-check the identifier table against the owned-files line of the lane it names: if a row assigns a route, a migration or a generated key, that lane’s ownership must include the file that implements it, in every repository and every language the identifier touches. A route is two files when the server and the client are written in different languages.
Where a digest, lockfile or checksum covers the whole registry, it belongs to the integration pass and is regenerated exactly once, at the end.
A shared evidence document is a registry too — but splitting it is the wrong fix. When several lanes each produce a number, a finding or a row for one results document, the per-lane stub pattern above produces a shredded document nobody can read. Give the document one owner, scheduled last, with real declared dependencies on the lanes that feed it. That owner takes the others’ figures as inputs and writes the whole thing once. It is a deliberate exception to “a lane writes its own evidence”, so say in the goal that it is one and say why, or the late owner reads as an accidental bottleneck.
When the project has no released users and no persisted state to preserve, prefer collapsing the registry to a single fresh baseline over extending it. A chain of increments nobody will ever replay is pure carrying cost. That licence is temporary — record it with its expiry, per §8.
Standard campaign topologies
- Research: RETRIEVAL and MAPPING lanes, then one EXECUTION synthesis lane.
- Ordinary implementation: DESIGN+INTEGRATION freezes unresolved seams, EXECUTION workers implement, a REVIEW lane checks the bounded changes, then the root integrates and a single GATE owner validates.
- Judgement-heavy implementation: DESIGN+INTEGRATION freezes the shared decisions, JUDGMENT+EXECUTION workers own the context-heavy or wider-blast-radius implementation, then the ordinary review, integration and gate sequence applies.
- Security-sensitive implementation: the ordinary topology plus a SECURITY review after integration of authentication, permission, migration, secret or data-loss boundaries.
- Premise or depth audit: independent EXECUTION evidence lanes, with DESIGN+INTEGRATION synthesis only when the evidence exposes a genuine product, architecture or security decision.
- CI and gates: workers run focused checks; one GATE lane validates the integrated state.
Optional narrow agent roles
Custom agents are useful when the same contract recurs. Keep roles narrow:
| Role shape | Routing role | Contract |
|---|---|---|
| Mapper | MAPPING | Read-only maps, inventories and structured research with searched scope and completeness check |
| Lane worker | EXECUTION | Frozen implementation, owned files, focused validation, no commit or external mutation |
| Complex lane worker | JUDGMENT+EXECUTION | Context-heavy or wider-risk implementation with frozen architecture, owned files and focused validation |
| Reviewer | REVIEW | Read-only correctness, regression, concurrency and false-pass review |
| Security reviewer | SECURITY | Read-only review only for high-blast-radius security and data contracts |
| Gate runner | GATE | Run one named gate once against one resolved state; report failures, do not repair source |
| Worktree auditor | REVIEW | Prove dirty state, ancestry, unique commits, patch identity and cleanup safety; never clean up |
Do not turn the security reviewer into a general quality reviewer. Where a harness lets a named role carry a fixed route in its own definition, the harness profile decides whether that definition or an explicit spawn value is authoritative. Follow that profile rather than attaching both and assuming they agree.
5. Complete child lane brief
Every delegated lane gets all of these fields:
Lane: [stable task name]
Role: [one of the routing contract's roles; plus a custom agent name where the harness has one]
Resolved route: [the exact values the harness profile gives that role]
Context scope: [self-contained | recent orchestration context | full inherited history]
Delegation: forbidden | [exact bounded grandchild authority]
Objective: [one verifiable outcome]
Why this route: [one sentence; mandatory for JUDGMENT+EXECUTION, DESIGN+INTEGRATION and SECURITY]
Prerequisites: [facts or lanes that must already be complete]
Owned files: [exact paths or directory globs]
Forbidden files/actions: [shared files, external state, commits, tracker writes]
Frozen decisions: [answers the worker must not reopen]
Allowed side effects: [normally local edits and focused validation only]
Acceptance check: [observable condition]
Validation: [targeted commands or evidence]
Retry budget: [number and evidence that would justify a retry]
Stop rule: [observable condition that completes or parks the lane]
Escalation evidence: [facts the root needs to resolve an uncovered decision]
Return exactly:
- status: complete | blocked | partial
- changed files or inspected scope
- validation and evidence
- proven facts
- unproven facts
- uncovered decisions or blocker
- recommended next action
Priority is not a dependency graph. State dependencies and permitted overlap explicitly. Do not spawn until the objective, exact scope, exclusions, ownership, acceptance and required output are all known.
Hoist the invariant fields into one shared block
Repeating fourteen near-identical field sets makes the goal long, hard to re-read after compaction, and easy to get wrong — the fields that go missing are always the same ones, because they are the boring ones: retry budget, stop rule, escalation evidence, required output.
Write a 5.0 lane contract immediately before the lane list, stating every field that is identical
across lanes, plus the sentence “these apply to all lanes below and are not repeated”. Each lane entry
then carries only what genuinely varies: role, resolved route, context scope, owned files, objective
and acceptance check. A lane needing a different value for a hoisted field overrides it in its own entry,
which makes the exception visible instead of hiding it in boilerplate.
Two definitions worth stating in that block rather than assuming:
- A retry is not a re-run. Retrying means acting on new evidence — a different error, a corrected assumption, a file not previously read. Re-issuing the same failing command unchanged is a loop, and it will consume a whole unattended night if nothing forbids it.
- Escalation evidence is not “it didn’t work.” It is the file and line, the command and its verbatim output, the frozen name the lane wanted and why, and what it would do given an answer. Unattended, there is nobody awake to ask the follow-up question, so the first report has to carry it.
6. Generic goal-file template
Delete empty sections and irrelevant examples. Do not keep headings that add no behaviour.
# [Project or programme] — [outcome], [date or run identifier]
This file is your goal. Re-read it in full after compaction; within one context, cite the section you
need rather than re-reading the file. It supersedes [older goal] where applicable; do not consult the
superseded file unless this goal explicitly points to it.
## 0. Run contract
- Run mode: daytime | unattended
- Human availability: available | unavailable for the whole run
- Terminal condition: stop after the listed lanes | continue into the fallback queue
- Current layer: research | design | implementation | review | live verification | deployment
- External-write authority: [exact scope]
- Harness: [name; its profile resolves every route in this goal]
- Root role / resolved route: [exact values]
- Launch rationale: [why the whole wave needs this route]
- Selected topology: solo | single auxiliary | campaign | campaign + security
- Topology rationale: [the independent bottleneck or risk that justifies this shape]
## 1. Outcome and success criteria
Outcome: [user-visible end state, not merely activity]
Success means:
- [measurable condition]
- [required validation and evidence]
- [required durable tracker or handoff state]
## 2. Independently verified starting state
Verified at [timestamp]. Do not re-derive unless a named check shows drift.
- repository heads and dependency pins;
- exact-SHA CI run IDs and conclusions;
- relevant live or deployed state;
- dirty worktrees and in-flight ownership;
- what changed underneath an existing session.
## 3. Authority and concurrency
- The root owns decisions, integration, external writes, commits, pushes, final gates and synthesis.
- Children do not commit, push or mutate external state unless a lane delegates that exact action.
- One file has one owner. Name integration files and resource mutexes.
- State which lanes may overlap and which must remain sequential.
- If nested delegation is allowed, start roughly two-thirds of the pool as direct children and reserve the rest.
## 4. Agent routing
[Paste the complete routing contract from this guide or an equivalent run-specific contract.]
## 5. Lanes
| Lane | Role | Route/context | Depends on | Owned files | May overlap | Acceptance |
|---|---|---|---|---|---|---|
| 1 | ... | ... | ... | ... | ... | ... |
[Give each lane a complete child lane brief.]
## 6. Applicable constraints and corrections
Include only invariants, disproved beliefs, false-pass traps and environment facts that can change
these lanes. Mark seductive disproved beliefs as: "X was WRONG; the verified truth is Y."
## 7. Testing and evidence
- Name where test-first is required and where validation replaces a test.
- Workers run focused checks. One named gate owner runs the integrated gate after wiring.
- Quote exact outputs, SHAs and CI run IDs. Separate source, CI, deployment and live proof.
- Never convert absence of evidence into a pass.
## 8. Blocking and stop rules
- State defaults for expected forks.
- Give every lane a retry budget, stop rule and required escalation evidence.
- An uncovered child decision returns to the root.
- In unattended mode the root records the blocker, parks the lane and moves on.
- State the terminal condition again and provide the ordered fallback queue if one exists.
## 9. Required final report
Writing this is the **last action of the run** (§10) — write it to `codex/report-[date]-[run-id].md`,
unprompted, without being asked, then reply with only its path and a short summary. The run is not
complete until the file exists.
For every lane: complete with evidence, blocked with the exact blocker, or partial with the precise
resume point. List commits, exact-SHA CI, external side effects, proven behaviour and unproven
behaviour. Cover the whole run rather than only its final phase.
State the section order and say the report is what the human reads *instead of* the transcript:
- headline — what is true now that was not true before, in three or four sentences;
- entry table — every entry by name with a status, the commit SHAs and one line of evidence, and the
expected row count stated so a short table is obvious;
- per-entry detail — acceptance check with its verbatim output, disposition record for any conditional,
and a precise resume boundary for anything parked;
- proven versus not proven, as two explicit lists, with skips reported separately from passes;
- integration — commits, final SHA, CI run ID and conclusion at that exact SHA;
- questions for the human — every decision the run had to take itself and every question the goal did
not cover (mandatory in front-loaded mode, §1);
- recommended next run, ordered.
7. Evidence and verification rules
Verified starting state is load-bearing. Reconcile drift-prone repository, tracker, CI and deployed
state immediately before launch. Say independently verified; do not re-derive, but give the agent a
named drift check where the state may change during the run.
Keep these proof layers distinct:
- a local focused check proves the worker’s changed area at the inspected tree;
- an integrated gate proves the composed repository state;
- cloud CI proves the exact pushed SHA only when the run ID and conclusion match that SHA;
- deployment or live evidence proves behaviour only in the environment actually observed.
Do not say CI is green merely because the latest run is green. Resolve the commit and verify the run against that exact SHA. Do not treat a process restart, cached output, existing fixture or unchanged input as proof of a mutation path. Name each run-specific false-pass route in the goal.
Testing has a job rather than a quota:
- Prefer a failing test first for bug fixes and for logic with real branching or contract risk.
- Validate rather than invent tests for documentation, declarative configuration, mechanical wiring and dependency metadata when a parser, linter, render or dry run is the better proof.
- Workers run focused checks covering their lane. One owner runs the proportionate integrated gate after integration. Do not make all children repeat an expensive gate against a changing tree.
- Run a complete repository gate for cross-cutting or high-risk changes, releases, explicit repository requirements or when the goal asks for it. State any skipped sub-gates and why.
- Never claim green without seeing the output. Once acceptance and the chosen gate pass, stop repeating unchanged checks.
Returned reports are claims, not proof. Verify load-bearing facts against source, git, CI, trackers and live systems. A missing child report is not proof of failed work either; inspect the expected artifact.
A reviewer reports findings and never implements its own corrections. Any implementation change after a REVIEW or SECURITY verdict invalidates that verdict, even when the fix appears mechanical. Re-run the relevant verification and obtain a fresh review against the corrected accumulated diff before using the earlier verdict as completion evidence.
Freeze external data contracts from the real artifact, before the wave
When lanes must parse, import or integrate an external format — a vendor export, a third-party API payload, a partner feed — walk a real instance of it and write the measured schema into the goal as a contract: every field, its type, its null count, the cardinality of anything enum-shaped, and the value ranges. Do this before the run, not inside a lane. Two reasons, both observed:
- A guessed schema compiles. A parser written against a plausible shape passes its own synthetic fixtures, imports the real file without crashing, and is silently wrong about the fields it never looked at. Nothing in the gate catches it.
- Vendor documentation contradicts vendor output. A published field reference has documented three fields that did not exist in the export it described, while omitting four that were present in every record. Where the documentation and the artifact disagree, the artifact wins, and the goal should say so by name so a lane does not “correct” working code to match a wrong document. Where the export ships its own description of itself — a manifest, a file-descriptions table, a checksum list — treat that file as an input rather than packaging, and expect its disagreements to be content: a dataset the vendor documents and did not send is a coverage fact, not a parse error. Say which reading applies, because the default handling turns the most user-relevant thing in the export — here is what they say they hold, here is what they actually gave you — into a logged warning.
State the traps separately from the schema, one per numbered item, each with the wrong-but-plausible handling it defeats. Anything encoded three different ways in one field, any sentinel value, any timestamp that means something other than what its name implies, and any field whose semantics differ from its obvious reading belongs there. These are exactly the items that pass a naive test.
Where an input may not have arrived by run time, give it a check-then-branch lane that cannot fail: if present, walk it and produce the schema for the next wave; if absent, report not started with the expected date. Never let a lane infer an absent format’s shape from a sibling’s.
A second sample of an external format is worth more than a bigger first one
When freezing a vendor format, prefer two instances from different circumstances over one large one. A second export from a different account, tenant, region or era costs nothing extra to walk and catches the class of bug volume never will.
Observed: two exports of the same platform, one with 5,266 comments and one with 24. The sparse one had fourteen files empty that were populated in the busy one. Every acceptance test written against the busy account would pass against the sparse account by skipping, and the parser would ship believing it handled the empty case. The 200× volume difference also proves nothing in the pipeline assumes a busy account — an assumption that is invisible until a real user with a quiet account imports one.
So: name both instances in the goal, require per-instance assertions rather than aggregates, and require the report to say which categories were empty in which. “It parsed both exports” is the claim that hides this.
8. Structural patterns and common failures
Patterns that work
-
Self-checking branches beat asserted readiness. Check the external prerequisite, then state both the ready and not-ready paths so the prompt stays valid if state changes before execution. This extends to a predecessor run: its deliverables are a prediction until it stops, because its own cut order may have fired. Check them by name, and name the shape you expect — a check for a seam that landed under a different name reports absent and the successor cheerfully builds a second one beside it.
-
One file has one owner. Give shared wiring and generated artifacts to the root or a dedicated integration lane. Fence exact files being edited elsewhere.
-
Tell a running agent what changed under it and whether anything is fenced. It should not spend a lane rediscovering an intentional edit or restoring a deliberate deletion.
-
Scale context at the spawn boundary. A fresh root gets the whole goal; a frozen child normally gets a complete self-contained lane brief and no inherited context; an existing root gets a short delta pointing to a new immutable goal.
-
Export resources behind tools the agent cannot access and reference the exported artifact by absolute path. Record decisions beside a generated snapshot rather than silently editing the snapshot.
-
State the cut order in advance. An unattended run cannot ask what to drop when it is running out of night, so name which lanes to park first and say that parking one cleanly beats half-building three.
-
Audit the available skills, tools and reference packs against the project’s actual dependency graph before the run, and name the ones not to use. See the failure table.
-
Record a temporary licence with the condition that ends it, in the same sentence. “This is free because X, and stops being free when Y” survives; a bare permission outlives its justification. Then check at the start of the next run whether the ending condition actually happened. A licence whose expiry was predicted but not reached is still live, and the next goal will confidently say otherwise: one wave wrote “the schema is free until W5 ships a build”, W5 never ran, and the following goal had to correct itself before it could freeze anything. If the condition fails to occur twice, the condition itself is wrong — it is a prediction dressed as a trigger. Restate it as something the next run can observe and check (“is a build installed?”), not something a previous run promised. If it fails a third time, stop predicting and ask the human for a cadence instead. One licence was predicted to end at wave 5, then at “when a build reaches a device”, then at “this is the last free one” — three waves, three misses, each goal opening with a correction to the last. The human’s answer when finally asked was a schedule: re-ask me every third wave. A cadence cannot be wrong about the future because it makes no claim about it, and it puts the decision back where it belongs.
-
A test suite that degrades to skips is unsafe for an unattended run, and reporting the skip is not enough. Reporting skips separately from passes (see the failure table) is the right rule for a run a human will read the same day. Overnight it is not: a suite that quietly reaches nothing still reports green, and the report saying so is read hours later, if at all. Before a long unattended run, remove the skip paths from the suites that run in it so an unreachable surface is a failure. Keep graceful skipping only where the missing input is genuinely expected and named.
-
A test target nothing in CI executes has only ever self-reported. When a wave creates a new suite, target or check, verify the pipeline actually runs it before treating its results as evidence. Observed: a whole UI test target was built, run locally, and reported green for two waves — CI ran four steps and none of them was that target, so every claim about it traced back to the agent’s own account of its own run. Creating the check and wiring the check are different pieces of work, and only the asked-for one gets done.
-
Put decisions and evidence in a durable source. Chat and memory are routing aids, not authoritative present-tense state.
-
Carry an authoritative copy of this sourcebook inside every repository driven this way, imported whole into the repository’s tracker docs with its source path and import date in a header. A repository that carries its own copy is complete: an agent given only the checkout — in CI, on another machine, or a year later — has the whole model without being told where a canonical file on somebody’s laptop lives. Decided 2026-08-14, reversing the previous rule that the sourcebook must exist in exactly one place outside every repository.
The price is a re-import discipline, and it is not optional: an edit to the canonical file is not finished until every consuming repository has been re-imported in the same change. That discipline exists because the failure it prevents was measured, not imagined — before the copies were tracked and dated, an in-repo copy was found 126 lines and one whole wave behind. Import it as a tracker document rather than a loose file at the repository root, so nothing resolves it by a cwd-relative read in preference to the canonical one; a tracker doc is reachable only by an explicit view.
Measure contention in files-per-new-thing before you fan out
Before a wave that adds N of something — sources, providers, adapters, tenants, endpoints — count how
many existing files adding one of them forces you to edit. Do it with rg, before writing the goal,
and put the number in it.
The number decides the shape of the entire next run. Observed: a product that had four data sources carried 21 exhaustive four-way switches across 13 files, plus two hardcoded source arrays. The next wave planned to add five sources concurrently. Under one-file-one-owner that is not a slow path, it is an impossible one — five lanes each needing the same 13 files either serialise into a queue or collide, and both fail quietly overnight.
This is the same contention as §4’s append-only registry, arriving from the opposite direction. A
registry is one file every lane must append to; an exhaustive switch over a closed enum is N files
every lane must edit. The counter is the same — collapse them to a registry the lanes append to — but
nothing surfaces it unless somebody counts, because each individual switch looks harmless.
Two things make the measurement honest:
- Count switches, not references. A file that merely mentions the type is fine. A file that enumerates every case breaks when a case is added. Only the second kind is contention.
- State the acceptance check as “where does an N+1th case force an edit”, not “does it compile”. A
switchrewritten as anif/elsechain or a call-site dictionary over the same four cases passes every test, compiles, reads as a refactor, and has changed nothing. It is one of the easiest false passes to ship because the diff looks like progress.
A second agent in the same repository is a concurrency problem, not a merge problem
When another agent, a human, or a scheduled job is working in the same checkout, the run must be told — and told in the launch message, not only the goal, because a dirty worktree is the first thing the agent sees and it will otherwise try to make sense of it.
Three specific hazards, none of which a merge strategy addresses:
git commit -aandgit add -Asweep the other party’s half-finished work into your commit. Say so by name and require explicit pathspecs. This is the one that actually loses work, because the other agent’s change lands in your history attributed to your wave and neither run notices.- Fenced files may still be changing during the run. A fence is normally static; here the file’s content at the end differs from its content at verification. So say the file is fenced and that its current content is not to be read for guidance — an agent that reads a half-migrated config as though it were the intended end state will faithfully build against it.
- “Assume it succeeded” must be explicit. Otherwise a conscientious run spends a lane validating work that is not its own and is not finished, and reports a failure that is simply someone else’s work-in-progress.
State what the concurrent work does and why it cannot collide — not merely that it exists. “It adds manual signing to the Release configuration; simulator tests build Debug” lets the run reason about the next surprise itself. A bare “don’t touch these files” does not.
A freeze protects a file from editing, not a contract from changing
“No lane edits this file — it is correct as it stands” is a statement about ownership, and it is routinely misread as a statement about correctness. Both can be true when written and only the first still true by the end of the run, because a different lane changed something the frozen file consumes.
Observed: a goal froze the insights engine as correct, while another lane in the same wave changed which enum case the largest data source emitted. Nothing edited the frozen file, every lane passed its acceptance check, the gate was green — and the engine silently stopped counting the biggest source in the product. The defect was found by reading the repository a wave later, not by anything in the run.
So when a wave changes a shared contract — an enum case, a field’s meaning, a unit, a nullability — enumerate that contract’s consumers in the goal and give each one an explicit disposition: in scope this wave, or re-validated and unaffected, here is the check. A consumer that is neither is how this fails. “Nobody edits it” is not a disposition.
An ownership map that omits a required file blocks the lane instead of protecting anything
The reciprocal of the freeze problem, and it costs whole entries rather than correctness. Ownership is normally written by listing the files each lane will touch and declaring everything else closed. That is safe for files nobody needs and quietly fatal for one somebody does.
Observed: a goal declared the package manifest “owned by nobody — if you believe you need it, park and say why”. A lane then had to add a dependency to a test target so it could import a module the same wave had just built, which is a manifest edit and nothing else. It parked, correctly and exactly as instructed. Four downstream entries were dependency-parked behind it and the run landed a third of its queue. Nothing was wrong with the lane, the rule, or the agent’s judgement — the ownership map was incomplete, and the rule faithfully enforced the gap.
Walk the real dependency graph before freezing ownership, not the list of files you expect to edit. Build manifests, registries, generated-artifact inputs and composition roots are the usual omissions, because they are edited rarely — and so are easy to forget — while being required by exactly the kind of work a wave does. Where you genuinely want a file closed, say who may open it and on what evidence: “closed; if a lane needs it, the root edits it on request” keeps the boundary and removes the deadlock. A boundary with no escape hatch is a stop condition wearing a safety label.
A read-only audit lane defers every finding it makes by a whole run
Making an audit lane read-only is the obvious way to stop it colliding with the lanes that own the files it inspects. It is also how a real, user-facing defect gets found on day one and fixed on day thirty.
Observed: an accessibility and layout audit found that the product’s main screen still advertised a single data source, on the exact run that had made the engine behind it multi-source. Every lane passed, the gate was green, the audit was excellent — and the defect shipped anyway, because the audit owned nothing and every view file belonged to somebody else. The finding sat in a markdown file until the following run.
Pick one deliberately, and write down which:
- Give the audit ownership of the surfaces it audits, and schedule the lanes that would otherwise own them around it. Best when the audit is the point of the run.
- Schedule it early and add a named follow-up lane that owns the fixes, with the audit’s output as its input. Costs a dependency; keeps the fix in the same run.
- Accept the latency, and say in the goal that findings land next run by design. Legitimate — but only if it is a choice, and only if the next goal actually picks them up.
The failure is not choosing. An audit whose findings have no owner is a document, not a lane.
Proven at scale is not shipped — put reachability in the acceptance check
A lane can prove an engine against a real artifact at full scale, commit it green, and leave it unreachable from the product. Observed: two parsers were tested against real multi-gigabyte archives, with exact record counts and second-import dedup proven, while the shipping app had exactly one entry point and it belonged to a third source. Three waves of cross-source features were built on top of data that no user could ever get into the app.
Correctness of the mechanism and reachability of the feature are different claims, and a goal that only asks for the first will get only the first. Where a lane builds something a user is meant to reach, make the acceptance check name the entry point — the route, the menu item, the command, the picker — and require evidence that it resolves. “It compiles and its tests pass” is not evidence that anyone can get to it.
Work parked between runs is preserved by patch identity, not by hope
Two different situations hand one run’s work to a later one, and both have a counter-intuitive rule.
A preserved stash is restored with apply, never pop. When a run parks validated work in a named
stash for a later run to land, the later run’s goal must say this outright. pop deletes the stash the
moment it succeeds, so any mistake afterwards — a bad merge, a wrong repair, a lane that overwrites a
file — has nothing left to fall back to, and in the case that produced this rule that was two runs of
security-reviewed work with no way to recover it. apply leaves it in place. Pair it with three more
rules in the goal:
- Recompute the patch id before applying and stop if it does not match the value frozen in the goal. Applying a stash you cannot identify is the silent-corruption path.
- Never drop, clear or branch it, even after the work has landed. Deleting it is the operator’s decision and it is not the run’s to make.
- On conflict, reset the tree rather than fighting the apply. The stash survives that; a half-resolved working tree does not.
A crashed run is not a lost run — sweep by patch identity, not by worktree count. One crashed main thread left 11 worktrees and 20 branches across three repositories, including a commit on a primary worktree that had never been pushed. It looked like carnage. Comparing
git show <commit> | git patch-id --stable
against the mainline proved every unique commit already had a byte-identical twin landed: nothing was lost and all of it was safe to delete. Run that sweep before believing either “we lost work” or “it’s all fine” — both conclusions are cheap to reach and expensive to get wrong, and the count of stray refs supports neither.
The same command is what makes a branch-and-worktree audit meaningful. “Redundant” and “unique” are claims about content, so require the evidence, not the adjective.
Failure and counter
| Failure | Counter |
|---|---|
| The root asks a question in unattended mode and idles | Pair never ask with record, park and move on plus an ordered fallback queue |
| Workers re-derive already established facts | Label timestamped state independently verified and provide only named drift checks |
| An attractive disproved belief returns | Preserve the wrong belief and correction together: X was WRONG; Y is verified |
| A check passes while proving nothing | Name the false-pass mechanism and the artifact or state transition that constitutes proof |
| A lane burns time on an unavailable external prerequisite | Add a check-then-branch route, retry budget and stop rule |
| An expensive judgement route becomes the default child | Require a written reason for JUDGMENT+EXECUTION, DESIGN+INTEGRATION and SECURITY, and reclassify resumed work once decisions are frozen |
| Every child receives the full root history | Use fresh, self-contained briefs; fork only the recent context the lane needs |
| Workers all run the same expensive gate | Workers run focused checks; one owner validates the integrated state once |
| Children collide on shared files or resources | Assign one owner per file and name integration files and mutexes before spawning |
| A report describes only the last phase | Require final synthesis covering every lane and every external side effect |
| A crashed run leaves many branches or worktrees | Prove dirty state, ancestry, unique commits and stable patch identity before cleanup |
| A generic template is mistaken for a product contract | Locate the authoritative product handoff and verify assumptions before implementation |
| Lanes queue behind one append-only registry function | Split it into one stub file per lane with a frozen call list and pre-assigned identifiers, in the pre-fan-out pass (§4) |
| A high-quality skill or reference pack is followed for the wrong stack | Audit the available packs against the real dependency graph and name the ones not to use, with the reason. An excellent skill for the persistence layer the project does not use produces confident, well-formed, irrelevant code that compiles as an example — the strongest false pass there is, because nothing about the output looks wrong |
| Widening a single-target API produces a second parallel implementation | Name it as a false-pass route. The parallel version passes every test and works; the cost lands later as two implementations of the same logic drifting. Require the existing entry point to be widened and its callers to pass a single-element collection |
| A temporary licence outlives its justification | Record the expiry with the permission: “free because there are no users; additive-only once a released build exists” |
| A worker retries by re-running the same failing command | Define a retry as acting on new evidence, and say that an unchanged re-run is a loop, not a retry (§5) |
| An unattended run runs short and half-builds several lanes | State the cut order in the goal and require parking with a resume boundary rather than partial delivery |
| A parser is proven against fixtures only | Require the acceptance check to run against a real artifact at real scale, and report the measurement as a number |
| A conditionally-dropped lane vanishes without evidence | Never write “drop this lane if X”. Use a check-then-branch lane that always runs and proves “already done”, require a disposition record for every conditional, and require the final report to name every lane with a status (§7) |
| A measurement is blocked by the UI automation surface rather than by the thing being measured | Measure the mechanism directly from a test harness. Driving a system picker, a login screen or a third-party surface is usually incidental to the number being sought; separate “does the flow work” from “how does the engine perform” and give each its own check |
| A frozen file silently stops being correct because another lane changed a contract it consumes | A freeze is about ownership, not correctness. When a wave changes a shared contract, enumerate its consumers and give each an explicit disposition — in scope, or re-validated with the check named (§8) |
| A component is proven against real data at real scale but no user can reach it | Put the entry point in the acceptance check. Correctness of the mechanism and reachability of the feature are different claims and only the asked-for one gets delivered (§8) |
| A test passes because its input was absent and it skipped | Report skips separately from passes, and state which inputs were present. An acceptance check whose evidence is “green” cannot distinguish proven from not-run |
| An optimisation target is met by changing how the thing is measured | Require the before and after to come from the same harness at the same scale, and say that a better number from a changed method is a false pass, not a result |
| A temporary licence is assumed to have expired on schedule | Re-check the ending condition at the start of the next run. The event that was supposed to end it may simply not have happened |
| An in-repo copy of the guidance has rotted behind the canonical file | Re-import every consuming repository in the same change as the edit to the canonical file. An edit that lands without its re-imports is half-finished; the copies were measured 126 lines and one whole wave behind before this was a discipline |
| A read-only audit finds a real defect it is forbidden to fix | Choose deliberately: give the audit ownership, add a named follow-up lane that owns the fixes, or state that findings land next run by design (§8) |
| Several lanes each need to write one results document | One owner, scheduled last, with declared dependencies on the lanes feeding it. Do not shred a document into per-lane stubs and do not merge it at integration (§4) |
| A new relational table is created and populated but nothing reads it | Make the acceptance check grep the query layer, not the schema. “The table exists and has rows” is not evidence the feature works |
| A test asserts on a value read from private data | Assert on counts, cardinality and ordering instead. A ranking test wants to name the top item; that is exactly the value that must not enter the repository |
| Adding the Nth instance of a thing requires editing N existing files | Count exhaustive switches over the closed enum with rg before writing the goal and put the number in it. Collapse them to a registry, and make the acceptance check “where does an N+1th case force an edit”, not “does it compile” (§8) |
| A collapse-the-switches refactor changes syntax and not coupling | An if/else chain or a call-site dictionary over the same cases is the same coupling. Require the count of files a new case touches to drop, and state the target number |
| Another agent is working in the same checkout and its work lands in your commit | Name the concurrent party and its files in the launch message, forbid git commit -a and git add -A by name, require explicit pathspecs, and say the fenced files’ current content must not be read as intent (§8) |
| A run replies with its report in chat instead of writing the file | Name the exact report path in the launch message as well as the goal. A goal that names only a structure gets a well-structured chat message and no file (§10) |
| A licence’s ending condition has now been mispredicted three times | Stop predicting and ask the human for a cadence. A schedule makes no claim about the future and cannot be wrong about it (§8) |
| A suite reaches nothing overnight and reports green | Reporting skips separately is enough for a run read the same day. For an unattended run, remove the skip paths so an unreachable surface fails (§8) |
| A new test target’s results are only ever the agent’s own account of them | Check that CI actually executes the target. Creating a check and wiring a check are different pieces of work (§8) |
| A goal carries a tracker reference that was true when it was written | Query every named item’s state, not its body, before copying it into a new goal. A closed item you think is open produces confident work on something already delivered, and nothing in the repository contradicts you (§2) |
| An identifier table assigns a route or key but no lane’s owned-files list contains the file implementing it | Cross-check the table against each lane’s ownership line. Every lane can pass, the gate can be green, and the feature can ship as a truthful “unavailable” page (§4) |
A run pops the stash it was told to preserve |
Say apply, never pop, in the goal, with the frozen patch id to re-verify before applying and an explicit ban on drop, clear and branch. pop deletes on success, so the next mistake has nothing to fall back to (§8) |
9. Unattended blocker and fallback contract
Include this only when the run is unattended:
## RULE ZERO — no human is available, so never wait
There is no human available for the entire run. Asking a question is equivalent to stopping.
- Take every explicit default in this goal.
- A child that finds an uncovered decision returns it to the root and stops that lane. It never asks
the user directly.
- The root resolves the decision from the goal or a named durable source if possible. Otherwise it
records the exact blocker, parks the lane and moves to another independent lane.
- Never use a question or input tool as a sleep or wait primitive.
- Follow the stated terminal condition. Enter the fallback queue only when explicitly told to.
- If the run makes an integrated state fail, follow the repository's recovery policy and do not leave
a knowingly broken state merely to keep the campaign moving.
An unattended fallback queue should contain useful, independently safe work, ordered in advance. It is not a licence to widen scope. Good candidates include reconciling durable tracker state, validating the premises of already-scoped backlog items, inspecting known TODOs in the authorised area, or improving the precise handoff for a parked lane.
10. The run-end protocol — the report is a terminal action, not a reply
The single most common reason a good run produces a bad handoff is that the report is specified as a format and never as a trigger. §6’s template says what the report must contain, and an agent will happily satisfy that specification if asked. Left alone it finishes the last lane, considers the work done, and stops — and the operator then has to ask “give me a full summary of the whole run including any decisions needed”, which is a question they should never have to type. Every goal must therefore say, in the run contract where it is read first and again in the report section, that emitting the report is the last unit of work.
With a tracker, the report narrows but the trigger does not weaken. Per-item outcomes belong in task state — landed with its SHA, parked with a concrete resume boundary, discovered work filed and labelled for triage — and the report keeps only what no single task can carry: cross-cutting findings, CI evidence with run and job IDs, deviations from the goal, and anything deliberately cut. It goes to the terminal rather than a file, and the binding rule is that nothing durable may live only in that message: if a finding matters it is already a task or a doc edit before the message is written. Do not let the narrowing become an excuse to skip it. Writing a coherent account of its own run is how an agent notices what no individual lane noticed — a stream of task updates does not force that reflection, and the questions worth asking at the end are what did this run learn that no single task captures, and what did I cut.
Put this block in every goal, adapted only in its paths:
## RUN-END PROTOCOL — the run is not over until the report exists
Producing the final report is the last task of this run, not a response to a request. Do not stop,
idle or report readiness on the grounds that the work is finished: the run is finished when the
report has been written. Nobody will ask you for it.
When the goal is complete — every lane in scope at its stop rule, and the session about to hand
control back to the operator — do this before you hand back:
1. Write the full report to `codex/report-<date>-<run-identifier>.md`.
2. Reply with the report's path and a summary no longer than a short paragraph.
3. Nothing else. Do NOT paste the report into the conversation.
Write it for a reader who has no memory of this run and cannot see the transcript. Never abbreviate
on the grounds that the operator watched it happen — they did not, and the transcript is discarded.
"As described above" and "as previously noted" are not permitted; restate the fact.
If the goal ends with lanes unfinished, the report still gets written, marked partial, with the
precise resume boundary for every unfinished lane. A partial report always beats no report.
The report is a file, not a chat message. The operator hands the next session a path instead of
pasting several hundred lines, the report survives the transcript being cleared or compacted, and it
sits in codex/ beside the goal that produced it. A chat dump of the same content is worse on every
axis, so the goal must say plainly not to produce one.
Name the exact path in the launch message too, not only in the goal. Observed: a goal carried this
whole section, and its launch message closed with “write the final report to the structure in section
12”. The run produced a long, complete, well-structured report — in chat, with no file. Naming a
structure asks for a shape; naming a path asks for an artefact, and the launch message is what the
agent is holding when it finishes the last lane. Say codex/report-<date>-<run-id>.md in both places,
and say in both that writing it is the run’s terminal action.
Two content rules that only exist because reports have got them wrong. Neither is obvious from the format alone:
- Enumerate external side effects by counting them, not by recalling them. A run that reports what it meant to create will silently omit what it actually created. Query the live state and report the count.
- Disclose every occurrence of a class of problem, not the notable one. A report that discloses one red integration run when six occurred is not lying, but the operator now believes something false about the run. State the full set and let its size speak.
11. Pre-flight checklist
- Run mode, human availability, current layer, external-write authority and terminal condition are explicit.
- The run contract names the harness, and the operator receives the root’s role and the exact route that harness’s profile resolves it to, with a one-sentence rationale.
- Tracker and live-state preflight happened before topology selection; the selected topology and its task-specific rationale are recorded before any spawn or mutation.
- The brief is an immutable goal file on disk and the launch message points to its absolute path.
- Outcome and measurable success criteria replace a mere activity list.
- Starting state, repository heads and relevant CI are re-verified now, at exact SHAs.
- The goal contains only constraints, corrections, traps and environment facts relevant to this run.
- A mid-run replacement says
do not pivot on receiptand states what changed underneath it. - Every lane names its role, its resolved route, its context scope, dependency, ownership, acceptance and output — a role name alone is not a route.
- Selected custom roles were preflighted for this task; pinned custom roles omit spawn model/effort overrides, while generic roles pass them explicitly.
- Spawn metadata confirms the selected role and every exposed route field; a missing, conflicting or substituted route stops the lane.
- A requested read-only sandbox is reported as enforced only when the observed sandbox and permission profile prove it; broader policy is handled and disclosed explicitly.
- Every JUDGMENT+EXECUTION, DESIGN+INTEGRATION and SECURITY child states why an EXECUTION lane cannot safely own the remaining work.
- Frozen lanes receive self-contained briefs rather than full root history by default.
- Every follow-up reclassifies the remaining work; settled design work moves to EXECUTION or RETRIEVAL.
- Root, child and optional grandchild authority are explicit; bounded workers do not commit.
- One file has one owner; integration files, gate owners and resource mutexes are named.
- Nested campaigns reserve part of the pool rather than saturating it at the root, and the reserve is sized against the harness’s real cap.
- Every lane has a retry budget, stop rule and escalation-evidence requirement.
- Rule Zero and a blocker path are present for unattended runs.
- Expected false-pass mechanisms are named and the required proof is observable.
- Out-of-band work uses check-then-branch rather than asserted readiness.
- Workers have focused validation and one owner has the integrated gate.
- Auxiliary work substitutes for root work rather than duplicating it; parent verification is proportionate and the integrated gate still has one owner.
- Required final reporting covers every lane, external side effect, proven fact and unproven fact.
- A fallback queue exists only when the terminal condition says to continue after listed lanes.
- Every append-only registry is split into per-lane stub files with pre-assigned identifiers.
- Invariant lane fields are hoisted into one shared contract block instead of repeated per lane.
- Every external data format a lane must parse is frozen from a real artifact, with its traps named.
- Available skills and reference packs are audited against the real stack, and the wrong ones named.
- Any temporary licence is recorded together with the condition that ends it.
- The cut order is stated, so a run that is short on time parks rather than half-builds.
- Lane and entry counts stated in prose match the actual lane list.
- No lane is conditionally dropped; conditionals are check-then-branch and emit a disposition record.
- The required final report names every lane with a status, and the goal states the expected count.
- Each required measurement is obtainable by the route the goal names, without a UI surface it cannot drive.
- In front-loaded mode, every fork was put to the human before the goal was written and the answers are frozen in it with a date.
- The goal says a decision the lane had to take itself is not a blocker, and names where such decisions get recorded.
- The required report has a dedicated questions-for-the-human section that may not be merged or omitted.
- Every shared contract this run changes has its consumers enumerated, each with an explicit disposition.
- Any lane building something a user must reach names the entry point in its acceptance check.
- Skips are required to be reported separately from passes, and inputs that were absent are named.
- Any optimisation target requires before and after from the same harness at the same scale.
- The run contract carries a run-end report line, and the goal states that writing the report is the run’s terminal action rather than a reply to a request.
- The report is written to
codex/as a file, and the goal says explicitly not to paste it into the conversation. - Goal, launch message and report all live in a
codex/directory that.gitignoreexcludes as a directory, not by filename pattern. - The goal requires a partial report, marked partial, if it ends with lanes unfinished.
- External side effects must be reported from a live count, not from what the run intended to create.
- Any recurring problem must be disclosed in full rather than by its most notable instance.
- Temporary licences granted by a previous run were re-checked against their actual ending condition, not their predicted one.
- Every repository driven this way carries an imported copy of this sourcebook in its tracker docs, and that copy matches the canonical file as of this run.
- Every audit or review lane’s findings have a named owner in this run, or the goal states they land next run by design.
- A correction after REVIEW or SECURITY invalidates the prior verdict and requires fresh verification and review.
- Any document several lanes feed has a single late owner with declared dependencies, not a merge at integration.
- A licence whose ending condition has failed to occur twice is restated as an observable check, not a predicted event.
- New storage this run adds has an acceptance check proving something reads it, not only that it was written.
- Contention was measured in files-per-new-thing with
rgbefore fan-out, and the number is in the goal. - A collapse-the-switches refactor states the target file count for an N+1th case, not merely that it compiles.
- Any concurrent agent, human or job in the same checkout is named in the launch message with its files fenced, and
git commit -a/git add -Aare forbidden by name. - The exact report path appears in the launch message as well as the goal.
- A licence mispredicted three times is replaced by a human-supplied cadence rather than a fourth prediction.
- Suites running unattended have their skip paths removed, so an unreachable surface fails rather than reporting green.
- Every test target or check a wave creates is verified to be executed by CI, not only by the agent that built it.
- Every external format is frozen from at least two instances where they exist, with per-instance assertions and empty categories named.
- A source that is really many datasets gets a declarative descriptor seam before fan-out, so a lane contributes rows rather than parsing code.
- The goal says to re-read in full after compaction and to cite a section within one context, not to re-read the file per lane.
- Every spawn is justified by how much of its output is discarded, not only by the cheapest route that satisfies it.
Appendix A — Codex profile
Complete. Everything the body defers to a profile is resolved here for Codex.
Root role → launch model and effort
Launch model: gpt-5.6-sol
Launch effort: high
Why: this wave coordinates independent lanes, owns integration and may encounter uncovered seams.
| Root role (§1) | Launch model | Effort |
|---|---|---|
| DESIGN+INTEGRATION, standard depth | gpt-5.6-sol |
high |
| DESIGN+INTEGRATION, raised depth | gpt-5.6-sol |
high |
| SECURITY, raised depth | gpt-5.6-sol |
high; xhigh only for exceptional risk or ambiguity |
| EXECUTION, standard depth | gpt-5.6-sol |
high, with Luna execution lanes |
| JUDGMENT+EXECUTION, raised depth | gpt-5.6-sol |
high, with Terra judgement lanes |
| EXECUTION with RETRIEVAL lanes | gpt-5.6-sol |
high, with Luna retrieval lanes |
Keep the campaign root on Sol/high for architecture, conflict resolution, verification and acceptance. Luna/max is the normal fully specified implementation leaf, not a campaign root. If the packet needs material local judgement, context or risk control, route it to JUDGMENT+EXECUTION on Terra/high instead of asking Luna to redesign the packet.
Role → model and effort
| Role | Route |
|---|---|
| RETRIEVAL | gpt-5.6-luna, low |
| MAPPING | gpt-5.6-luna, medium |
| GATE | gpt-5.6-terra, low |
| EXECUTION | gpt-5.6-luna, max |
| JUDGMENT+EXECUTION | gpt-5.6-terra, high |
| REVIEW | gpt-5.6-terra, high |
| DESIGN+INTEGRATION | gpt-5.6-sol, high |
| SECURITY | gpt-5.6-sol, high or xhigh |
The §4 narrow-role table resolves the same way: Mapper → Luna/medium, Lane worker → Luna/max, Complex lane worker → Terra/high, Reviewer → Terra/high, Security reviewer → Sol/high, Gate runner → Terra/low, Worktree auditor → Terra/high.
Spawn resolution and task-scoped runtime preflight
Codex custom-agent files are configuration layers. When a selected custom role pins model or
model_reasoning_effort, that file wins: spawn by agent_type, pass the required fork_turns, and
do not attach redundant model or effort overrides. For a built-in or generic role without pins,
pass the resolved model, reasoning effort and fork_turns explicitly.
Preflight only the roles selected by this task. Confirm the effective multi-agent feature, the named agent definition where one is used, and the requested model and effort before dispatch. After spawn, inspect the public role/model/effort metadata the client exposes. A missing, conflicting or silently substituted route is a hard stop for that lane; never accept a fallback and report the requested route as though it ran.
A custom agent’s requested read-only sandbox is not proof of enforced isolation: live parent
permission overrides may broaden it. Record the observed sandbox and permission profile when the
client exposes them. Under a broader policy, continue a behaviorally read-only review only when hard
isolation is not required and the parent captures exact before/after repository and artifact state;
report the broader policy as residual risk. When hard isolation is required, use a separately
constrained session. This does not require tightening the normal campaign profile.
Context scope → fork_turns
| Context scope (§3) | Codex spawn |
|---|---|
| self-contained | fork_turns="none" plus the complete lane brief |
| recent orchestration context | a small positive fork_turns, only where those decisions bear on the lane |
| full inherited history | full history, only where the child genuinely needs it |
REVIEW, SECURITY and fully specified EXECUTION workers use fork_turns="none" by default and receive
the complete evidence or implementation packet explicitly. JUDGMENT+EXECUTION receives recent context
only when the relevant decisions cannot be stated safely in its brief. Full inherited history is
reserved for the rare child that genuinely needs the root’s whole decision trail.
A follow-up continues on the thread’s existing model and effort. Reclassify the remaining work before every follow-up: when a design or judgement thread has reduced the work to bounded implementation, start a fresh Luna/max EXECUTION lane rather than continuing on the more expensive route.
Concurrency and depth
max_concurrent_threads_per_session = 10 excludes the primary thread, so the pool is the root plus
ten simultaneous child threads.
Flat, non-delegating fan-out may use all ten child slots. In a nested campaign the root starts at most six direct children and reserves four child slots for grandchildren, replacement lanes and urgent investigation. That is the concrete form of §4’s two-thirds rule for the current profiles.
max_depth is not a documented public safety boundary. Every lane therefore defaults to
Delegation: forbidden, and a child may delegate only when its brief grants exact authority. Luna is
a leaf and never delegates. Do not claim a configured depth limit enforced this contract.
Appendix B — Claude Code profile
Routing is deliberately not restated here. A Claude Code session already loads
~/.claude-personal/rules/operating-model.md § “Model routing for sub-agents” and
~/.claude-personal/rules/subagent-dispatch.md on every request, before anything asks for this
document. Those two own the routing test, the model tiers and the dispatch mechanics, and they win
on any conflict with this appendix — a second copy of a contract that is already always-loaded is
the drift hazard, not the safety net. Read them first; this appendix carries only what they do not:
how the body’s roles map onto them, and the ways Claude Code’s dispatch surface differs in kind
from Appendix A.
Role → route
The routing test is operating-model.md’s, not a new one: can you state the acceptance check now?
Yes → Sonnet. No — the lane must decide what “done” means, or the deliverable is a judgement → Opus,
or do not delegate at all. Cross-check on blast radius: wrong-and-cheap-to-detect → Sonnet;
wrong-and-silently-propagating (a frozen seam, a data model, a cardinality or PII call) → Opus.
| Role | Route | Note |
|---|---|---|
| RETRIEVAL | Haiku, or Sonnet | Haiku only for single-fact lookups whose answer is self-evidently right or wrong. Never where you would have to trust it finished — it drops steps in long tool loops, so a partial sweep returns looking complete. Completeness matters → Sonnet. |
| MAPPING | Sonnet | The Explore agent type is purpose-built for read-only fan-out search. |
| GATE | Sonnet, low effort | Run the gate, report failures verbatim, repair nothing. |
| EXECUTION | Sonnet | The normal parallel-build lane, once its seams are frozen. |
| JUDGMENT+EXECUTION | Opus | Context-heavy or wider-risk implementation where the acceptance check is known but the implementation still carries material judgement. |
| REVIEW | Sonnet, or Opus | Sonnet for spec conformance against a written contract; Opus where the review is the judgement. |
| DESIGN+INTEGRATION | Opus | Normally the root keeps this rather than delegating it. |
| SECURITY | Opus, raised effort | Never delegated to a cheaper tier to save a round trip. |
Six structural differences from Appendix A
These are not naming differences. A lane written against Appendix A’s mechanics and run on Claude Code fails in ways its own acceptance check will not catch.
-
There is no
fork_turns, and the middle option does not exist. Context scope is binary:subagent_type: "fork"inherits the whole conversation, anything else starts fresh. There is no “last N turns”. A lane needing partial context gets a fresh agent and the relevant facts written into its brief — which is what §3 prefers anyway. A fork also always runs on the parent’s model; amodeloverride on a fork is ignored.A fork is therefore the most expensive spawn shape available: it copies the entire parent conversation into a second context that then re-sends all of it on every one of its own turns, at the parent’s model, with no way to route it cheaper. Reach for it only when the lane genuinely needs the conversation itself; a self-contained brief is both cheaper and the default §3 asks for.
-
effortis not a parameter on theAgenttool. It is settable only in an agent definition’s frontmatter or inWorkflow’sagent()opts. A lane brief that specifies an effort through a plain dispatch is a silent no-op — the lane runs at the session’s effort and nothing reports the discrepancy. Where a lane genuinely needs a different effort, it needs an agent definition or aWorkflow, not a sentence in the brief. -
There is no ten-thread pool.
Workflowcaps concurrent agents atmin(16, CPUs − 2)and queues the excess rather than refusing it, so saturation is invisible; theAgenttool has no documented cap at all. §4’s reserve is therefore a ratio here and not a count, and “we did not hit the cap” is not evidence the fan-out was sized correctly. -
Delegation depth is enforced differently at each surface.
Workflowforbids nesting outright — aworkflow()call inside a child throws. Agent-spawned subagents can spawn further, soDelegation: forbiddenin a lane brief is a real instruction there, not a restatement of a platform limit. -
Naming a background agent swallows its deliverable. Passing
name:promotes a one-shot subagent into a persistent addressable teammate; teammates go idle awaiting messages instead of completing, so no completion event fires and the final message never reaches the dispatcher — only an idle notification. Re-asking by message produces another idle ping. This has no Codex analogue, and it directly breaks §5’s “Return exactly:” contract and §10’s report chain. Dispatch unnamed, or synchronously, or hand off through a file at an absolute path.subagent-dispatch.mdowns this, including the A/B experiment that isolated it. -
A lane cannot clear a permission block the root could have cleared. Subagents inherit the parent’s permission mode and cannot opt out, while a soft block clears only on the user’s own message naming the action — and a subagent’s transcript contains no user message. A dispatch brief is explicitly refused as consent. So a lane can be blocked on work the root was allowed to do, with nothing able to unblock it, and re-dispatching is treated as bad faith. Lanes do read-only investigation, code edits, tests and inventory sweeps; SSH, deploys, tenant or cloud mutations, secret-store writes and destructive git stay on the root. If a lane returns blocked, the root runs that step itself.
Workflow is a second orchestration mode Codex has no analogue for
Where the fan-out shape is known before the run — the lanes, their dependencies, what verifies what —
Workflow expresses the topology as a deterministic script (pipeline() without barriers,
parallel() where a barrier is genuinely needed, per-agent schema for structured returns) instead
of trusting a prompted root to hold it across a multi-hour campaign. It is also the only surface
where per-lane effort and worktree isolation are settable.
This does not replace the goal file. The goal still carries the run contract, ownership, frozen decisions, traps and the run-end protocol; the script carries only the topology. Use it when the shape is frozen, and a prompted root when the wave must still discover its own shape — which is the same DESIGN+INTEGRATION-versus-EXECUTION question §1 already asks about the root.