Problem Statement
FinishReasonTransfer exists in agent/event.go as a declared constant but is a placeholder with no implementation. There is no mechanism for one agent to hand off a conversation to another agent while preserving the session context.
The current workaround is agent-as-tool (agenttool), but this creates a fresh, isolated session for the child agent. The child cannot see the parent's conversation history, and the parent only gets back a text summary. This is delegation, not handoff — the conversation doesn't transfer.
Real agent systems need true handoffs where:
- Agent A decides Agent B is better suited for the current conversation
- The conversation (session) transfers to Agent B with full history
- Agent B continues the same session, not a new one
- The user experiences a seamless transition
Proposed Solution
Agent tree structure
Agents form a tree via a SubAgents relationship. An agent can transfer to:
- Sub-agents (children): always allowed
- Parent: transfer back up (configurable, opt-in)
- Peers (siblings): transfer to a sibling agent (configurable, opt-in)
agent, _ := llmagent.New("coordinator", systemPrompt, model,
llmagent.WithSubAgents(billingAgent, techAgent, accountAgent),
llmagent.WithTransferPolicy(agent.TransferPolicy{
AllowTransferToParent: true,
AllowTransferToPeers: false,
}),
)
Transfer tool
A built-in transfer_to_agent tool that the LLM can call to initiate a handoff:
// LLM sees this tool in its available tools:
// transfer_to_agent(agent_name: string, reason: string)
//
// When called:
// 1. Validates the target agent exists in the tree
// 2. Returns FinishReasonTransfer
// 3. The runner/orchestrator finds the target agent
// 4. Runs the target agent with the SAME session (full conversation history)
The transfer tool is auto-injected when an agent has sub-agents. The tool description includes the names and descriptions of available transfer targets so the LLM knows where it can route.
Same-session handoff
The critical difference from agent-as-tool: the target agent receives the same session reference, not a copy. It sees the full conversation history and continues appending to it. The user's experience is seamless — they don't know (or care) that a different agent took over.
Agent discovery
FindAgent(name) traverses the agent tree to locate transfer targets:
type Agent interface {
// ...existing methods...
SubAgents() []Agent
FindAgent(name string) Agent
}
Use Case Example
Customer support routing:
coordinator := llmagent.New("support-coordinator",
"You are a customer support coordinator. Route conversations to the appropriate specialist.",
model,
llmagent.WithSubAgents(
billingAgent, // Handles billing questions, refunds, invoices
techAgent, // Handles technical issues, debugging, configuration
accountAgent, // Handles account management, permissions, settings
),
)
// User: "I'm being charged twice for my subscription"
// Coordinator LLM decides this is billing → calls transfer_to_agent("billing")
// Billing agent takes over the SAME session, sees full conversation history
// Billing agent: "I can see your account. Let me look into the duplicate charges..."
Multi-level handoff:
// techAgent itself has sub-agents:
techAgent := llmagent.New("tech-support", ...,
llmagent.WithSubAgents(networkAgent, databaseAgent, authAgent),
)
// coordinator → techAgent → databaseAgent (nested handoff)
Why This Matters
- Natural conversation flow: Users expect to explain their problem once. With agent-as-tool delegation, context is lost and users must repeat themselves. Handoff preserves the full conversation.
- Specialization at scale: As agent systems grow, no single agent can handle all tasks. Handoff enables routing to specialists without the overhead of re-summarizing context.
- Existing infrastructure:
FinishReasonTransfer already exists, the event system supports it, and the A2A adapter has no special handling needed. The main work is the transfer tool and agent tree traversal.
- Composability with orchestration: Handoffs compose naturally with workflow patterns — a router agent is essentially a classifier that uses handoffs instead of delegation.
Problem Statement
FinishReasonTransferexists inagent/event.goas a declared constant but is a placeholder with no implementation. There is no mechanism for one agent to hand off a conversation to another agent while preserving the session context.The current workaround is agent-as-tool (
agenttool), but this creates a fresh, isolated session for the child agent. The child cannot see the parent's conversation history, and the parent only gets back a text summary. This is delegation, not handoff — the conversation doesn't transfer.Real agent systems need true handoffs where:
Proposed Solution
Agent tree structure
Agents form a tree via a
SubAgentsrelationship. An agent can transfer to:Transfer tool
A built-in
transfer_to_agenttool that the LLM can call to initiate a handoff:The transfer tool is auto-injected when an agent has sub-agents. The tool description includes the names and descriptions of available transfer targets so the LLM knows where it can route.
Same-session handoff
The critical difference from agent-as-tool: the target agent receives the same session reference, not a copy. It sees the full conversation history and continues appending to it. The user's experience is seamless — they don't know (or care) that a different agent took over.
Agent discovery
FindAgent(name)traverses the agent tree to locate transfer targets:Use Case Example
Customer support routing:
Multi-level handoff:
Why This Matters
FinishReasonTransferalready exists, the event system supports it, and the A2A adapter has no special handling needed. The main work is the transfer tool and agent tree traversal.