agents-go
Building agents

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 / OnExit fire as the agent becomes / stops being active (the hooks a handoff runs); OnUserTurnCompleted runs 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

On this page