Handoffs
Multi-agent collaboration via delegation and handoffs.
Complex agentic workflows often benefit from dividing responsibilities among multiple specialized agents (e.g., triage, billing, support). Chai AI SDK supports seamless one-way agent handoffs, allowing one agent to delegate a conversation to another mid-run.
Defining Handoffs
Handoffs are defined by passing target agents to the handoffs array in the Agent constructor. You can also specify maxHandoffs (default is 5) to limit the total number of agent transfers allowed in a single run:
import { Agent } from "chai-ai";
import { openai } from "@chai-ai/openai";
const billingAgent = new Agent({
name: "billing-agent",
description: "Handles subscriptions, invoices, refunds, and billing issues.",
instructions: "You are a billing specialist. Help the customer with billing queries.",
model: openai("gpt-4o-mini"),
});
const triageAgent = new Agent({
name: "triage-agent",
instructions: "You are a triage agent. Analyze user queries and hand off to the billing agent if needed.",
model: openai("gpt-4o-mini"),
handoffs: [billingAgent],
maxHandoffs: 3,
});description: A one-sentence description explaining what the target agent does. This is used by the parent agent's model to determine when to hand off to it.maxHandoffs: Caps total transitions across the whole run to prevent infinite delegation loops.
How It Works: Synthetic Tool Calls
Handoffs are implemented entirely as synthetic tool calls under the hood. No custom provider extensions are required:
- Synthetic Tool Creation: During initialization, the parent agent sanitizes each target agent's name into a tool name (e.g.,
"billing-agent"becomestransfer_to_billing_agent). - Declaration Injection: The synthetic tools are injected into the tools array sent to the model.
- Execution Interception: When the model calls
transfer_to_billing_agent, the SDK intercepts the execution before running any real tools. It swaps theactiveAgentfor the target agent and resumes the outer run loop using the target agent's instructions, model, and toolset. - Step Budget Reset: Each target agent receives a fresh step budget (
maxSteps) to perform its work.
Context Preservation
When a handoff occurs, the conversation history is preserved:
- The
messagesarray remains intact. - The target agent's system instructions are appended as an additional system message rather than replacing the prior system prompt. Both Google and OpenAI providers correctly support multiple system-role messages.
Sequence Trace
Here is how a triage agent hands off a payment issue to the billing agent:
triageAgent.generate({ prompt: "I was charged twice on my card." })
→ activeAgent = triageAgent; visited = {"triage-agent"}
→ Model Call (triageAgent)
← Model requests tool call: transfer_to_billing_agent({ reason: "Double charge" })
→ Intercept handoff: toAgent = billingAgent, blocked = false
→ Append billingAgent.instructions as a new system message
→ activeAgent = billingAgent; visited = {"triage-agent", "billing-agent"}; hops = 1
→ Restart run loop using billingAgent's instructions & model
→ Model Call (billingAgent)
← Model responds: "I can help you look at that double charge..."
← Returns final responseLoop Prevention & Max Handoffs
If an agent attempts to hand off to an agent already visited in the current run, or if the maxHandoffs threshold is exceeded:
- The handoff is blocked.
- A tool failure response is sent back to the model:
{"error": "Cannot hand off to <agent>: already visited / maxHandoffs exceeded"}. - The model sees the failure and can apologize or find an alternative path without crashing the application.