Course · Codex · Subagents
Light orchestration - gate every hand-off and merge worktree branches sequentially
You now have several workers grinding through the money refactor, each walled off in its own worktree, each producing a branch - money/parse, money/store, money/report. The fan-out got the work done in parallel. What it didn’t do is bring it back together, and the last two lessons both flagged a debt still sitting unpaid: fan-out doesn’t divide your wall-clock time by the number of workers, because the merge tail runs one at a time no matter how wide the fan-out was. This lesson pays that debt with real turns, not another claim. Orchestration here is not a workflow engine, it’s three habits - gate every hand-off, decide the merge before you scatter, and bring the branches home one at a time through a gate. Get those right and the parallelism you built pays off without a half-finished branch quietly poisoning main.
Orchestration is the aggregation step, made real
Section titled “Orchestration is the aggregation step, made real”Back in the fan-out lesson the rule was: write the aggregation first - be able to say in one sentence how the slices come back together before you spawn anything. Orchestration is that sentence turned into an actual procedure. For the recategorisation it was cheap - “concatenate the change-logs, sum the per-category counts” - because the workers only produced data. The money refactor is harder, because the workers produced code, and code doesn’t reduce by concatenation. It reduces by merge, and merges can conflict, and a passing test on one branch tells you nothing about the other three. So the aggregation step grows teeth: a review gate on each branch and a sequential merge, not a free-for-all.
You can run this orchestration from your main Codex thread - it stays the coordinator, dispatching workers and reading back their summaries - or you can describe the whole reduce step to the agent and have it drive. As with spawning in the delegate lesson, the mechanism is prompt-driven: you describe the procedure in prose and Codex coordinates the workers. The exact flags it exposes for this shift between releases, so pin specifics to the official Codex reference rather than scripting a flag you haven’t confirmed. The plain git commands below are the durable, verifiable part; lean on those.
Gate each hand-off with a read-only reviewer
Section titled “Gate each hand-off with a read-only reviewer”The cheapest way for parallel work to go wrong is for a worker to think it’s done when it isn’t - tests green on a path it didn’t exercise, an amount left as a float in a corner it didn’t touch. The fix is a separate set of eyes before any branch is allowed to merge: a read-only reviewer.
This is where the approvals & sandbox two-axis model earns its keep one more time. A reviewer is a subagent run under --sandbox read-only - it can read every file on the branch and run the tests, but it cannot edit, cannot push, cannot touch the network. Its only output is a verdict:
> Review the money/parse branch as read-only. Confirm every amount is parsed to integer cents per AGENTS.md, that no float path survived, and that the money tests pass. Return PASS or FAIL with the specific files and line ranges behind a FAIL. Do not edit anything.Two things make this a gate and not a rubber stamp. First, the reviewer is a different context from the worker that wrote the code - it didn’t talk itself into the change, so it’s not invested in it passing. Second, it’s read-only by construction, so a reviewer can never “helpfully” fix what it found and merge its own opinion in; a FAIL goes back to the writing worker, the gate stays a gate. You run one reviewer per branch, and a branch that comes back FAIL does not advance to the merge - it goes back for another pass first.
Every branch pays for one review turn before it’s allowed anywhere near main, and that turn doesn’t run in parallel with the other branches’ reviews either - one reviewer, one verdict, one branch at a time.
Merge sequentially, and price the tail you were promised
Section titled “Merge sequentially, and price the tail you were promised”It’s tempting, once three branches all come back PASS, to merge all three at once. Don’t. Parallel editing was safe because worktrees isolated the files; parallel merging is not, because every merge after the first lands on a main the previous merge already changed. Bring them home one at a time, and re-test between each - a sequential gated merge:
# start cleangit switch maingit pull --ff-only
# branch 1: merge, then prove main still passes before going ongit merge --no-ff money/parsepytest # the gate - green or you stop here and fix
# only if that gate passed, branch 2git merge --no-ff money/storepytest
# and finally branch 3git merge --no-ff money/reportpytestNow count what that actually cost, in the same turns instrument this course has been keeping since chapter 1. Three branches, one review-gate turn each, one merge-and-test turn each: six turns, run strictly one after another, because git only merges into one main at a time and the pytest gate has to see each merge in isolation to tell you which one broke something. That’s the number the fan-out lesson told you to remember. Fan out to three workers instead of one and the reading and classifying front genuinely runs in parallel - that part of the win is real. This six-turn tail does not shrink to match it, and it never will: it scales with how many branches you have to bring home, not with how many workers produced them. Widen the fan-out to six workers on six slices next time and the parallel front gets no slower - but the tail doubles, to twelve turns, because there are now twelve gates to clear one at a time.
Fan-out buys you parallel work. It never buys you a parallel merge.
The pytest line between each merge is the whole point beyond the turn count, too. Branch two might pass in isolation and still break once branch one’s changes are underneath it - two independently-correct money refactors can disagree about a shared helper. Testing after every merge means that when something breaks, you know exactly which merge broke it, because only one thing changed. Merge all three at once and a red suite tells you only that one of three is wrong, which is the diagnostic position you went to all this trouble to avoid.
If a merge conflicts, that’s not a failure of the orchestration - it’s the orchestration doing its job, surfacing the one place two slices genuinely overlapped. Resolve it in your main thread (or hand the conflict to a worker with the conflicting hunks as its brief), re-run the gate, then continue down the list.
Clean up, then step back
Section titled “Clean up, then step back”Once a branch is merged and the suite is green, its worktree has done its job. Tear it down the way the worktrees lesson set up - git worktree remove per merged slice, git worktree prune to clear any registrations whose directories are gone - so the next fan-out starts from a clean board rather than inheriting a yard full of stale trees.
Step back and look at the shape you just ran, because it’s the same shape every time, regardless of the task:
- Decide the reduce step first - how do the branches come back together, in one sentence.
- Fan out independent, heavy slices into isolated worktrees.
- Gate each hand-off with a read-only reviewer that returns a verdict, not an edit.
- Merge sequentially, re-testing between each, so a break names its own cause and the tail’s cost stays honest.
- Tear down the worktrees so the next run starts clean.
That’s light orchestration: no engine, no framework, just a coordinator thread, a review gate, and git used carefully. It scales from three branches to thirty without changing shape - only the queue, and the turns it costs, get longer.
The chapter’s row in the ledger
Section titled “The chapter’s row in the ledger”Score the whole chapter the way every chapter since the first change has been scored - tokens, turns, blast radius, recurrence. Here are the first two rows again, abbreviated, with this chapter’s row added:
| Move | Tokens | Turns | Blast radius | Recurs? |
|---|---|---|---|---|
| Read-only question | ~3,900 est. | 1 | None | No |
| Fix the CSV date bug | ~2,100 est. (csv.py alone) | 4 | Money-adjacent, briefly drifted into the amount field | Partly - the fix holds, the habit doesn’t |
| Delegate + fan out the recategorisation; worktree + gate the money refactor | ~900 est. returned to parent (3 subagent summaries at ~300 each); 58,860 spent reading, isolated per worker, never enters this window | ~9 (2 to delegate and receive the recategorisation; 1 to fan out and dispatch the refactor; 6 sequential review-and-merge turns to bring the three branches home) | Money-handling code across src/budgetcli/importers/csv.py, src/budgetcli/models.py, and src/budgetcli/categorise/rules.py, edited by three parallel workers at once - walled off in separate worktrees, then merged one at a time through a review gate before any of it reaches main | Partly - the recategorised data and the merged code hold, because they’re committed. The procedure that produced them - scoping a reviewer, gating each merge by hand - isn’t written down anywhere yet, so you re-drive every step of it the next time budgetcli needs a parallel refactor |
That last cell splits the same way the first chapter’s did: the artifact recurs cleanly, because git doesn’t forget a commit. The procedure does not, because nothing about “delegate this, fan that out, gate the merge, tear down the trees” lives anywhere Codex reads by default. You just ran five deliberate steps by hand, and you’ll narrate the same five steps again the next time this repo needs a parallel refactor, and the time after that.
A procedure you repeat with the same steps every time is exactly the thing you should stop re-explaining and start packaging.
And the running scenario has a procedure begging for exactly that treatment: every bank’s CSV export is shaped differently, and onboarding a new one is the same sequence of moves each time - read the format, extend the importer, add the test, verify the money rule held. That’s the next chapter - Skills: turning a procedure you keep narrating into one the agent invokes itself.