Recipes← all recipesView on GitHub

Transfer a call without losing context

AI Agentscontext-preserving call transfer

Pass caller identity and state with the transfer so the next call leg receives the context.

governancetransfer

The claim

The caller gives their name and reason once. When the call moves to a second agent, that agent already has both, with no re-authentication and no repeated intake. Nothing is serialised into a URL in the hope that the other side parses it.

Example · Annotated transcript
intakeWho am I speaking with, and what can I help with?
callerDana Whitfield. There is a charge on my bill I do not recognise.
caller_name, intake_reason and verified are written to the shared context, then the call connects to the billing specialist.
intakeThanks Dana, connecting you to billing.
New agent, same call. The context is already present on its first turn.
billingHi Dana - I see you are asking about an unrecognised charge. Let me pull that up.
callerYou already know?
billingIt came through with your call.
One handoff. The caller is not asked anything twice.

An example written from the code beside it, not a recording. Replays at reading pace.

Why it holds

The platform holds interaction state, so the handoff carries it. On a stateless CPaaS this is application code you write and maintain yourself.

How it works

Two agents run on one AgentServer: /intake and /billing-specialist. Both agents set params.persist_global_data = true, so the platform saves global_data to a channel variable when one AI session ends and restores it for the next. They also set params.transfer_summary = true, giving the next agent a summary of the conversation so far.

The intake tool route_caller writes what it learned from the handler, not through the model:

{"set_global_data": {"caller_name": "Dana Whitfield", "intake_reason": "...", "verified": true}}
{"SWML": {"version": "1.0.0", "sections": {"main": [{"transfer": {"dest": "https://<host>/billing-specialist"}}]}}, "transfer": "true"}

The transfer URL carries none of the context. The billing agent’s prompt reads it directly, ${global_data.caller_name} and ${global_data.intake_reason}, so its first turn already knows who is calling and why.

One SDK note: in 3.0.1, FunctionResult.execute_swml(..., transfer=True) places the transfer flag inside the SWML document. The documented action shape, and what FunctionResult.connect() emits, is a sibling "transfer": "true", so this recipe builds that action explicitly.

Limitations

Context travels with the call, not with the caller. Recognising a returning caller across separate calls is a different problem and needs durable storage keyed to the contact.

What to change first

Add a field during intake and read it from the receiving agent to find the boundary of what survives.