Document · doc-0001

Agent fan-out protocol (canonical)

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:

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:

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:

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.

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:

  1. Creates one empty stub file per lane, each exporting a single registration function whose name is frozen in the goal.
  2. Reduces the shared file to a call list invoking those functions in a frozen order, and never edits it again.
  3. 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

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:


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:

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:

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:

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

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:

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:

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:

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:

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:


11. Pre-flight checklist


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.

  1. 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; a model override 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.

  2. effort is not a parameter on the Agent tool. It is settable only in an agent definition’s frontmatter or in Workflow’s agent() 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 a Workflow, not a sentence in the brief.

  3. There is no ten-thread pool. Workflow caps concurrent agents at min(16, CPUs − 2) and queues the excess rather than refusing it, so saturation is invisible; the Agent tool 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.

  4. Delegation depth is enforced differently at each surface. Workflow forbids nesting outright — a workflow() call inside a child throws. Agent-spawned subagents can spawn further, so Delegation: forbidden in a lane brief is a real instruction there, not a restatement of a platform limit.

  5. 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.md owns this, including the A/B experiment that isolated it.

  6. 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.

View the source file on GitHub