Five Agents, One Deadlock
A team split a document pipeline into five agents shaped like their org chart. It demoed perfectly, then produced telephone-game handoffs, two agents each waiting on the other, contradictory records, and a 6x token bill — here is how they found it and what they rebuilt.
A composite teaching case: realistic fiction assembled from well-documented public patterns — not a real engagement.
Norsted Logistics moves about 4,200 shipping documents a business day — bills of lading, packing lists, customs declarations, most of them scanned by somebody at a port on a phone. Their pipeline, DocDesk, turns those scans into structured records in their transport management system. Before the rebuild it was a Python script with two model calls in it, and it left 14% of documents for a human to finish.
Tomás Feld, the platform lead, pitched a multi-agent replacement, and the design took about an hour to draw because it drew itself. DocDesk had five teams — intake, extraction, validation, enrichment, filing. So: five agents. Each team would own its agent, its prompts, its tools, its deploys. Each agent would hand off to the next with a short natural-language brief, the way the teams already emailed each other.
Priya Raman built the extraction agent and thought the split was one agent too many, but the demo settled the argument. Twenty curated PDFs, five agents, twenty clean records, zero human touches. The room applauded. They shipped to 10% of production traffic three weeks later.
If the reasoning above sounds familiar, that is the point: the architecture was a copy of the org chart, and nobody had to defend it on technical grounds. Multi-Agent Systems: When One Agent Isn’t Enough works through the four decompositions that actually earn their overhead — this was not one of them.
DocDesk v2 as designed — five agents, five briefs
- Scanned document arrives
Average 9 pages, roughly 9k tokens of OCR text per document.
- Intake agent
Splits multi-page scans, classifies document type, writes a brief for extraction. Tools: storage read, OCR, brief_handoff.
- Extraction agent
Pulls 31 structured fields. Tools: OCR re-read, schema validate, brief_handoff.
- Validation agent
Job description: “if a field is missing or implausible, request enrichment and wait for the enriched record before deciding.”
- Enrichment agent
Job description: “only enrich fields validation has confirmed as present-but-incomplete; if validation status is unknown, ask validation for a decision first.”
- Filing agent
Writes the record to the TMS. Also had write access to the same row as enrichment — nobody noticed until week four.
- Record filed
- Dead-letter queue
Where the deadlocked documents landed, after both agents burned their 40-turn budgets.
Production did not fail loudly. It degraded, which is worse, because degradation gets explained away for a fortnight.
Week one at 10% traffic looked fine. Week two, the human-review rate crept from the promised 2% up past 6% and the operations lead started asking why her team was busier than before the rebuild. Week three it hit 11% — worse than the script DocDesk had replaced — and roughly 17 documents a day, about 0.4%, simply never came out of the pipeline at all. They sat until the 40-turn budget expired and dropped into the dead-letter queue, where a human re-keyed them from scratch.
Then finance forwarded the model bill. Per document, v2 was burning 6.2x the tokens of the shelved single-agent prototype: roughly 41,000 versus 6,600. Nobody had budgeted for that, because nobody had modelled what five agents each re-reading the same nine pages costs. Cost and Latency Budgets You Can Defend exists because this is the single most common surprise in a first multi-agent rollout.
Four distinct failures were tangled together, and they had to be separated before any of them could be fixed.
1 · The telephone game — briefs are lossy compression, applied four times
Each agent wrote the next one a short natural-language brief instead of passing the record. That felt humane and read beautifully in the demo. It also meant every handoff was a lossy re-summarisation of a re-summarisation.
One traced document: intake wrote “partial container load, repeat filer, some pages may be duplicates.” Extraction relayed “possible duplicate pages present.” Validation passed on “duplicate pages flagged.” Enrichment forwarded “duplicate document.” Filing read that and discarded page 6 — which was the only page carrying the customs value.
No single hop was unreasonable. Four hops of hedge-dropping turned may be into is, and a hedge that dies in hop two cannot be resurrected in hop five. This is context engineering failing at the seams rather than inside any one context window.
2 · The mutual wait — two polite agents, one wait cycle
Read the two job descriptions in the diagram again. Validation waits for enrichment when a field is implausible. Enrichment asks validation to decide first when validation status is unknown. Each rule is defensible alone; together they are a wait cycle that fires on exactly one input class — a field that is present but ambiguous, like a container number with an OCR-mangled check digit.
On those documents the two agents took turns asking each other to go first. It was not a thread deadlock; there was nothing to detect at the runtime level. It was a logical deadlock, and it was expensive: every polite “could you confirm first?” was a fresh model call with the whole thread re-summarised into it. Both agents ran their 40 turns and the document died.
Nobody wrote a cycle. Two people wrote one rule each, six weeks apart, and neither rule was wrong. State Machines vs LLM Loops: Who Owns Control Flow? is the module that would have caught this on a whiteboard — a coded state machine cannot express “wait for the peer who is waiting for you” without someone drawing the arrow and seeing it.
3 · Contradictory state — two writers, one row, last write wins
Enrichment normalised declared values to USD. Validation never saw the normalisation, because it had already handed off. Both agents held write access to the same TMS row.
So the currency stored on a record depended on which agent finished second. Under load, that varied. The team had two state holders with no owner and no version check, which is not an agent problem at all — it is the oldest concurrency bug in the book, reintroduced because the writers were now prompts and nobody thought of them as writers. Reliability Plumbing: Timeouts, Retries, Idempotency, Breakers covers the fix in one line: one writer, idempotent, keyed by document id.
4 · The 6.2x bill — five contexts, one document
The arithmetic is boring and that is why it goes unmodelled. Per document:
- Five agents each pulled the same ~9k tokens of OCR text into their own context. That alone is a 5x floor on input.
- Every handoff cost a brief written by one model and read by the next — eleven extra model calls on a clean run.
- Retries and the mutual-wait turns landed on top. The 0.4% of documents that deadlocked consumed about 9% of the total spend.
A workflow doing the same job pays the document cost once. Multi-agent decomposition does not divide work; it multiplies context. If a split does not buy you something worth 5x, it is not an architecture, it is a bill.
| Hypothesis | How they tested it | What came back | Verdict |
|---|---|---|---|
The extraction model regressed | Re-ran the 20 demo documents, then replayed 300 failed production documents through the extraction agent alone. | Demo set still 20/20. Extraction alone was correct on 96% of the failed documents — its output was usually fine. | Wrong. The mistake was rarely in what extraction produced; it was in what the next agent was told about it. |
It is a scan-quality problem | Bucketed six weeks of failures by OCR confidence and page count. | Failures spread evenly across clean and messy scans. They correlated instead with documents containing any ambiguous field. | Wrong, and the correlation was the first real clue — ambiguity, not noise, was the trigger. |
One agent’s prompt is bad | All five prompts read line by line in one room, by all five owners. | Every prompt was defensible on its own terms. Two of them, read together, formed a wait cycle nobody had ever seen on the same page. | Wrong at the component level, right at the system level. No prompt was broken; the composition was. |
Just reproduce it locally | Replayed the same document through the full pipeline five times. | Three different agent paths in five runs. Two runs completed cleanly. Non-determinism meant bisecting by re-running never converged. | Dead end by construction. You cannot bisect a distribution. You need the failure recorded, not reproduced — the argument behind Trace Anatomy: Reading an Agent Run Like a Professional. |
Whose context held the mistake? | Tried to reconstruct, for one dead-lettered document, what each agent believed at each turn. | Impossible. Each agent logged its own turns to its own team’s dashboard, with its own request ids and no shared key. Five traces, no run. | The right question — and the one they could not answer yet. That gap is what they fixed next. |
Does this step deserve its own agent? (the gate Norsted now runs on every proposal)
Interactive decision tree — outcomes:
- Write the code
Three of Norsted’s five agents ended up here. Splitting pages, checking that totals add up, looking up a consignee — these were only ever agents because a team owned them. Code is cheaper, faster, testable with assertions, and cannot be talked out of its job.
- A step inside the one agent
Same trust boundary, small output, genuine judgment: keep it in the loop that already owns the document. You get shared context for free and pay for it once. This is the default the single-agent patterns module argues for.
- A subagent — the delegation that works
One-way, context-isolating, returns a structured result and shuts up. This is the decomposition that survived at Norsted. Subagents and Context Isolation: The Delegation Move covers the pattern and the honest cost: you pay for a second context to keep the first one clean.
- Supervisor-worker, with a named arbiter
Workable — as long as the supervisor, not the workers, owns the decision and the write. The moment a worker can send work back to a peer, you have left supervisor–worker and entered peer negotiation.
- You just designed the deadlock
Two peers negotiating over one record with no arbiter is a wait cycle waiting for an ambiguous input. It will pass your demo, because your demo has no ambiguous inputs. Give one of them authority, or replace the negotiation with a coded state machine that can only move forward.
Interactive sorting exercise: Norsted ran all eleven of DocDesk’s responsibilities through that gate. Sort them the way the rebuild did — plain code, a single model call, a step in the one document agent, or a separate agent on purpose.
What we changed
One agent owns the document; code owns the sequence. DocDesk v3 is a coded pipeline — split, OCR, extract, arithmetic checks, lookups, write — with a single document agent invoked at the two points where judgment is actually required, plus two subagents it can call. The orchestration lives in Python, in a state machine that can only move forward. Workflow Patterns: Five Ways to Compose Model Calls is the shape they landed on; they had skipped straight past it to agents.
Records move, briefs do not. Every step passes the typed record plus an explicit open_questions list. Free-text handoffs between components are banned. The audit summary is written once, at the end, and read only by humans.
One writer. The TMS write is a coded, idempotent function keyed by document id, with a version check. No model holds write credentials to that row.
No peer negotiation, ever. If two components would have to agree, one of them gets authority or the negotiation becomes a coded transition. That rule came directly from the waiting_on screenshot.
Traces before theories. Correlation id, workflow span, per-agent token counts, and waiting_on are now non-negotiable at rollout, not added during an incident. Monitoring and Incident Response: When the Agent Is the Incident is the module they should have read in week one; three of their five debugging weeks were spent building the visibility they could have had on day one.
What stayed multi-agent, and why. Two things, both because they passed the gate above rather than because they matched a team. The tariff-research subagent is one-way and context-isolating: it swallows hundreds of pages and returns one cited answer, so the document agent never sees the mess. The customer-correspondence agent exists for a trust boundary, not efficiency: it reads untrusted third-party email, runs with its own credentials, and holds no write access to the record. Neither can hand work back to a peer. Both were worth their extra context; the other three splits never were.
The numbers, twelve weeks later. Human-review rate 11% → 1.8%, against a 14% baseline for the old script. Median tokens per document 41,000 → 8,900 — still 1.35x the shelved prototype, and they consider that a fair price for the two subagents that earn their keep. p95 latency 34s → 9s. Dead-lettered documents: zero in twelve weeks, because there is no longer a cycle to enter.
The five teams still exist. Norsted just stopped shipping the org chart as an architecture. Decompose along context boundaries, trust boundaries, and who owns the decision — never along reporting lines. And if you cannot name the single component that owns the record, you have not designed a system yet; you have arranged a meeting.