Workflows And Multi Agent Systems
Multi-Agent Failure Modes
Multi-agent systems frequently perform WORSE than
JrCodex·7 min read
Jr Codex Agentic AI Notes
Level: Advanced Prerequisites: Chapter 4: Agent Communication Time to complete: ~20 minutes
Table of Contents
- The Uncomfortable Baseline
- Error Compounding
- Cost Explosion
- Diffusion of Responsibility
- Coordination Deadlock
- Deciding Honestly
- Summary & Next Steps
1. The Uncomfortable Baseline
The Finding to Take Seriously
─────────────────────────────────────────
Multi-agent systems frequently perform WORSE than
a single well-equipped agent on the same task —
while costing several times more and taking longer.
This is not an argument against multi-agent. It is
an argument for MEASURING, because the failure is
invisible: the system produces plausible output
and nobody compares it to the alternative.
─────────────────────────────────────────
Why the Intuition Misleads
─────────────────────────────────────────
Human teams outperform individuals because humans
have hard limits on attention, expertise and
working hours, and communication between them is
cheap and lossless-ish.
For agents, almost the reverse holds: context is
the scarce resource, "expertise" is a prompt, and
communication is LOSSY and EXPENSIVE (Chapter 4).
The analogy that makes multi-agent feel obviously
right is the thing that makes it fail.
─────────────────────────────────────────
2. Error Compounding
The Arithmetic
─────────────────────────────────────────
Suppose each agent is 90% reliable on its part.
1 agent ──► 0.90 = 90%
3 chained ──► 0.90³ = 73%
5 chained ──► 0.90⁵ = 59%
And this UNDERSTATES it, because a wrong result
from agent A is passed to B as an established
fact. B has no way to know it is wrong and builds
on it confidently.
─────────────────────────────────────────
def with_verification(agent, task, verify, max_retries=1):
"""Check at each boundary — the alternative is silent compounding."""
for _ in range(max_retries + 1):
result = agent.run(task)
check = verify(result) # Module 4, Chapter 3's hierarchy
if check.passed:
return result
task = task.with_feedback(check.reason)
return result.mark_low_confidence() # PASS THE DOUBT ON, do not hide itThe Mitigations
─────────────────────────────────────────
VERIFY AT BOUNDARIES a check between agents
costs one call and stops
compounding
SHORTEN CHAINS each removed hop removes a
multiplication
PARALLEL NOT SERIAL independent agents' errors
do not multiply — they stay
independent and are
detectable by disagreement
PROPAGATE CONFIDENCE Chapter 4's field, so
downstream agents can
discount weak inputs
─────────────────────────────────────────
3. Cost Explosion
Where the Money Goes
─────────────────────────────────────────
SINGLE AGENT, 10 steps
~10 calls, one growing context.
SUPERVISOR + 3 WORKERS, same task
supervisor decisions ~8 calls
handoff briefs ~3 calls
worker A internal loop ~8 calls
worker B internal loop ~8 calls
worker C internal loop ~8 calls
result summarisation ~3 calls
final synthesis ~2 calls
─────────
~40 calls
4x the calls. And each worker re-reads the goal
and its brief, so token count grows faster still.
─────────────────────────────────────────
class MultiAgentBudget:
"""One budget for the WHOLE system, not one per agent."""
def __init__(self, total_cents=300, per_agent_cents=80):
self.total, self.per_agent = total_cents, per_agent_cents
self.spent, self.by_agent = 0.0, {}
def check(self, agent_name, estimate):
if self.spent + estimate > self.total:
raise BudgetExhausted(f"system budget reached ({self.spent:.0f}c)")
if self.by_agent.get(agent_name, 0) + estimate > self.per_agent:
raise BudgetExhausted(f"{agent_name} exceeded its allowance")
def record(self, agent_name, actual):
self.spent += actual
self.by_agent[agent_name] = self.by_agent.get(agent_name, 0) + actualPer-Agent Budgets Are Not Enough
─────────────────────────────────────────
Five agents with generous individual limits can
collectively spend far more than intended, and
each one looks well-behaved in isolation.
You need BOTH: a system-wide ceiling and a
per-agent allowance. This is the multi-agent
version of Module 4, Chapter 4's bounding layer.
─────────────────────────────────────────
4. Diffusion of Responsibility
What It Looks Like
─────────────────────────────────────────
Supervisor: "Research agent, find the pricing."
Research: "I found tier names but no prices;
the analysis agent may have better
sources."
Analysis: "I was not given prices. The research
agent handles that."
Supervisor: synthesises a report describing "tier
structures" without any prices, and
presents it as complete.
Every agent behaved reasonably. The task failed.
Nobody owns the gap.
─────────────────────────────────────────
def check_coverage(plan, results) -> list[str]:
"""Verify every required output actually exists. Never ask the agents."""
gaps = []
for requirement in plan.required_outputs:
provider = results.get(requirement.owner)
if provider is None or provider.status != "completed":
gaps.append(f"{requirement.name}: owner {requirement.owner} did not deliver")
elif not satisfies(provider, requirement):
gaps.append(f"{requirement.name}: delivered but does not meet the spec")
return gapsThe Fix Is Structural
─────────────────────────────────────────
EXACTLY ONE OWNER per required output, named in
the plan before execution.
An EXTERNAL coverage check against that plan —
Module 4, Chapter 4's premature-completion check,
extended across agents.
Never ask the supervisor whether the task is
complete. Ask the plan.
─────────────────────────────────────────
5. Coordination Deadlock
Three Shapes
─────────────────────────────────────────
CIRCULAR WAITING A waits for B's output, B waits
for A's. Both idle.
PING-PONG A delegates to B, B decides it
is A's job, delegates back.
Repeat until the budget is gone.
POLITENESS LOOP two agents agree with each other
indefinitely without producing
anything (Module 5, Chapter 4).
─────────────────────────────────────────
class DelegationGuard:
def __init__(self, max_depth=3, max_handoffs=10):
self.chain, self.count = [], 0
self.max_depth, self.max_handoffs = max_depth, max_handoffs
def check(self, sender, receiver):
self.count += 1
if self.count > self.max_handoffs:
raise CoordinationFailure("handoff limit reached")
if receiver in self.chain: # A ──► B ──► A
raise CoordinationFailure(f"delegation cycle: {' → '.join(self.chain)} → {receiver}")
if len(self.chain) >= self.max_depth:
raise CoordinationFailure("delegation too deep")
self.chain.append(receiver)
def complete(self, agent):
if self.chain and self.chain[-1] == agent:
self.chain.pop() # unwind on returnDetect It in Code, Not in Prompts
─────────────────────────────────────────
This is Module 4, Chapter 4's point again: the
agents cannot see the cycle. From inside, each
delegation looks reasonable.
The delegation chain is a data structure your
orchestrator owns. Check it there.
─────────────────────────────────────────
6. Deciding Honestly
Run This Before Committing
─────────────────────────────────────────
1. Build the SINGLE-AGENT version with all the
tools.
2. Measure quality, cost, and p95 latency on your
eval set (Module 7, Chapter 2).
3. Build the multi-agent version.
4. Measure the same three.
5. Multi-agent must win on quality by enough to
justify what it lost on cost and latency.
If you cannot run this comparison, you cannot
claim the architecture is justified.
─────────────────────────────────────────
When Multi-Agent Genuinely Wins
─────────────────────────────────────────
✓ Sub-tasks need large, INCOMPATIBLE contexts
that will not fit together
✓ One agent would need 40+ tools
✓ Independent sub-tasks run in PARALLEL and
latency matters more than cost
✓ Different sub-tasks genuinely need different
MODELS — a cheap one for extraction, an
expensive one for reasoning
Note that all four are RESOURCE arguments, not
intelligence arguments. That is the pattern.
─────────────────────────────────────────
The Default to Hold
─────────────────────────────────────────
ONE agent, good tools, good context management,
strong evaluation.
Add a second agent when you can name which
resource limit forced it — and can show the
measurement.
─────────────────────────────────────────
7. Summary & Next Steps
Key Takeaways
- Multi-agent systems often underperform a single well-equipped agent while costing more; the failure is invisible unless you measure the single-agent baseline.
- Errors compound multiplicatively across chained agents and are passed downstream as established fact — verify at boundaries and propagate confidence.
- Budgets must be both system-wide and per-agent, because five individually well-behaved agents can collectively overspend by far.
- Diffusion of responsibility and delegation cycles are invisible from inside the agents; assign exactly one owner per output and detect cycles in orchestrator code.
Concept Check
- Why does the human-team analogy actively mislead when reasoning about multi-agent systems?
- Three chained 90%-reliable agents give 73% end-to-end. Why is the real figure typically worse?
- All four legitimate reasons for multi-agent share a property. What is it, and why does that matter?
Module 6 Complete — What's Next
You can now orchestrate durable, multi-step, multi-agent systems — and you have been told repeatedly to measure rather than assume. Module 7 is how: evaluating trajectories rather than outputs, and operating these systems once they are running.
Next Module
→ Module 7: Evaluating & Operating Agents
Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index | Back to Agentic AI Index