TL;DR
When a single AI Agent hits the ceiling — context window overload, tool sprawl, and lack of specialization — multi-agent systems offer a path forward through role specialization, parallel processing, and emergent collaboration. This article explores three core coordination patterns supported by the Eino framework: Router (intent-based routing), Supervisor (hierarchical management), and Swarm (peer-to-peer handoff). We demonstrate each with complete Go implementations and cap it off with a practical multi-agent code review system.
Table of Contents
- Key Takeaways
- Why Multi-Agent
- Router Pattern: Intent-Based Routing
- Supervisor Pattern: Hierarchical Coordination
- Swarm Pattern: Peer-to-Peer Collaboration
- Agent as Tool: Hierarchical Composition
- State Sharing and Isolation
- Pattern Comparison
- Practice: Multi-Agent Code Review System
- Best Practices
- FAQ
- Summary
- Related Resources
Key Takeaways
- Router Pattern: A router agent classifies user intent and delegates to domain-expert agents — low latency, clean architecture
- Supervisor Pattern: A manager agent plans, assigns tasks to workers, and synthesizes results — ideal for complex multi-step workflows
- Swarm Pattern: No central coordinator, agents communicate peer-to-peer with autonomous handoff — best for open-ended collaboration
- Agent as Tool: Any compiled Graph or Agent can be exposed as a Tool, enabling hierarchical agent architectures
- State Strategy: Share facts (via context), isolate reasoning (independent conversation histories) — balance consistency with autonomy
Why Multi-Agent
A single agent faces clear capability ceilings when tackling real-world complex tasks:
| Limitation | Manifestation | Multi-Agent Solution |
|---|---|---|
| Context window | Information overload causes forgetting critical details | Each agent processes only relevant subset |
| Tool overload | Too many tools decrease selection accuracy | Each agent binds only necessary tools |
| Lack of specialization | Generalists underperform domain experts | Role-specialized system prompts |
| Serial execution | Complex tasks take too long | Parallel subtask processing |
| Single point of failure | One error impacts everything | Independent reasoning, localized failures |
These limitations are systematically addressed in Eino through Graph orchestration + Agent composition. In our previous article, we built a single-agent application. Now let's enter the world of multi-agent systems.
Router Pattern: Intent-Based Routing
The Router pattern is the simplest form of multi-agent coordination — a router classifies user intent and forwards the request to the appropriate specialist agent.
Architecture Diagram
Core Implementation
package main
import (
"context"
"github.com/cloudwego/eino/compose"
"github.com/cloudwego/eino/schema"
)
func buildRouterGraph(ctx context.Context) (*compose.Runnable[string, *schema.Message], error) {
graph := compose.NewGraph[string, *schema.Message]()
// Router node: classify user intent
graph.AddLambdaNode("router", compose.InvokableLambda(func(ctx context.Context, input string) (string, error) {
// Use a lightweight model for fast intent classification
classification, err := classifyIntent(ctx, input)
if err != nil {
return "general", nil // fallback to general
}
return classification, nil
}))
// Add specialist agent nodes
graph.AddChatModelNode("code_agent", codeModel, &compose.ChatModelNodeConfig{
SystemPrompt: "You are a senior Go developer specializing in code writing, debugging, and optimization.",
})
graph.AddChatModelNode("data_agent", dataModel, &compose.ChatModelNodeConfig{
SystemPrompt: "You are a data analysis expert skilled in SQL, visualization, and statistics.",
})
graph.AddChatModelNode("general_agent", generalModel, &compose.ChatModelNodeConfig{
SystemPrompt: "You are a general-purpose assistant for everyday questions.",
})
// Set up edges and branching
graph.AddEdge(compose.START, "router")
graph.AddBranch("router", compose.NewBranch(func(ctx context.Context, intent string) (string, error) {
switch intent {
case "code":
return "code_agent", nil
case "data":
return "data_agent", nil
default:
return "general_agent", nil
}
}))
graph.AddEdge("code_agent", compose.END)
graph.AddEdge("data_agent", compose.END)
graph.AddEdge("general_agent", compose.END)
return graph.Compile(ctx)
}
When to Use
- Customer support: Route by issue type to pre-sales, post-sales, or technical support
- IDE assistants: Distinguish between code generation, documentation lookup, and configuration requests
- Data platforms: Split SQL queries, report generation, and anomaly analysis
Supervisor Pattern: Hierarchical Coordination
The Supervisor pattern introduces a "manager" agent that understands the full task scope, creates execution plans, assigns subtasks to worker agents, and synthesizes the final result.
Architecture Diagram
Core Implementation
package main
import (
"context"
"fmt"
"github.com/cloudwego/eino-ext/devops/adk"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
)
// workerAsTool wraps an Agent as a Tool for the Supervisor to invoke
func workerAsTool(name, description string, agent *adk.ChatModelAgent) tool.BaseTool {
return tool.NewTool(
name,
description,
func(ctx context.Context, input string) (string, error) {
messages := []*schema.Message{
schema.UserMessage(input),
}
result, err := agent.Generate(ctx, messages)
if err != nil {
return "", fmt.Errorf("worker %s failed: %w", name, err)
}
return result.Content, nil
},
)
}
func buildSupervisorAgent(ctx context.Context) (*adk.ChatModelAgent, error) {
// Create Worker Agents
researcher, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: researchModel,
SystemPrompt: "You are a technical researcher. Collect and organize technical materials into structured reports.",
})
writer, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: writerModel,
SystemPrompt: "You are a technical writer. Produce high-quality articles from research materials.",
})
reviewer, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: reviewModel,
SystemPrompt: "You are a strict technical reviewer. Check accuracy, completeness, and readability.",
})
// Create Supervisor Agent with Workers exposed as Tools
supervisor, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: supervisorModel,
Tools: []tool.BaseTool{
workerAsTool("researcher", "Call the researcher to gather technical materials. Input: research topic", researcher),
workerAsTool("writer", "Call the writer to draft an article. Input: writing requirements and references", writer),
workerAsTool("reviewer", "Call the reviewer to audit content. Input: article to review", reviewer),
},
SystemPrompt: `You are a project manager coordinating a technical content team.
Workflow:
1. Analyze the user's request and decompose into subtasks
2. Call researcher to gather materials
3. Pass materials to writer for drafting
4. Have reviewer audit the draft
5. If issues found, have writer revise
6. Synthesize and return the final result`,
MaxSteps: 10,
})
return supervisor, err
}
When to Use
- Content production: Research → Write → Review → Finalize pipeline
- Code review: Analyze → Review → Fix iteration loops
- Project planning: Requirements → Design → Risk assessment multi-dimensional review
Swarm Pattern: Peer-to-Peer Collaboration
The Swarm pattern (inspired by the OpenAI Swarm concept) features agents in a peer relationship with no central coordinator. Each agent has autonomous decision-making authority and can hand off conversation control to another agent.
Core Concepts
- No centralization: No fixed "manager" — any agent can take over
- Handoff mechanism: The current agent proactively transfers to a more suitable agent when a problem exceeds its scope
- Context passing: Essential conversation context is carried through handoffs
Core Implementation
package main
import (
"context"
"fmt"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
"github.com/cloudwego/eino-ext/devops/adk"
)
// HandoffResult represents a handoff operation
type HandoffResult struct {
TargetAgent string
Context string
}
// buildSwarmAgents creates a set of peer-collaborating agents
func buildSwarmAgents(ctx context.Context) (map[string]*adk.ChatModelAgent, error) {
agents := make(map[string]*adk.ChatModelAgent)
// Sales Agent
salesAgent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: model,
SystemPrompt: `You are a sales consultant. Handle product inquiries and purchase questions.
If the user asks technical questions, call handoff_to_tech to transfer to tech support.
If the user wants a refund, call handoff_to_refund to transfer to the refund specialist.`,
Tools: []tool.BaseTool{
makeHandoffTool("handoff_to_tech", "Transfer to technical support", "tech"),
makeHandoffTool("handoff_to_refund", "Transfer to refund specialist", "refund"),
},
})
// Technical Support Agent
techAgent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: model,
SystemPrompt: `You are a tech support engineer. Resolve technical issues and bug reports.
If the user wants to purchase or upgrade, call handoff_to_sales to transfer to sales.`,
Tools: []tool.BaseTool{
makeHandoffTool("handoff_to_sales", "Transfer to sales consultant", "sales"),
},
})
// Refund Agent
refundAgent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: model,
SystemPrompt: "You are a refund specialist. Handle refund requests and order issues.",
Tools: []tool.BaseTool{
makeHandoffTool("handoff_to_sales", "Transfer to sales consultant", "sales"),
},
})
agents["sales"] = salesAgent
agents["tech"] = techAgent
agents["refund"] = refundAgent
return agents, nil
}
// runSwarm executes the Swarm conversation loop
func runSwarm(ctx context.Context, agents map[string]*adk.ChatModelAgent, input string) (string, error) {
currentAgent := "sales" // default entry point
currentInput := input
for i := 0; i < 5; i++ { // max 5 handoffs
agent := agents[currentAgent]
result, err := agent.Generate(ctx, []*schema.Message{
schema.UserMessage(currentInput),
})
if err != nil {
return "", err
}
// Check if a handoff occurred
if handoff := extractHandoff(result); handoff != nil {
currentAgent = handoff.TargetAgent
currentInput = handoff.Context
continue
}
return result.Content, nil
}
return "", fmt.Errorf("exceeded max handoff limit")
}
When to Use
- Customer service: Flexible transfers between agents with different skill sets
- Multi-role dialogue: Simulating discussions and debates between multiple personas
- Unpredictable workflows: Scenarios where execution paths cannot be pre-defined
Agent as Tool: Hierarchical Composition
In Eino, any compiled Graph or Agent can be exposed as a Tool — this is the key pattern for building hierarchical multi-agent architectures.
// Wrap a complete agent system as a Tool
func agentAsTool(name, desc string, runnable *compose.Runnable[string, string]) tool.BaseTool {
return tool.NewTool(name, desc, func(ctx context.Context, input string) (string, error) {
return runnable.Invoke(ctx, input)
})
}
// Build hierarchical structure: top-level agent calls subsystems
func buildHierarchicalSystem(ctx context.Context) (*adk.ChatModelAgent, error) {
// Subsystem 1: Code analysis agent graph
codeAnalysisGraph := buildCodeAnalysisGraph(ctx)
codeAnalysisTool := agentAsTool(
"code_analysis",
"Analyze code quality, complexity, and potential issues. Input: code snippet",
codeAnalysisGraph,
)
// Subsystem 2: Documentation generation agent graph
docGenGraph := buildDocGenGraph(ctx)
docGenTool := agentAsTool(
"doc_generation",
"Generate technical documentation for code. Input: code and context description",
docGenGraph,
)
// Top-level coordinator agent
coordinator, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: model,
Tools: []tool.BaseTool{codeAnalysisTool, docGenTool},
SystemPrompt: "You are a development assistant with access to code analysis and documentation generation subsystems.",
})
return coordinator, err
}
Advantages of hierarchical composition:
- Subsystems can be developed, tested, and iterated independently
- The top-level agent's prompt stays concise — it only needs to understand Tool interfaces
- Supports on-demand loading; unused subsystems consume no resources
State Sharing and Isolation
State management is a core challenge in multi-agent coordination. Eino provides flexible strategies:
| Strategy | Implementation | Use Case | Trade-offs |
|---|---|---|---|
| Context passing | context.WithValue |
Read-only shared facts | Simple and direct, but immutable only |
| External storage | Redis / Database | Mutable shared state | Supports concurrent R/W, but adds external dependency |
| Message passing | Tool call input/output | Inter-agent communication | Good decoupling, but token overhead |
| Independent history | Each agent maintains its own | Conversation memory | No reasoning interference, but no shared experience |
Best Practice: Share Facts, Isolate Reasoning
// Share read-only task context via Context
type TaskContext struct {
TaskID string
UserID string
Constraints []string
}
func withTaskContext(ctx context.Context, tc *TaskContext) context.Context {
return context.WithValue(ctx, taskContextKey, tc)
}
// Each agent maintains its own conversation history
type AgentState struct {
History []*schema.Message
// Only stores this agent's reasoning process
}
Pattern Comparison
| Dimension | Router | Supervisor | Swarm |
|---|---|---|---|
| Topology | Star (one-to-many) | Hierarchical (manager-worker) | Mesh (peer-to-peer) |
| Coordination | Static classification | Dynamic planning | Autonomous handoff |
| Latency | Low (single routing step) | Medium-high (multi-turn) | Unpredictable |
| Complexity fit | Low-Medium | Medium-High | High |
| Fault tolerance | Sub-agent failure can fallback | Supervisor can reassign | Naturally isolated |
| Implementation difficulty | Simple | Moderate | Higher |
| Typical scenarios | Intent dispatch, customer routing | Project management, content production | Role-play, flexible negotiation |
Practice: Multi-Agent Code Review System
Combining the patterns above, let's build a code review system with a Planner (coordinator), Reviewer (quality checker), and Fixer (patch generator).
package main
import (
"context"
"fmt"
"github.com/cloudwego/eino-ext/devops/adk"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
)
func buildCodeReviewSystem(ctx context.Context) (*adk.ChatModelAgent, error) {
// Reviewer Agent: focused on code quality inspection
reviewer, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: reviewModel,
SystemPrompt: `You are a code review expert. Analyze these dimensions:
- Logical correctness
- Performance issues
- Security vulnerabilities
- Code style compliance
Output format: [severity] file:line - description`,
})
// Fixer Agent: generates fix patches based on review feedback
fixer, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: fixerModel,
SystemPrompt: `You are a code fix expert. Generate fix patches from review feedback.
Output standard unified diff format. Ensure fixes don't introduce new issues.`,
})
// Planner (Supervisor): coordinates the entire review workflow
planner, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: plannerModel,
Tools: []tool.BaseTool{
workerAsTool("reviewer", "Run code quality review. Input: code to review", reviewer),
workerAsTool("fixer", "Generate code fixes from review feedback. Input: review comments + original code", fixer),
},
SystemPrompt: `You are the coordinator of a code review project. Workflow:
1. Receive the user's code changes
2. Call reviewer for quality inspection
3. If issues found, call fixer to generate patches
4. Call reviewer again to verify fixes
5. Output the final review report and fix suggestions
If reviewer finds no issues twice in a row, output approval directly.`,
MaxSteps: 8,
})
return planner, err
}
// Usage example
func main() {
ctx := context.Background()
system, err := buildCodeReviewSystem(ctx)
if err != nil {
panic(err)
}
codeChange := `
func processOrder(order *Order) error {
db.Save(order)
sendEmail(order.UserEmail, "Order confirmed")
chargePayment(order.Amount)
return nil
}
`
result, _ := system.Generate(ctx, []*schema.Message{
schema.UserMessage(fmt.Sprintf("Please review the following code change:\n%s", codeChange)),
})
fmt.Println(result.Content)
}
This system demonstrates the Supervisor pattern in action: the Planner controls the workflow, while Reviewer and Fixer serve as specialized workers. The MaxSteps limit prevents infinite loops.
During development and debugging, you can use the JSON Formatter to inspect structured data passed between agents, or JSON to Go to quickly generate Go struct definitions from JSON payloads.
Best Practices
1. Start with a single agent, split on demand
Don't introduce multi-agent prematurely. Validate core logic with a single agent first. Only split when you observe clear performance bottlenecks or capability limitations.
2. Control nesting depth
Agent-as-Tool nesting should not exceed 3 levels. Each additional level roughly doubles token consumption and latency.
3. Define clear boundaries for each agent
Every agent's system prompt should explicitly specify: scope of responsibility, input/output format, and when to hand off.
4. Implement graceful degradation
// Set fallback in Router when confidence is low
if confidence < 0.6 {
return "general_agent", nil // uncertain intent -> general agent
}
5. Use Callbacks for full-chain observability
agent.WithCallbacks(
callbacks.OnStart(func(ctx context.Context, info *callbacks.RunInfo) {
log.Printf("[%s] started with input: %s", info.Name, info.Input)
}),
callbacks.OnEnd(func(ctx context.Context, info *callbacks.RunInfo) {
log.Printf("[%s] completed in %v", info.Name, info.Duration)
}),
)
FAQ
Q: How do I choose between Router, Supervisor, and Swarm patterns?
Router works best for clearly classifiable intents with low-latency requirements. Supervisor excels at complex tasks requiring planning, delegation, and synthesis. Swarm fits scenarios with unpredictable workflows needing flexible peer negotiation. Key decision factors: whether tasks are pre-classifiable and whether central coordination is needed.
Q: What are the limitations of Agent as Tool?
Main limitations: 1) Tool calls are synchronous — child agent execution blocks the parent; 2) Return values are strings, requiring serialization for complex structures; 3) Deep nesting causes token consumption to grow exponentially. Keep nesting to 3 levels maximum.
Q: How should state be shared between agents?
Recommended: pass read-only shared facts via context.Context, while each agent maintains independent conversation history. Use external storage for mutable intermediate results. Core principle: share facts, isolate reasoning.
Q: Does Eino's multi-agent system support streaming?
Yes. The Supervisor's final synthesis node can stream output. In Router mode, the routed-to sub-agent can return in Stream mode. The Graph orchestration engine handles stream propagation automatically.
Q: How do you debug multi-agent systems?
Eino's built-in Callback mechanism lets you inject logging, tracing, and metrics at each agent boundary. Set a unique RunInfo.Name per agent, integrate with OpenTelemetry for distributed tracing, and visualize the complete multi-agent call chain on your observability platform.
Summary
Multi-agent coordination is the primary path to breaking through single-agent limitations. Through Graph orchestration, Agent as Tool composition, and flexible state management, Eino natively supports three coordination patterns:
- Router: Low-latency intent dispatch for clearly classifiable scenarios
- Supervisor: Structured task planning and execution for complex multi-step workflows
- Swarm: Flexible peer collaboration for open-ended, high-uncertainty scenarios
When choosing a pattern, start by evaluating whether Router suffices. Upgrade to Supervisor when tasks require multi-turn planning. Consider Swarm when workflows cannot be pre-defined. In the first article of this Eino series, we introduced the framework landscape; this article demonstrates how to leverage these orchestration capabilities to build production-grade multi-agent systems.