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

  1. Key Takeaways
  2. Why Multi-Agent
  3. Router Pattern: Intent-Based Routing
  4. Supervisor Pattern: Hierarchical Coordination
  5. Swarm Pattern: Peer-to-Peer Collaboration
  6. Agent as Tool: Hierarchical Composition
  7. State Sharing and Isolation
  8. Pattern Comparison
  9. Practice: Multi-Agent Code Review System
  10. Best Practices
  11. FAQ
  12. Summary
  13. 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

graph TD A["User Input"] --> B["Router Agent"] B -->|"Code-related"| C["Code Agent"] B -->|"Data Analysis"| D["Data Agent"] B -->|"General Q&A"| E["General Agent"] C --> F["Output"] D --> F E --> F

Core Implementation

go
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

graph TD A["User Task"] --> B["Supervisor Agent"] B -->|"Assign research"| C["Researcher Worker"] B -->|"Assign writing"| D["Writer Worker"] B -->|"Assign review"| E["Reviewer Worker"] C -->|"Return findings"| B D -->|"Return draft"| B E -->|"Return feedback"| B B --> F["Synthesized Output"]

Core Implementation

go
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

go
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.

go
// 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

go
// 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).

go
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

go
// 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

go
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.