The research agent found the customer’s account, confirmed the plan, and pulled the last four invoices. It handed off to the billing agent with a one-line summary: “Customer wants a refund.” The billing agent, which now knows nothing about the account, the plan, or the invoices, starts by asking for the account ID.
Every fact the first agent gathered was thrown away at the boundary. That is the handoff problem, and it costs you twice: once in the duplicated API calls, once in the errors that come from the second agent working with less information than the first.
This guide covers what has to survive a handoff, the three ways teams pass state and when each works, why summaries lose more than people expect, and how to test that a handoff carried what it claimed to. Our post on why agents break in production treats lost state as a core failure mode; this is the multi-agent version of it.
Apidog shows up because the cheapest fix is usually to stop passing data at all and pass identifiers instead, which only works if every agent can fetch the same record the same way.
What actually needs to cross the boundary
Not everything. A handoff that copies the entire conversation is as broken as one that copies nothing, just in the other direction: the second agent inherits a full context window and has to work out which parts matter.
Four categories are worth separating.
Identifiers. Account IDs, order IDs, job IDs, ticket numbers. These are small, stable, and let the receiving agent fetch anything it needs. They are the most valuable thing to pass and the most commonly dropped.
Decisions already made. “The customer is eligible for a refund under policy 3.” The receiving agent must not re-litigate this. If it does, you get two agents disagreeing inside one task.
Constraints. Budget limits, approvals granted, actions already taken. Losing this is how a task ends up charging twice or asking for the same approval a second time. It pairs directly with our post on idempotency for AI agents.
Open questions. What the first agent could not resolve. Passing this explicitly stops the second agent from silently assuming.
What does not need to cross: raw API responses, the reasoning transcript, and anything the receiving agent can fetch on its own in one call.
Three ways to pass state
Pass the whole conversation. Simple, and it works for two agents in one short task. It fails as soon as the transcript is long, because the receiving agent spends most of its budget reading history and the relevant facts are buried in the middle. Our post on keeping tool responses out of the context window explains why that middle is exactly where models lose things.
Pass a summary. The first agent writes a handoff message; the second starts from it. This is the default in most frameworks and it is lossy in a specific way: models summarize toward narrative and away from identifiers. Ask for a summary and you get “the customer has been a subscriber for two years and is frustrated” instead of “account 8812, plan pro, four invoices, refund approved for invoice inv_44.”
Pass a structured handoff object. The first agent fills a schema. The second reads fields, not prose. This is more work to set up and it is the one that holds up.
{
"task_id": "task_2026_08_26_0031",
"from_agent": "research",
"to_agent": "billing",
"entities": {
"customer_id": "cus_8812",
"invoice_ids": ["inv_41", "inv_42", "inv_43", "inv_44"],
"subscription_id": "sub_119"
},
"decisions": [
{ "decision": "refund_eligible", "value": true, "basis": "policy 3.2, charged twice in one cycle" }
],
"constraints": {
"max_refund_cents": 4900,
"human_approval_granted": false,
"actions_taken": ["read_invoices"]
},
"open_questions": ["Customer has not confirmed which invoice to refund"],
"summary": "Customer cus_8812 was double-charged in August. Refund of one invoice is approved under policy 3.2, up to 4900 cents. Awaiting the customer's choice of invoice."
}
Prose still appears, in the summary field, because it carries nuance the schema does not. It sits alongside the structured fields rather than replacing them, which is the whole point.
Validate the object before the handoff runs. If customer_id is missing, fail loudly at the boundary rather than letting the second agent discover it three calls later.
Pass references, not payloads
The strongest version of a handoff passes almost no data. It passes IDs, and the receiving agent fetches what it needs.
This works for three reasons. The state stays fresh, so if something changed between the two agents the second one sees the current value rather than a stale copy. The handoff stays small, a few hundred bytes instead of tens of thousands of tokens. And the audit trail improves, because every read shows up as an API call rather than as text copied between prompts.
It requires one thing: every agent can reach the same API with the right permissions. That is not free. Each agent needs its own credentials scoped to what it does, which is the argument in our post on least-privilege API keys for agents. A billing agent holding a read-only research token cannot issue the refund, and a research agent holding the billing token is a blast-radius problem.
Where a refetch would be expensive or slow, cache the record in your orchestrator and pass a reference to the cache entry. The receiving agent still asks for the data explicitly, so the pattern stays the same, but the second read is cheap.
Where handoffs actually break
Four failures cover most incidents.
The dropped identifier. The summary says “the customer” and never gives an ID, so the second agent searches by name, finds two matches, and picks the wrong one. Prevent it by validating that required entity IDs are present before the handoff is allowed to proceed.
The repeated action. The first agent already sent the email. The handoff does not record it. The second agent sends it again. Record actions_taken in the handoff object and check it before any write, backed by the idempotency keys that make a repeat harmless.
The lost approval. A human approved a refund while the first agent was running. The second agent, not knowing this, asks again. Users read the second prompt as a system that does not listen. Carry approvals as explicit constraints, and treat them as scoped to the task rather than to the agent.
The confident invention. The receiving agent needs a value the handoff did not carry, and rather than asking, it invents one that fits the narrative. This is the most dangerous failure because it looks like a completed task. The defense is the open_questions field plus a hard rule in the receiving agent’s prompt: if a required identifier is absent, stop and ask.
Loops make all four worse. When agent A hands to B and B hands back to A, state decays on each pass, the way a photocopy of a photocopy does. Cap the number of hops and carry the original task object through every one of them rather than rebuilding it at each boundary.
Test the boundary, not just the agents
Handoffs are integration points, so test them as integration points.
Assert on the handoff object. Run the first agent against a fixed scenario and check the object it produces: required identifiers present, decisions recorded, actions listed. This is a deterministic assertion on a structured payload even though the agent that produced it is not deterministic, which is what makes it a usable test. The general approach is in our guide to testing non-deterministic agents.
Test the receiver in isolation. Feed the billing agent a handcrafted handoff object and check what it does. Then feed it a deliberately broken one, with the customer ID removed, and confirm it asks instead of guessing. This second test is the one that catches invention.
Run both against mocks. A handoff test that issues real refunds is a test you will run once. Point both agents at mocked endpoints so the suite can run on every change, following our post on running agents against mocks instead of production. In Apidog the mocks come from the same API definition both agents call, so the two never drift apart.
Log every handoff. Record the full object at each boundary with the task ID. When a multi-agent run goes wrong, the handoff log tells you which agent had the information and which one lost it, which is usually the entire investigation. Our post on tracing agent tool calls covers what else belongs in that record.
What the frameworks give you
Most orchestration frameworks ship a handoff primitive, and it helps to know what each one actually moves across the boundary before you rely on it.
The OpenAI Agents SDK handoff documentation models a handoff as a tool the agent can call, which means the model decides when control transfers. That is convenient, and it puts the decision in the least deterministic part of your system, so pair it with validation on the way out.
LangGraph’s multi-agent guidance takes the opposite tack: state is an explicit graph object that every node reads and writes. This maps closely to the structured handoff described above, and the main work left to you is deciding which fields are required.
Anthropic’s write-up on building a multi-agent research system is worth reading for the operational detail, particularly on how much instruction a sub-agent needs before it can work usefully on its own.
The common thread: every framework will move something. None of them decide for you which facts are load-bearing. That list is yours to write, and it is the thing worth reviewing when a run goes wrong.
Keep the task object outside the conversation
One structural change prevents a whole family of bugs. Store the task state somewhere durable, keyed by task ID, and have every agent read and write it rather than passing it through messages.
The conversation is a poor container for state. It gets compacted, truncated, and rewritten by summarization, and none of those operations know which fields you cannot afford to lose. A row in a database does not have that problem.
The pattern is small. At the start of a turn, the agent loads the task object. When it takes an action, it appends to actions_taken and saves. On handoff, it passes the task ID, and the receiving agent loads the same object. Nothing important travels in the prompt, so nothing important can be summarized away.
This also gives you a resume point. If a run dies at step four, the task object still holds everything the first three steps established, and the retry starts from there instead of from nothing.
Where the platform can hold the state
If your agents run as CLI runtimes on developer machines, the durable task object described above is something you build. Some agent work-management platforms already model it, and it is worth knowing what that looks like before you write your own.
Sharkly is a work management system for people and agents built around exactly this unit. A Task carries the goal, the status, the person responsible, the Agent or Crew assigned to execute it, the comments, and the agent’s execution state and result. A Crew pairs a leader Agent with other Agents and people, so a task that needs several specialists is assigned to a reusable group rather than passed hand to hand through prompts. Because the state lives on the Task rather than in a conversation, a handoff between two agents does not depend on one of them summarizing well.
The runtimes stay whatever you already use. Claude Code, Codex, and the rest execute the work on a Computer you register; the platform supplies the task record, the assignment, and the review loop around them. If you are building the durable-task pattern yourself, the Sharkly documentation is a useful reference for which fields turn out to matter.
A checklist for handoffs
- A defined schema exists for the handoff, and it is validated at the boundary.
- Entity identifiers are required fields, not optional ones.
- Decisions carry their basis, so the receiver does not redo the reasoning.
- Actions already taken are recorded and checked before any write.
- Approvals and budgets travel with the task, not with the agent.
- Open questions are explicit, and the receiver asks rather than assumes.
- Data is passed by reference where a refetch is cheap.
- Hop count is capped, and the original task object survives every hop.
- Every handoff is logged with the task ID.
- Boundary tests run in CI against mocks, including a deliberately incomplete handoff.
Most multi-agent failures are not reasoning failures. They are a fact that existed in one agent and not in the next. Design the boundary as an interface, with a schema and tests, and the second agent stops asking questions the first one already answered. Download Apidog to keep the mocks and boundary tests next to the API both agents depend on.
Frequently asked questions
Is a structured handoff worth it for two agents? For two agents in a short task, passing the conversation is usually fine. The structured object earns its keep at three or more agents, at long tasks, or anywhere a handoff crosses a process or a run boundary.
Should the model write the handoff object or should code build it? Code where it can. Identifiers, actions taken, and approvals should be filled by your orchestrator from what actually happened, not from the model’s recollection. Let the model write only the summary and the open questions.
How do I stop context decay in a loop? Carry one task object through the whole run and update it, rather than regenerating it at each boundary. Then cap the hops. If a task needs more than a handful, the decomposition is probably wrong.
What about frameworks with built-in handoff support? Use them, and check what they actually transfer. Many pass the message history and nothing else, which means identifiers survive only if they happen to appear in the text. Add a structured payload alongside whatever the framework carries.
Do sub-agents need separate API credentials? Yes, scoped to what each one does. Sharing one powerful key across agents removes your ability to limit damage and to tell which agent made a call. Our post on least-privilege API keys for agents covers the setup.
How much should the summary field contain? A few sentences, covering intent and nuance that the structured fields cannot hold. If it starts listing IDs and amounts, those belong in the structured fields where they can be validated.



