By Sagar Shankaran, Founder of CallSphere
Master techniques for diagnosing and fixing context loss during multi-agent handoffs, including context inspection, handoff logging, serialization validation, and state verification strategies.
Key takeaways
A user tells your triage agent they want to reschedule their appointment for Tuesday at 2 PM. The triage agent hands off to the scheduling agent. The scheduling agent asks: "What time would you like to schedule your appointment?" The user is frustrated — they just said Tuesday at 2 PM.
Context loss during agent handoffs is one of the hardest bugs to diagnose because it is invisible in logs that only capture text. The handoff succeeds — no errors, no exceptions. But the receiving agent does not have the information it needs because the conversation context was not transferred correctly.
In the OpenAI Agents SDK, a handoff transfers control from one agent to another. The key question is: what data travels with the handoff?
flowchart LR
APP(["Agent or API"])
SDK["OTel SDK<br/>GenAI conventions"]
COL["OTel Collector"]
subgraph BACKENDS["Backends"]
TR[("Traces<br/>Tempo or Honeycomb")]
MET[("Metrics<br/>Prometheus")]
LOG[("Logs<br/>Loki or ELK")]
end
DASH["Grafana plus alerts"]
PAGE(["Pager"])
APP --> SDK --> COL
COL --> TR
COL --> MET
COL --> LOG
TR --> DASH
MET --> DASH
LOG --> DASH
DASH --> PAGE
style SDK fill:#4f46e5,stroke:#4338ca,color:#fff
style DASH fill:#f59e0b,stroke:#d97706,color:#1f2937
style PAGE fill:#dc2626,stroke:#b91c1c,color:#fff
from agents import Agent, handoff
scheduling_agent = Agent(
name="Scheduling Agent",
instructions="Help users schedule and reschedule appointments.",
)
triage_agent = Agent(
name="Triage Agent",
instructions="Route user requests to the appropriate agent.",
handoffs=[scheduling_agent],
)
When the triage agent decides to hand off, the conversation history is passed to the new agent. But the quality of that history depends on what the triage agent included in its messages and how the handoff was configured.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
Create an inspector that captures and displays the exact state being transferred:
import json
from dataclasses import dataclass, field
from typing import Any
@dataclass
class HandoffSnapshot:
from_agent: str
to_agent: str
conversation_history: list[dict]
context_variables: dict
timestamp: float
history_token_count: int = 0
class HandoffInspector:
def __init__(self):
self.snapshots: list[HandoffSnapshot] = []
def capture(
self,
from_agent: str,
to_agent: str,
messages: list[dict],
context: dict,
):
snapshot = HandoffSnapshot(
from_agent=from_agent,
to_agent=to_agent,
conversation_history=json.loads(json.dumps(messages)),
context_variables=json.loads(json.dumps(context)),
timestamp=__import__("time").time(),
)
self.snapshots.append(snapshot)
return snapshot
def diff_context(self, index_a: int, index_b: int):
"""Compare context between two handoff snapshots."""
a = self.snapshots[index_a].context_variables
b = self.snapshots[index_b].context_variables
added = {k: v for k, v in b.items() if k not in a}
removed = {k: v for k, v in a.items() if k not in b}
changed = {
k: {"before": a[k], "after": b[k]}
for k in a
if k in b and a[k] != b[k]
}
print(f"Context diff: snapshot {index_a} -> {index_b}")
if added:
print(f" Added: {json.dumps(added, indent=2)}")
if removed:
print(f" Removed: {json.dumps(removed, indent=2)}")
if changed:
print(f" Changed: {json.dumps(changed, indent=2)}")
Context variables must be serializable. Non-serializable objects silently fail or get dropped:
from datetime import datetime, date
class ContextValidator:
SAFE_TYPES = (str, int, float, bool, type(None), list, dict)
@classmethod
def validate(cls, context: dict) -> list[str]:
"""Find context values that may fail serialization."""
issues = []
for key, value in context.items():
cls._check_value(key, value, issues)
return issues
@classmethod
def _check_value(cls, path: str, value: Any, issues: list):
if isinstance(value, datetime):
issues.append(
f"{path}: datetime object — convert to ISO string"
)
elif isinstance(value, date):
issues.append(
f"{path}: date object — convert to ISO string"
)
elif isinstance(value, set):
issues.append(
f"{path}: set — convert to list"
)
elif isinstance(value, dict):
for k, v in value.items():
cls._check_value(f"{path}.{k}", v, issues)
elif isinstance(value, list):
for i, v in enumerate(value):
cls._check_value(f"{path}[{i}]", v, issues)
elif not isinstance(value, cls.SAFE_TYPES):
issues.append(
f"{path}: unsupported type {type(value).__name__}"
)
# Usage
context = {
"user_name": "Alice",
"appointment_time": datetime(2026, 3, 17, 14, 0),
"preferences": {"tags": {"urgent", "follow-up"}},
}
issues = ContextValidator.validate(context)
for issue in issues:
print(f" WARNING: {issue}")
# WARNING: appointment_time: datetime object — convert to ISO string
# WARNING: preferences.tags: set — convert to list
Add assertions that verify the receiving agent has everything it needs:
class HandoffVerifier:
def __init__(self):
self.requirements: dict[str, list[str]] = {}
def register_agent(self, agent_name: str, required_context: list[str]):
self.requirements[agent_name] = required_context
def verify_handoff(self, to_agent: str, context: dict) -> list[str]:
required = self.requirements.get(to_agent, [])
missing = [key for key in required if key not in context]
return missing
# Define what each agent needs
verifier = HandoffVerifier()
verifier.register_agent("Scheduling Agent", [
"user_id", "requested_date", "requested_time",
])
verifier.register_agent("Billing Agent", [
"user_id", "account_id", "issue_type",
])
# Check before handoff
missing = verifier.verify_handoff("Scheduling Agent", context)
if missing:
print(f"HANDOFF BLOCKED — missing context: {missing}")
When conversation history is long, the receiving agent may lose important details buried in earlier messages. Add a handoff summary:
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
from agents import handoff
def create_summarized_handoff(target_agent, summary_fn):
async def on_handoff(ctx):
summary = await summary_fn(ctx.messages)
ctx.messages.append({
"role": "system",
"content": f"Handoff summary: {summary}",
})
return handoff(
agent=target_agent,
on_handoff=on_handoff,
)
Compare the conversation history at the point of handoff against what the receiving agent actually processes. If the information is in the message history but the agent does not use it, the problem is the receiving agent's instructions — it needs explicit guidance to review prior messages. If the information is missing from the history, the problem is in the handoff mechanism.
Use both. Conversation history provides natural language context the model can reason over. Context variables provide structured data like user IDs, dates, and settings that must be exact. Relying solely on conversation history risks the model misinterpreting or overlooking critical details buried in long message chains.
Implement a redaction layer that replaces sensitive values with tokens before logging. Log the structure and keys of context variables without their values. Use correlation IDs to link handoff events across agents so you can trace the flow without seeing the actual content.
#Debugging #MultiAgent #Handoffs #ContextManagement #AIAgents #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
A founder's guide to the personal AI assistant market: best AI assistant apps, business-grade options, and how CallSphere's voice agent fits in.
A founder's guide to free AI agents, low-code AI agent builders, and how to know when you should pay for a real platform like CallSphere.
Graphiti is the open-source temporal knowledge graph for AI agents in 2026. Learn how bi-temporal memory beats vector RAG for voice agents and long-running LLMs.
Chatbot app vs ChatGPT in 2026: a founder's clear take on the difference, when to use which, and how a real AI chatbot app development works.
How we built a fault-tolerant HVAC emergency triage and tech-dispatch platform on Kubernetes — three-tier CQRS, 11 micro-agents on the OpenAI Agents SDK + LangGraph, NATS JetStream, DTMF/SMS/WebSocket acceptance, circuit breakers, and an evaluation pipeline that catches regressions before they wake a tech at 3 AM.
How to design a multi-agent system using MCP for tools and A2A for cross-vendor coordination, with a CallSphere voice agent as a participating node.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.
Try Live DemoBook a DemoCalculate Your ROI