examples/multiagent-patterns/handoffs-multiagent/README.md
This module implements the multiple agent subgraphs handoffs pattern. Distinct sales and support agents exist as separate nodes in a StateGraph. Handoff tools navigate between agent nodes by updating active_agent, which the parent graph's conditional edges use for routing.
Separate agents as graph nodes
Sales and support agents are separate nodes. Each invokes a ReactAgent (CompiledGraph). The parent StateGraph routes between them based on active_agent.
Handoff tools (one class per tool for clarity)
TransferToSalesTool: Support agent uses it to hand off to sales. Updates active_agent, uses returnDirect=true so the agent exits immediately.TransferToSupportTool: Sales agent uses it to hand off to support.Conditional routing
route_initial: START → sales_agent or support_agent (default: sales).route_after_sales: If active_agent is support_agent → support_agent; else → END.route_after_support: If active_agent is sales_agent → sales_agent; else → END.returnDirect on handoff tools
Handoff tools use @Tool(returnDirect = true) so the agent exits immediately after the tool. No model response is generated. The parent graph's conditional edge then routes to the target agent.
State update
Tools use ToolContextHelper.getStateForUpdate(toolContext) to set active_agent. The update is merged into the graph state when the agent node completes.
Context
The full message history flows to the next agent. For production, consider summarizing or filtering context for token efficiency.
Studio: The two agents (sales_agent, support_agent) are Spring beans; Studio discovers them automatically via context scanning. No custom AgentLoader is required.
examples/multiagent-patterns/handoffs-multiagent/
├── pom.xml
├── README.md
└── src/main/
├── java/.../handoffs/
│ ├── HandoffsApplication.java
│ ├── MultiAgentHandoffsConfig.java # StateGraph, agents, routing
│ ├── MultiAgentHandoffsService.java # invokes graph
│ ├── MultiAgentHandoffsRunner.java # optional demo runner
│ ├── route/
│ │ ├── RouteInitialAction.java # START → sales or support
│ │ ├── RouteAfterSalesAction.java # sales → support or END
│ │ └── RouteAfterSupportAction.java # support → sales or END
│ ├── state/
│ │ └── MultiAgentStateConstants.java
│ └── tools/
│ ├── TransferToSalesTool.java # support → sales handoff
│ └── TransferToSupportTool.java # sales → support handoff
└── resources/
└── application.yml
export AI_DASHSCOPE_API_KEY=your-keyFrom the repo root or module directory:
cd examples/multiagent-patterns/handoffs-multiagent
./mvnw -B package -DskipTests
Set handoffs.runner.enabled=true (in application.yml or env), then start the app. The runner sends a support-style query; the sales agent (default) may hand off to support.
# application.yml: handoffs.runner.enabled: true
# or
export HANDOFFS_RUNNER_ENABLED=true
java -jar target/handoffs-multiagent-0.0.1-SNAPSHOT.jar
Or run without the demo (app starts and waits for your own calls):
./mvnw spring-boot:run
Start the app (without the runner), then open the built-in chat UI in your browser:
./mvnw spring-boot:run
# Open http://localhost:8080/chatui/index.html
The UI lets you type messages and see Sales / Support agent responses and handoffs in real time.
@Autowired
MultiAgentHandoffsService service;
var result = service.run("Hi, I'm having trouble with my account login. Can you help?");
result.messages().forEach(msg -> System.out.println(msg.getText()));
spring.ai.dashscope.api-key
Required. Defaults to AI_DASHSCOPE_API_KEY env var.
handoffs.runner.enabled
If true, runs the multi-agent handoffs demo on startup. Default: false.