Agents & handoffs
The persona, per-agent models and hooks, and transferring control.
An Agent is the persona: instructions, tools, optional per-agent models, and
lifecycle hooks. It holds no live connection — one agent is reusable across
sessions, and a session can swap which agent is active. Keeping the persona
separate from the runtime (AgentSession)
is what makes handoffs cheap: you switch the active agent — even its models —
without tearing down the session or its transport.
Define an agent
agent := agents.MustNewAgent(agents.AgentOptions{
Instructions: "You are a friendly booking assistant.",
Tools: []tool.Entry{bookTable},
LLM: model, // optional per-agent model; else the session default
OnEnter: func(ctx context.Context, ac *agents.AgentContext) error {
// e.g. greet the caller when this agent becomes active
return nil
},
})- Per-agent models (
STT/LLM/TTS/VAD) override the session default while this agent is active — so a handoff can change persona and the underlying model/voice. - Hooks:
OnEnter/OnExitfire as the agent becomes / stops being active (the hooks a handoff runs);OnUserTurnCompletedruns after a user turn commits, before generation — the place to inspect or augment context (e.g. RAG).
Handoff — replace the active agent
A handoff transfers control for good — the new agent owns the rest of the conversation. Two ways:
From a tool (graceful drain)
Inside a tool, agents.RequestHandoff transfers control after the tool batch.
The current reply isn't cut off — it drains onto the new agent.
transfer, _ := tool.New("transfer_to_billing",
func(ctx context.Context, run *tool.RunContext, _ *struct{}) (*struct{}, error) {
agents.RequestHandoff(run, billingAgent, "Transferring you to billing.")
return &struct{}{}, nil
},
tool.WithDescription("Hand the caller off to the billing agent."),
)The third argument (returns any, typically a short string) becomes the tool
call's output and triggers a follow-up reply on the new agent; pass nil to hand
off silently and let the new agent's OnEnter drive the next turn.
Two tools in one batch requesting different target agents is a conflict — the switch is ignored and a warning is logged. Request at most one handoff per turn.
Explicitly (hard swap)
Outside a tool — from your own control logic — swap the active agent directly.
This is a hard handoff: in-flight speech is cancelled, OnExit/OnEnter run.
if err := session.UpdateAgent(ctx, specialist); err != nil {
return err
}Swaps are serialised against session close and tool-driven handoffs, so they never interleave.
Handoff vs. delegate
Handoff is one-way. When you instead need a bounded answer back — collect an address, run one survey step — delegate to a sub-agent and resume; see Tasks & sub-agents.
RequestHandoff
Route/triage from inside a tool, graceful drain. One-way.
UpdateAgent
Hard swap from outside a tool. Cancels current speech. One-way.
RunTask
Delegate a bounded job and come back with a value.
Next
- Tasks & sub-agents — delegate and resume.
- Full API: Reference → Agent.