Multi-Agent Failure Modes
The four ways multi-agent systems fail in production — loops, cost blow-ups, responsibility diffusion and undebuggable behaviour — and the controls that contain each one.
Multi-agent systems fail in ways single agents do not, because the thing going wrong is the coordination rather than any individual agent. Each failure mode has a specific control, and the controls are cheap compared with discovering the problem from a bill or an incident.
Loops
Agent A delegates to B; B decides the request belongs to A and hands it back. Or a supervisor keeps re-asking the same collaborator because the answer never quite satisfies its instruction. Both burn tokens indefinitely while looking healthy from the outside.
The structural fix is to make the delegation graph acyclic: a collaborator should not be able to invoke its supervisor, and two peers should not be able to invoke each other. On top of that, enforce a hop budget in your own code — count delegations per user turn and fail closed when the budget is spent. Bedrock agents also have idleSessionTTLInSeconds, and a Step Functions TimeoutSeconds or state-machine-level timeout gives you a hard ceiling that no prompt can talk its way past.
Cost blow-ups
You are billed per input and output token, and every hop re-sends a system prompt, a set of tool schemas and — if you relay conversation history — the whole conversation. Three collaborators consulted twice each is not three times the cost of a single agent; it can be far more, because the supervisor's own context grows with each result it absorbs.
Contain it by relaying history only when a collaborator genuinely needs it, by having collaborators return short structured results rather than prose, by using a small cheap model for classification and routing, and by using prompt caching where the model and Region support it. Then measure: enable model invocation logging, watch Bedrock's CloudWatch invocation and token metrics, tag agents for cost allocation, and put an AWS Budgets alarm on the tag so an experiment cannot run all weekend unnoticed.
Responsibility diffusion
The subtle one. Every agent assumes another agent checked. The research agent returns a plausible figure it did not verify, the writing agent presents it confidently, and no component ever owned correctness. Multi-agent systems are very good at laundering a guess into an authoritative-sounding answer.
The fix is organisational, not technical: exactly one agent owns the user-visible answer and its verification, and collaborators must be instructed to return "unknown" rather than a best guess. Require collaborators to return structured output with the source of each claim, so the owner can check rather than trust.
Debugging difficulty
The same input can take a different path on two runs, so "reproduce it" is not always available. Instrument before you need it: call InvokeAgent with enableTrace=True and store the trace, enable Bedrock model invocation logging to CloudWatch Logs or S3, thread a single correlation id through every hop, and use Amazon Bedrock AgentCore Observability or OpenTelemetry with CloudWatch if you are running agents outside Bedrock Agents. Keep a fixed evaluation set of representative requests and run it after every prompt or model change — in a system where the path is chosen by a model, a regression suite is the only reliable regression detector.
The meta-lesson
Every one of these gets worse with depth. Two levels of delegation is manageable; four is an incident waiting to happen. When a design review shows a deep or cyclic agent graph, the right response is usually to collapse part of it into a deterministic workflow.
Example
import json, time, uuid, boto3
rt = boto3.client("bedrock-agent-runtime")
logs = boto3.client("cloudwatch")
MAX_DELEGATIONS = 6 # hard hop budget per user turn
MAX_WALL_SECONDS = 120 # hard time budget
def ask_supervisor(user_text: str, session_id: str, correlation_id: str) -> str:
started = time.time()
delegations = 0
answer = []
response = rt.invoke_agent(
agentId="ABCDEFGHIJ",
agentAliasId="LIVE01",
sessionId=session_id,
inputText=user_text,
enableTrace=True,
)
for event in response["completion"]:
if time.time() - started > MAX_WALL_SECONDS:
raise TimeoutError(f"{correlation_id}: wall-clock budget exceeded")
if "chunk" in event:
answer.append(event["chunk"]["bytes"].decode())
continue
trace = event.get("trace", {}).get("trace", {})
# Count every hand-off to a collaborator and fail closed on the budget.
if "routingClassifierTrace" in trace or "orchestrationTrace" in trace:
delegations += 1
if delegations > MAX_DELEGATIONS:
raise RuntimeError(
f"{correlation_id}: delegation budget exceeded - probable loop"
)
# One correlation id across every hop makes the run greppable later.
print(json.dumps({"correlationId": correlation_id,
"sessionId": session_id,
"delegations": delegations,
"trace": trace}, default=str))
logs.put_metric_data(
Namespace="AgenticApp",
MetricData=[{"MetricName": "DelegationsPerTurn", "Value": delegations,
"Unit": "Count"}],
)
return "".join(answer)
ask_supervisor("Order 44821 arrived damaged, can I get my money back?",
session_id="session-8f2c",
correlation_id=str(uuid.uuid4()))When to use it
- Catching a support supervisor that ping-pongs a refund request between two collaborators before it costs a weekend of tokens.
- Attaching cost-allocation tags to each agent so a Budgets alarm fires when one specialist's token spend spikes.
- Storing agent traces with a shared correlation id so an incorrect customer answer can be traced back to the collaborator that produced the figure.
Discussion