~/thoughts

Failure modes in multi-agent orchestration

  • agents
  • engineering

Running several coding agents against one problem sounds like a throughput trick: more workers, more output. In practice the throughput is the least interesting part. You buy independent judgment. You only get it if you design for it deliberately.

Here's what I've found holds up.

Isolate each agent's context

The instinct is to give every agent everything: the full history, prior task summaries, the accumulated state of the project. It feels generous. It's the single most reliable way to degrade the output.

An agent handed 40,000 characters of "here is what happened in tasks 1 through 6" spends its attention reconstructing your session instead of solving its task. Worse, it inherits your conclusions, including the wrong ones. The value of a second opinion collapses the moment the second agent has read the first one's reasoning.

What a fresh agent needs is small and specific:

  • the task, stated completely, with exact values rather than descriptions
  • the interfaces it touches, with real signatures
  • the constraints that bind it
  • nothing else

Hand artifacts over as files, not as pasted text. A diff written to disk and referenced by path costs one line of context; the same diff pasted inline stays resident for the rest of the session and is re-read on every turn.

Separate the writer from the reviewer

The most valuable structural choice is also the simplest: whoever wrote the code doesn't get to approve it.

Self-review by the implementing agent catches typos and little else. It can't catch the class of error that matters: the misreading of the requirement, the test that passes for the wrong reason, the assumption baked in so early it never surfaced as a decision. Those need someone who did not make the assumption.

A concrete example from a recent build. A test asserted that a value equalled 'tube'. It passed on every run. It had been passing since the day it was written. Nothing in the test ever recomputed the value it was checking. The assertion compared a constant to itself. The agent that wrote it had no reason to look twice; a reviewer reading it cold spotted it in one pass.

Adversarial verification beats consensus

If you ask three agents "is this finding real?", you'll get three yeses. They're agreeable by construction, and a plausible-sounding claim clears a low bar.

Ask them to refute it instead. Prompt each one to argue the finding is wrong, and default to refuted when uncertain. Keep the finding only if a majority fails to knock it down. The asymmetry matters: it costs you a few true findings and saves you from a long tail of confident nonsense.

Better still, give each verifier a different lens: correctness, security, does-it-actually-reproduce. Three identical skeptics are one skeptic with error bars. Three different ones catch different failure modes.

Pipeline, do not barrier

The default shape people reach for is: run every agent in stage one, wait for all of them, then run stage two. That barrier is usually unnecessary, and it's expensive. Wall-clock becomes the sum of each stage's slowest agent.

Let each item flow through all its stages independently. Item A can be in verification while item B is still being analyzed. Wall-clock drops to the slowest single chain.

A barrier is only correct when stage two genuinely needs all of stage one at once: deduplicating across the full result set, or deciding whether to run stage two at all. "I need to flatten the array first" is not a reason. Do the flattening inside a stage.

The failure mode that hides

The orchestrator forgets what it already did.

Long-running orchestration outlives its own context window. When the summary kicks in, the orchestrator's memory of dispatching tasks 1 through 6 is compressed into a sentence. Or lost. The observed failure is that it re-dispatches completed work, and every re-run costs real money and real time.

The fix is unglamorous: write progress to a file, not to memory. A ledger with one line per completed task and the commit it produced. After a context reset, the ledger and git log are ground truth; the orchestrator's recollection is not.

Loop until dry, not until N

For open-ended discovery: find the bugs, find the edge cases. A fixed count is the wrong stopping condition. You don't know how many there are, and while (found < 10) will happily invent the tenth.

Stop when consecutive rounds surface nothing new. Deduplicate against everything seen, including findings the verifiers rejected. Otherwise rejected findings resurface every round and the loop never converges. That's easy to get wrong and hard to notice, because it looks like the search is being thorough.


None of this is specific to coding. It's the same problem as staffing a review: the answer is never "add more people." It's "make sure the people disagree for real reasons."