Orchestrating Parallel Agents: A Discipline
You plan, adjudicate, and verify; the sub-agents write the code.
This is not “throwing work over the wall.” It is a discipline. Here is the whole method.
When to use it, when not to
Use it when you need several tracks moving at once, or need to get through a long plan in bounded time.
Don’t use it for a single small change, for a design phase that needs tight back-and-forth with a human, or for anything you could finish yourself in two minutes.
Dispatching has fixed overhead — writing the task letter, collecting the work, verifying it. On small tasks the orchestration cost exceeds the time it saves.
1. Partition territory by shared runtime state, not by file path
The most common mistake is reasoning “these files don’t overlap, so they can run in parallel.”
That is wrong. What determines whether two tasks can run in parallel is not file overlap — it’s whether they share an exclusive resource:
- The dev server port — give it only to the track that needs end-to-end verification; every other task letter must say “do not start it and do not kill it”
- The database file and migrations — only one track may touch the data layer at a time
- Dependency installation and the lockfile — the orchestrator pre-creates package skeletons and installs up front; executors are flatly forbidden from installing anything
- The version control index lock — if you hit it, wait a few seconds and retry. Never delete it. Deleting a lock someone else is holding corrupts repository state
- The index itself — see below
Beyond that: test fixtures shared by multiple implementations, root configuration, and shared constants must be changed by the orchestrator only, with each executor using its own fixtures. “Only change your own directory” is not enough — shared files have to be named explicitly.
Narrow the commit scope with a pathspec
An executor stages its files, then gets interrupted before committing. You then stage your own files and commit — and the commit takes the entire index, wrapping someone else’s half-finished work into a message that has nothing to do with it.
The fix is to specify commit scope as a pathspec, rather than “stage everything, then commit.”
Note that a pathspec only matches tracked files. New files must be staged first — and at that moment the index may already contain someone else’s work, so immediately after staging, commit with those same paths to narrow the scope back down.
Put this in every task letter.
2. The fixed loop for each round
- Collect leftovers — check working tree state first. If there are changes, run the tests; if green, commit by path. Never commit the whole tree.
- Verify state — typecheck plus tests; at key milestones, bring the service up and walk it end to end
- Push — push once you have enough; don’t hoard commits
- Dispatch the next batch — follow the plan order, one small batch of one chain at a time. If it gets killed, you lose less
- Adjudicate — design gaps reported by executors: decide on the spot where you can, and write the decision down
- Update the report — maintain a single human-facing entry document
Do not run full verification while agents are in flight. Their files are half-finished, so the typecheck will necessarily be red. You will misread work-in-progress as a regression and waste a round. Either run something scoped, or wait until they land.
3. How to write the task letter
Four things, none optional:
Territory — state exactly which directory may be changed, and name every directory that may not be touched. “Don’t touch anyone else’s stuff” is useless — the executor doesn’t know who else exists.
Why it’s being done this way — give the reasoning, not just the instruction. An executor that has the reason knows where the boundary is, and can make a sensible call in a situation you didn’t anticipate.
How it will be accepted — which command to run, and what counts as passing.
A discipline block — a fixed template, attached to every letter.
Two that are easy to miss
A scoped test run must also require a typecheck. The test runner does not check types. Asking an executor for only a scoped test run removes the type layer for the entire parallel window — you end up with all tests green and the typechecker reporting a pile of errors.
Require “commit after each small step.” Background tasks get killed. An executor that hoards work and gets killed delivers nothing.
4. What executors must escalate instead of deciding
- Product semantics
- Scope changes
- Introducing a new dependency
- Irreversible actions
- Two executors in conflict with evidence on both sides
- An error in the design document itself
- Measurements that contradict an established conclusion
State it plainly in the letter: do not decide this yourself; write it in the report and let the orchestrator adjudicate.
A good executor will comply — and its diagnosis of its own patch is often better than yours.
Also: never mock out production code to get past a blocker. If a probe fails to import a production module, the right move is to stop and report — not to mock a fake implementation inside the probe. What you measure then is not the real system.
The most valuable report is “your judgment is wrong”
Write this into the dispatch explicitly:
If measurement shows the root cause or model I gave you doesn’t hold, report that honestly with the data. Do not force-fit my model.
Without that sentence, the executor assumes your model is correct and then finds a way to make the data agree with it. What you get back is a beautiful conclusion that successfully validated a wrong model.
An executor that stops and says “you’re wrong” is worth far more than one that produces a good-looking number.
5. Acceptance discipline: green is not evidence
This is the most valuable part of the whole set.
Because you no longer run every step yourself and can only read reports, the risk that “looks right” and “is right” produce identical output is amplified.
One: performance numbers must come with evidence that something is actually changing. A speed figure on its own is not accepted; it needs independent corroboration that varies with load. And when that evidence doesn’t hold, make the script throw rather than write the result anyway.
Two: the instrument must be able to vouch for itself. Measurement has to be able to distinguish a real zero from a zero caused by a stale selector. Optimization changes the structure of the system, and an instrument that isn’t updated alongside it starts lying — quietly.
Three: end-to-end is not optional. Unit tests are green by nature in a clean environment; they cannot catch what only surfaces in a particular runtime.
Four: a false pass is far more dangerous than an error. An error at least gets seen; an assertion that is always true stays green until someone happens to notice it never verified anything. So whenever you assert that something does not exist, first confirm that in the current environment it should exist — otherwise you are testing the environment, not the code.
Five: after fixing, verify in reverse. Deliberately break the production code, confirm the test actually goes red, then put it back. A test you cannot show still catches bugs is not a test.
A guard that goes red at random is not a guard
Assertions with a wall-clock threshold jitter in a parallel suite. Even with generous headroom, a full run will fail intermittently.
The harm is not “you occasionally have to re-run.” It is that it trains everyone to ignore that red light. The day it goes red for a real regression, nobody will take it seriously. And if it also gates deployment, it is blocking deploys at random.
The fix is not to raise the threshold — that just defers the jitter to the next time the machine is busy. The fix is to measure work, not time: iteration counts, comparison counts, output counts, total request volume. Those are deterministic, catch algorithmic regressions just as well, and don’t care how loaded the machine is.
Before measuring, confirm you’re measuring your own code
If a metric is dominated by third-party latency, it cannot see your implementation at all, and the before/after difference is just noise.
Measure the infrastructure’s own cost separately before drawing conclusions — otherwise you’re optimizing someone else’s server.
Add machine gates, don’t rely on remembering
For example: have the build compare “test files present on disk” against “the set the test framework actually collected,” and fail by name on any mismatch.
But a gate you have never seen fail correctly doesn’t count — after adding it, verify it in reverse once.
6. Attitude when collecting the work
Don’t trust the report alone. Re-run the conclusions that matter.
Factual claims about shared code must be checked yourself. An executor’s account of its own situation is credible; its claims about someone else’s code are not — it can’t see the whole picture.
When you find an error in a report, correct it on the spot and leave a trace in the document. Never fix it silently.
7. When dispatch gets killed
Background channels get killed in batches, sometimes taking the dev server with them. This is normal, not a failure.
- Don’t panic-redispatch — collect leftovers first, run the tests, commit by path if green
- Retry once — this pattern usually recovers on its own; don’t fall back to manual too early
- Only go manual after several consecutive failures — write the task as a self-contained letter into the executor’s inbox, and hand over commands the human can run in their own terminal
- If the service was killed, bring it back immediately — someone may be watching it
As long as you collect leftovers every round, no work is lost.
8. Write adjudications into documents, not just into chat
When an executor reports a design gap and you rule on it, write the ruling into the design document along with the reasoning.
The reason is blunt: the next agent cannot read your conversation. It can only read the document.
And when you rule, “why we didn’t do it that way” is more useful than “what we did” — the latter is visible in the code; the former, if unwritten, is gone in six months.
Closing
The whole discipline compresses to three lines:
Partition by runtime state, not by files.
An executor’s most valuable output is “you’re wrong,” not “done.”
Green is not evidence.