Note: The former Spring Boot example moduleIn the handoffs pattern, behavior changes dynamically based on state. Tools update a state variable (e.g.agentscope-examples/multiagent-patterns/was removed during the 2.0 package refactor. Use the code snippets on this page as the reference implementation. For other runnable samples, seeagentscope-examples/documentation/.
active_agent) that persists across turns; the graph reads this variable to route to different agents. This pattern is well-suited to customer support and sales flows where control transfers between specialized agents (e.g. sales vs. support) via tool calls.
The term handoffs is commonly used for using tool calls (such as transfer_to_sales_agent or transfer_to_support) to transfer control between agents or states (see e.g. OpenAI Agents.
Overview
Key characteristics:- State-driven routing: The graph routes to different agent nodes based on a state variable (e.g.
active_agent). - Tool-based transitions: Handoff tools update that state variable so that when the current node completes, the graph follows conditional edges to the next agent (or end).
- Direct user interaction: Each agent handles the user message in turn; handoff tools only change who handles the next turn.
- Persistent state: State survives across conversation turns within the graph.
Architecture
With AgentScope and Spring AI Graph, the implementation uses:- Separate agents as graph nodes: e.g. a sales agent and a support agent, each implemented as an
AgentScopeAgent(ReActAgent + Toolkit). - Handoff tools: Registered on each agent’s Toolkit. When the model calls a handoff tool, the tool uses
ToolContextHelper.getStateForUpdate(toolContext)to setactive_agent(or similar). The graph declares that key with a strategy (e.g.ReplaceStrategy) so the update is merged when the node completes. - Conditional edges: After each agent node, a routing action reads
active_agentand either goes to another agent node or toEND.
Implementation
Step 1: Define state keys and graph state
Define constants for state keys and agent node names, and create aStateGraph with key strategies so that handoff tools can update routing state (e.g. active_agent with ReplaceStrategy).
Step 2: Create handoff tools
Each handoff tool is an AgentScope@Tool with ToolContext auto-injected. The tool updates the graph state via ToolContextHelper.getStateForUpdate(toolContext) so that when the node completes, the graph’s conditional edges see the new active_agent and route accordingly.
Transfer to support (used by the sales agent):
toolkit.registerTool(TransferToSupportTool.create()) and toolkit.registerTool(TransferToSalesTool.create()).
Step 3: Build the agents (AgentScopeAgent + Toolkit)
Create a sales and a support agent asAgentScopeAgent, each with its own ReActAgent, system prompt, and Toolkit that includes the appropriate handoff tool.
Step 4: Add nodes and conditional edges
Add both agents as nodes, then wire START and post-node routing based onactive_agent:
- Initial route: From START, route to
sales_agentorsupport_agent(e.g. default tosales_agentifactive_agentis unset). - After sales: From
sales_agent, ifactive_agentissupport_agentgo tosupport_agent, else go toEND. - After support: From
support_agent, ifactive_agentissales_agentgo tosales_agent, else go toEND.
active_agent from state; if it is support_agent, return the support node; otherwise return "__end__".
Step 5: Invoke the graph
Invoke the compiled graph with the user input. The graph will start at the initial agent (e.g. sales), and each time an agent calls a handoff tool, the state update will cause the next step to route to the other agent or to end.Reading and updating state in tools
AgentScope tools that participate in a graph receiveToolContext (auto-injected). Use it to read or update graph state:
-
Update state (for routing): Put keys into the map returned by
ToolContextHelper.getStateForUpdate(toolContext). The graph merges this when the node completes. The graph must declare those keys with a key strategy (e.g.ReplaceStrategyforactive_agent). -
Read state: Use
ToolContextHelper.getState(toolContext)to get the currentOverAllState(e.g. to branch inside the tool or to pass context into the handoff message).
getStateForUpdate must be declared in the graph’s key strategies; otherwise the update may not affect routing.
Design choices
-
AgentScope Toolkit for both agents
Sales and support agents both useio.agentscope.core.tool.Toolkitand ReActAgent; handoff tools are standard AgentScope@Toolimplementations withToolContextfor state updates. -
State update at node completion
Tools do not redirect the graph immediately; they only update state. When the current agent node finishes, the graph’s conditional edges run and use the updatedactive_agentto choose the next node or end. -
ToolContext in tools
Handoff tools useio.agentscope.core.tool.Tooland optional@ToolParam;ToolContextis auto-injected so tools can callToolContextHelper.getStateForUpdate(toolContext)(and optionallygetState(toolContext)).
Example project
The handoffs pattern (sales + support with handoff tools) is illustrated by the snippets below:- Highlights:
AgentScopeHandoffsConfig(graph, agents, routing),TransferToSalesTool,TransferToSupportTool,RouteInitialAction,RouteAfterSalesAction,RouteAfterSupportAction, andAgentScopeHandoffsServiceto invoke the graph.
agentscope.runner.enabled=true in application.yml to run the demo on startup. Default port is 8089.
Related Documentation
- Pipeline - Sequential and parallel agent execution
- Routing - Classify and route to specialist agents
- Supervisor - Central supervisor with specialized agents as tools
- MsgHub - Message broadcasting for multi-agent conversations
- Agent as Tool - Registering an agent as a tool for another agent
- Tool System - AgentScope tools and Toolkit