World’s largest virtual agentic engineering & quality conference
Agent handoff testing catches context loss, orphaned tool calls, and delegation loops between AI agents. Learn the failure modes, assertions, and CI gates.

Samyak Goyal
Author

Sirajuddin Khan
Reviewer
Published on: August 21, 2026
A customer types "I want a refund for order #48812". The triage agent reads the order number, decides this is a refund case, and transfers the conversation to the refunds agent. The refunds agent opens with "Sure, I can help with that. What is your order number?"
Both agents worked correctly. The routing decision was right. The bug lives in the space between them, and no test that checks the final answer will ever point at it.
TL;DR
Agent handoff testing verifies what survives when one agent transfers control to another: payload fields, conversation history, and ownership of the task. It targets the boundary, because both agents can behave correctly while the transfer between them drops information.
A handoff is a transfer of control between agents. The sending agent stops working on the task, and the receiving agent takes ownership of the conversation from that point forward.
The mechanism is worth knowing because it shapes what you can test. In the OpenAI Agents SDK, handoffs are presented to the model as tools with names in the form transfer_to_<agent_name>, which means the model itself decides when to delegate. That decision is non-deterministic, so a handoff is not a code path you can trace statically.
Two pieces of state cross the boundary, and each fails differently:
LangChain takes the opposite default. Its handoff documentation states that unlike single-agent flows where message history moves along naturally, you must explicitly decide what messages pass between agents, and it recommends passing only the handoff pair rather than the full history. Explicit defaults make context loss a design decision instead of an accident, which is exactly why the decision needs a test.
If you are still deciding whether to split one agent into several, the tradeoffs are covered in our guide to multi-agent AI systems.
They are different operations with different bugs, and conflating them produces tests that check the wrong thing. Routing picks a destination before work starts. A handoff moves control after work has already begun, which is why state has to travel with it.
| Dimension | Routing | Handoff | Agents-as-tools |
|---|---|---|---|
| Who owns the task after | The router, which usually regains control once the chosen agent replies. | The receiving agent, which continues the conversation directly with the user. | The caller, which receives a return value and keeps the conversation. |
| What crosses the boundary | The request, generally unmodified, before any work has happened. | A payload plus whatever history the filter allows through. | Arguments in, a result out, with history staying on the caller. |
| Characteristic failure | Work reaches the wrong agent, which is visible in the first reply. | Work reaches the right agent without the detail it needs to act. | The result comes back but the caller ignores or misreads it. |
| What the test asserts | Destination selection across a set of labeled inputs. | Payload completeness, history validity, and hop count. | The return value and how the caller uses it in the next turn. |
A routing bug is loud. The billing question lands on the shipping agent and the very next sentence is wrong. A handoff bug is quiet, because the receiving agent is qualified and its reply reads as a competent request for more information. That asymmetry is why handoff coverage tends to be missing from suites that already test routing well. The wider pattern set is covered in our breakdown of agentic AI orchestration patterns.
Researchers at UC Berkeley built the Multi-Agent System Failure Taxonomy by annotating traces from seven multi-agent frameworks and grouping what they found into 14 failure modes across three categories. Their empirical analysis reports a 41% to 86.7% failure rate on those seven state-of-the-art open-source multi-agent systems, according to the MAST taxonomy paper. One of those three categories is inter-agent misalignment.
The per-mode numbers are more useful than the category total, because each one names a specific thing to assert. The modes below are the ones that surface at a transfer boundary, with shares reported in the same paper:
| Failure mode | Share of failures | What it looks like at a handoff |
|---|---|---|
| Task derailment | 7.40% | The receiving agent drifts away from the delegated task and starts solving an adjacent problem. |
| Loss of conversation history | 2.80% | Earlier turns are gone after the transfer, so constraints the user already stated stop being honored. |
| Conversation reset | 2.20% | The dialogue restarts from its opening state, and the user repeats everything they already said. |
| Ignored other agent's input | 1.90% | The payload arrives intact and the receiving agent proceeds as if it were empty. |
| Information withholding | 0.85% | The sending agent holds a detail it collected and never writes it into the transfer. |
Two of these are worth separating, because they look identical in a transcript and need opposite fixes. Information withholding is a sender bug, so the fix is the payload schema. Ignored other agent's input is a receiver bug, so the fix is the receiving agent's prompt. A test that only reads the final reply cannot tell you which one you have.
Note also how small the individual shares are. No single handoff mode dominates, which means sampling a handful of conversations by hand is unlikely to surface any of them. Coverage has to be systematic, and the same argument applies across the broader problem of multi agent testing.
Note: Handoff bugs hide behind fluent replies, which is why they survive manual review. TestMu AI deploys autonomous evaluators that score every conversation on context awareness, so a receiving agent that re-asks for a known detail is flagged rather than read past. Try TestMu AI free!
Five assertions cover the failure modes above. Each one is checkable without a model in the loop, which keeps the test fast and deterministic even though the agents are not.
All five read from one artifact: a recorded trace of the transfers. Build that recording first, because assertions written against a final transcript can only report that something went wrong, never which hop did it.
Wrap the transfer in a function that appends to a trace, then assert on the trace. The harness below models the refund scenario from the opening, with the bug deliberately left in: the triage agent extracts the order number but never writes it to the payload.
// handoff.test.mjs - records every transfer, then asserts on the record.
const trace = [];
function handoff(from, to, payload, history) {
trace.push({ from, to, payload, historyLen: history.length });
return { agent: to, payload, history };
}
function runTriage(userTurn, history) {
history.push({ role: "user", content: userTurn });
const orderId = (userTurn.match(/#(\d+)/) || [])[1] || null;
history.push({ role: "assistant", tool_call: "transfer_to_refunds_agent", tool_call_id: "call_1" });
// BUG under test: orderId is collected but never placed on the handoff payload.
return handoff("triage_agent", "refunds_agent", { reason: "refund request" }, history);
}
const history = [];
const result = runTriage("I want a refund for order #48812", history);
const required = ["reason", "order_id"];
const failures = [];
for (const key of required) {
if (!(key in result.payload)) failures.push(`CONTEXT LOSS: required field "${key}" missing from handoff payload`);
}
const lastToolCall = history.filter(h => h.tool_call).pop();
const ack = history.find(h => h.role === "tool" && h.tool_call_id === lastToolCall?.tool_call_id);
if (lastToolCall && !ack) failures.push(`ORPHANED TOOL CALL: "${lastToolCall.tool_call}" has no ToolMessage acknowledgement`);
if (trace.length > 3) failures.push(`HANDOFF LOOP: ${trace.length} transfers exceeded the budget of 3`);
console.log("handoff trace:", JSON.stringify(trace, null, 2));
console.log(`\nassertions run: ${required.length + 2} failed: ${failures.length}`);
for (const f of failures) console.log(" FAIL " + f);
process.exit(failures.length ? 1 : 0);Running it with node handoff.test.mjs produces this output:
handoff trace: [
{
"from": "triage_agent",
"to": "refunds_agent",
"payload": {
"reason": "refund request"
},
"historyLen": 2
}
]
assertions run: 4 failed: 2
FAIL CONTEXT LOSS: required field "order_id" missing from handoff payload
FAIL ORPHANED TOOL CALL: "transfer_to_refunds_agent" has no ToolMessage acknowledgement
$ echo $?
1Two failures from one transfer, and both name the hop and the field. The first is the refund bug from the opening scenario, caught at the boundary instead of at the customer. The second was not the bug being hunted: the harness pushes a tool call with id call_1 and nothing ever acknowledges it, which is precisely the invalid-history state LangChain's documentation warns about.
That is the argument for trace-based assertions in one run. A transcript check would have reported a single vague failure. Asserting on the record found a second defect nobody was looking for, and printed the tool call id needed to fix it.
The harness exits non-zero when an assertion fails, so any CI runner already knows how to gate on it. What matters is the trigger list, because handoff behavior changes through edits that never touch application code.
Keep the deterministic assertions in the fast job that runs on every commit, and put the model-graded checks such as tone or completeness in a slower scheduled job. A fast gate people trust catches more than a thorough one they learn to skip, which is the same reasoning behind agent smoke testing.
Once handoff runs are in the pipeline, keep the traces. Production handoff failures are hard to reproduce from a user report, and a stored trace turns a vague complaint into a specific hop. Our guide to agent observability covers moving from evals to production traces.
The harness above proves the boundary contract holds. It cannot tell you whether the receiving agent sounded confused, invented a policy, or quietly dropped a constraint, because those need judgment rather than an equality check. That is the gap TestMu AI's AI agent testing platform fills, by deploying autonomous evaluators against the agent you already have.
Three capabilities map directly onto the failure modes in this article:
Handoff trends are tracked alongside metrics like containment rate, so a decline in transfer quality shows up as a trend line rather than a scattering of individual complaints. For pipeline runs, testmu-a2a-cli documentation covers triggering evaluations from a terminal and wiring them into CI. Note that this CLI is TestMu AI's agent-to-agent testing tool and is unrelated to Google's Agent2Agent protocol, despite the shared initials.
Use both layers rather than choosing between them. Deterministic assertions on the trace are cheap enough to run on every commit and tell you exactly which field died at which hop. Model-graded scoring is slower and costs more, and it is the only thing that catches a handoff that was technically complete and still left the user worse off.
Start by recording your transfers. Wrap whatever function moves control between agents so it appends the sender, receiver, payload, and history length to an array, then write the payload-completeness assertion for your single busiest handoff. That one assertion covers the failure mode users notice first, and it takes less time to write than reproducing the bug by hand.
Add history validity and a hop budget next, wire the harness into the job that runs on prompt changes, and keep the traces from failed runs. When you need scoring that a deterministic check cannot provide, TestMu AI's autonomous evaluators grade conversations that switch agents mid-run across nine quality dimensions including context awareness, and the agent testing documentation covers connecting an agent over REST, WebSocket, or a realtime transport.
Author
Samyak Goyal is a Senior Member of Technical Staff at TestMu AI engineering Kane CLI, the command-line tool that runs browser automation from the terminal, where a flow described in natural language executes in a real Chrome browser and returns pass or fail with shareable proof. He is a backend engineer with 4+ years of experience, previously an SDE at Innovaccer, where he built APIs, introduced Kafka, and cut deployment from weeks to hours. Samyak also builds multi-agent systems, skill-orchestration frameworks, and a personal copilot that indexes 200+ microservice repositories.
Reviewer
Sirajuddin Khan is Vice President of Product Management at TestMu AI (formerly LambdaTest), where he drives the company's agentic AI product strategy, building a suite of autonomous agents that includes Agentic Browsers and Agentic Visual Testing and shifting the unit of work from test execution to autonomous outcomes. One of the company's earliest product leaders, he has owned the roadmap for the high-performance execution cloud and grew the cross-browser testing products from early adoption to market leadership. He brings over a decade of experience across SaaS, B2B, and eCommerce, with earlier product roles at Wydr and ShopClues, where his catalog and search work cut delivery SLAs and lifted seller activity. Sirajuddin holds an MBA in Information Technology from Sikkim Manipal University and a B.Tech in Computer Science Engineering from Maharshi Dayanand University.
Did you find this page helpful?
More Related Blogs
TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance