Every multi-agent framework eventually hits the same problem.
Everything works perfectly in demos.
Then production traffic arrives.
One malformed tool response, one empty vector search, or one invalid JSON payload later...
Your expensive LLM agents begin doing this forever:
Retrieve documents
↓
No results
↓
Call retrieval tool again
↓
Still empty
↓
Try again...
↓
Try again...
↓
Try again...
CPU usage climbs.
OpenAI bill climbs.
Users wait.
Nothing ever finishes.
If you're using CrewAI, LangGraph, AutoGen, OpenAI Agents SDK, or your own orchestration layer, you've probably seen some variation of this.
The good news is that the fix is surprisingly small.
The Real Production Failure
Most agent frameworks assume tools eventually return something useful.
Reality is messier.
Typical retrieval flow:
Question
↓
Retriever
↓
Vector DB
↓
Empty Context
↓
LLM:
"I should search again."
↓
Retriever
↓
Empty Context
↓
Repeat forever
The model isn't "broken."
It's following its objective.
Without explicit stopping conditions, the optimal strategy becomes:
"Maybe the next retrieval succeeds."
Forever.
Another Common Failure: Invalid JSON
Modern agent frameworks frequently communicate using structured outputs.
Example:
{
"action": "search",
"query": "LangGraph memory"
}
Now imagine the model returns:
{
action: search,
query: LangGraph memory
Oops.
JSON parser throws.
Your orchestration catches the exception.
The LLM receives:
"Tool failed."
Its response?
Try the tool again.
Another malformed payload.
Loop.
Why Multi-Agent Systems Make This Worse
Single-agent systems usually fail once.
Multi-agent systems amplify failures.
Planner
↓
Retriever
↓
Researcher
↓
Critic
↓
Planner again
One empty retrieval can bounce between agents indefinitely.
I've seen production graphs where six agents continuously handed an impossible task to one another for hundreds of iterations before timing out.
The Three-Line Fix
Instead of blindly retrying forever:
if not context:
raise EmptyContextError()
Intercept it immediately.
MAX_RETRIES = 2
if retries >= MAX_RETRIES:
return "NO_CONTEXT_AVAILABLE"
Then teach downstream agents to recognize the sentinel value.
if result == "NO_CONTEXT_AVAILABLE":
return "Answer cannot be generated with current knowledge."
That's it.
No infinite loop.
No runaway token spend.
No mystery production hangs.
A Cleaner Version
Here's a minimal wrapper around any retrieval function.
from typing import Callable, Any
MAX_RETRIES = 2
EMPTY = "NO_CONTEXT_AVAILABLE"
def safe_retrieve(fn: Callable[..., Any], *args, **kwargs):
retries = 0
while retries < MAX_RETRIES:
try:
docs = fn(*args, **kwargs)
if docs:
return docs
except Exception:
pass
retries += 1
return EMPTY
Now your agent logic becomes predictable.
docs = safe_retrieve(vector_store.search, query)
if docs == EMPTY:
return {
"status": "insufficient_context",
"answer": (
"No supporting documents were found. "
"Avoid retrying the retrieval tool."
),
}
# Continue with normal RAG pipeline...
Notice that we don't silently swallow the failure—we return an explicit state that downstream components can understand and handle.
Handling Invalid JSON Safely
Never assume LLM output is valid.
Instead:
import json
def parse_agent_output(raw: str):
try:
return json.loads(raw)
except json.JSONDecodeError:
return {
"status": "invalid_json",
"retry": False
}
Then your workflow can branch intentionally instead of recursively retrying.
response = parse_agent_output(llm_output)
if response["status"] == "invalid_json":
return "Agent produced malformed output."
Add a Circuit Breaker
Every production agent should have one.
MAX_STEPS = 20
for step in range(MAX_STEPS):
result = agent.run()
if result.done:
break
else:
raise RuntimeError(
"Agent exceeded maximum execution steps."
)
Think of this as the equivalent of a database query timeout.
Without it, one bad prompt can burn thousands of unnecessary tokens.
Observability Matters More Than Retries
One of the biggest mistakes in agent orchestration is treating every failure as something to retry.
Instead, log structured events.
logger.info(
"retrieval_failed",
extra={
"query": query,
"attempt": retries,
"reason": "empty_context",
},
)
Now your dashboards can answer questions like:
- Which tools fail most often?
- Which queries produce empty context?
- Which agent loops consume the most tokens?
- How many requests terminate because of malformed outputs?
Once you can measure failures, you can improve them.
Production Checklist
Before deploying any multi-agent RAG system, verify that you have:
- ✅ Maximum tool retry limits
- ✅ Empty-context detection
- ✅ Invalid JSON handling
- ✅ Maximum execution steps
- ✅ Circuit breakers
- ✅ Structured logging
- ✅ Token usage monitoring
- ✅ Graceful fallback responses
- ✅ Tool timeout limits
- ✅ Human-readable failure states
These safeguards are lightweight, but they prevent many of the most expensive production failures.
Final Thoughts
Most production agent failures don't come from model quality.
They come from missing guardrails.
The most reliable systems aren't the ones with the largest models—they're the ones that know when to stop.
Three lines of defensive logic can save hours of debugging, thousands of wasted tokens, and a lot of confused users.
If you're building AI agents in production, I put together a free resource covering the most common orchestration pitfalls:
📋 10 AI Agent Failure Modes Checklist (Free)
https://shadowcraft41.gumroad.com/l/bjezoo
And if you're building or operating production RAG systems with multiple agents, retrieval pipelines, and tool orchestration, I also created the:
⚙️ Multi-Agent RAG Operations Kit ($29)
https://shadowcraft41.gumroad.com/l/xmzthq
It includes practical patterns, operational checklists, architecture guidance, and production-ready practices for building more reliable multi-agent RAG systems.
Happy shipping!
Top comments (0)