TL;DR
In Eino v0.9.21, the safest default for multi-agent coordination is not an unrestricted swarm. Use deterministic routing when policy can decide ownership, and use a ChatModelAgent with specialist agents exposed through adk.NewAgentTool when the model must delegate. Eino still contains transfer-based Supervisor, Sequential, Parallel, and Loop APIs, but their source documentation marks them not recommended because full-context transfer has not shown an empirical advantage. Treat every agent boundary as a typed, budgeted, observable trust boundary.
Table of Contents
- What Changed in Eino v0.9.21
- Three Coordination Patterns
- Recommended Architecture
- Runnable AgentTool Example
- Context, State, and Authority
- Failure and Cost Budgets
- Evaluation Protocol
- Common Failure Modes
- Frequently Asked Questions
- Primary Sources
What Changed in Eino v0.9.21
Eino's current ADK makes an important design distinction: calling a specialist as a tool is the recommended composition path, while full-context agent transfer is a specialized compatibility path.
The v0.9.21 source exposes:
adk.NewChatModelAgentfor a model-backed agent loopadk.NewAgentToolfor wrapping anadk.Agentas an Eino tooladk.NewRunnerfor running an agent and consuming eventsadk.WithCallbacksfor invocation-scoped observability- checkpoint and resume APIs for interruptible execution
- transfer, Supervisor, Sequential, Parallel, and Loop APIs that remain available but carry explicit "NOT RECOMMENDED" guidance
This changes how older Router/Supervisor/Swarm advice should be read. The names describe coordination shapes, not equally good defaults.
| Pattern | Control owner | Context passed | Best fit | Primary risk |
|---|---|---|---|---|
| Deterministic router | application code | explicit request contract | stable domains and policy routing | stale or incomplete rules |
| Orchestrator + AgentTool | parent agent | explicit tool arguments, optionally history | dynamic delegation with bounded specialists | nested cost and opaque tool choice |
| Full-context transfer | current agent | conversation history | rare workflows that truly change conversational owner | context, authority, and failure coupling |
The AI Agent glossary explains the single-agent loop, while the multi-agent glossary defines the broader coordination model.
Three Coordination Patterns
Deterministic routing
Deterministic routing should be the first choice when a trusted field, entitlement, locale, product area, or validated classifier can choose the worker. It is cheap, testable, and easy to audit.
Do not ask an LLM to make a decision that an exact rule can make. A model router is useful only when the labels are semantically ambiguous and classification errors are measured on representative data.
Orchestrator with AgentTool
adk.NewAgentTool gives the parent agent a normal tool boundary. The child must provide a non-empty Name and Description; by default the tool accepts a JSON object with a request string. adk.WithAgentInputSchema can replace that contract, and adk.WithFullChatHistoryAsInput can pass history when it is genuinely needed.
The boundary matters:
- The parent chooses a specialist using tool metadata.
- The child receives a bounded input instead of inheriting every message by default.
- The child returns text through the tool result.
- Child
Exit, transfer, and loop-break actions stay inside the tool boundary; interrupts can propagate for checkpoint/resume.
This pattern supports least privilege: each specialist can receive only the tools, credentials, data scope, and budget required for its role.
Full-context handoff
A handoff changes which agent owns the conversation. It can be useful when a specialist must continue a long interaction directly, but it should not be treated as a generic collaboration primitive.
Full history creates three coupled risks:
- Authority expansion: the recipient may see data or instructions outside its role.
- Context dilution: irrelevant turns compete with the facts needed for the new task.
- Recovery ambiguity: retries and resumes must know which agent owns each side effect.
If a handoff is retained, define allowed destinations, maximum transfers, a termination rule, context filtering, and an explicit ownership record.
Recommended Architecture
Use application code for invariants and an agent only for decisions that require semantic judgment.
The parent agent should receive a small tool catalog with mutually distinct descriptions. The child agent should receive a task contract, not a vague instruction such as "help with this request."
A useful delegation contract includes:
| Field | Purpose |
|---|---|
| objective | exact result the child must produce |
| evidence | identifiers or source material already known |
| constraints | scope, tenant, region, and forbidden actions |
| output schema | fields and maximum size |
| deadline | remaining wall-clock budget |
| call budget | remaining model and tool calls |
| idempotency key | deduplicates side effects |
Runnable AgentTool Example
The following program uses Eino v0.9.21 without an external model. It implements two deterministic specialist agents, wraps each with NewAgentTool, routes a request, and invokes the selected tool. The same boundary can be placed in a ChatModelAgent.ToolsConfig when delegation must be model-driven.
package main
import (
"context"
"fmt"
"log"
"strings"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
)
type specialist struct {
name string
description string
prefix string
}
func (s *specialist) Name(context.Context) string {
return s.name
}
func (s *specialist) Description(context.Context) string {
return s.description
}
func (s *specialist) Run(
ctx context.Context,
input *adk.AgentInput,
_ ...adk.AgentRunOption,
) *adk.AsyncIterator[*adk.AgentEvent] {
iter, gen := adk.NewAsyncIteratorPair[*adk.AgentEvent]()
go func() {
defer gen.Close()
if err := ctx.Err(); err != nil {
gen.Send(&adk.AgentEvent{Err: err})
return
}
request := ""
if len(input.Messages) > 0 {
request = input.Messages[len(input.Messages)-1].Content
}
reply := fmt.Sprintf("%s handled: %s", s.prefix, request)
gen.Send(adk.EventFromMessage(
schema.AssistantMessage(reply, nil),
nil,
schema.Assistant,
"",
))
}()
return iter
}
func chooseTool(input string, tools map[string]tool.InvokableTool) tool.InvokableTool {
if strings.Contains(strings.ToLower(input), "invoice") {
return tools["billing"]
}
return tools["technical"]
}
func main() {
ctx := context.Background()
tools := map[string]tool.InvokableTool{
"billing": adk.NewAgentTool(ctx, &specialist{
name: "billing",
description: "Resolve invoice and payment questions.",
prefix: "billing",
}).(tool.InvokableTool),
"technical": adk.NewAgentTool(ctx, &specialist{
name: "technical",
description: "Diagnose API and integration failures.",
prefix: "technical",
}).(tool.InvokableTool),
}
request := "Why was invoice INV-42 charged twice?"
selected := chooseTool(request, tools)
result, err := selected.InvokableRun(
ctx,
`{"request":"Why was invoice INV-42 charged twice?"}`,
)
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
// Output: billing handled: Why was invoice INV-42 charged twice?
}
Use this module pin:
require github.com/cloudwego/eino v0.9.21
For a model-driven orchestrator, place the resulting tools in adk.ChatModelAgentConfig.ToolsConfig.Tools, set a finite MaxIterations, create the agent with adk.NewChatModelAgent, and execute it through adk.NewRunner. The model implementation must support Eino's model.WithTools option.
Context, State, and Authority
Multi-agent state should be split by meaning rather than stored in one shared conversation.
| State class | Recommended owner | Examples |
|---|---|---|
| immutable request facts | request context or typed input | tenant, locale, trace ID |
| conversational reasoning | individual agent | messages and intermediate hypotheses |
| durable workflow state | transactional store | approvals, task status, checkpoints |
| evidence artifacts | object or document store | search results, reports, code patches |
| security decisions | policy engine | grants, scopes, object-level access |
context.Context is appropriate for cancellation, deadlines, trace propagation, and small request-scoped values. It is not a durable business-state store. Eino session values can support runtime instruction formatting, but authorization must still come from trusted server-side policy.
Avoid copying the full parent transcript into every specialist. Pass the minimum evidence needed for the subtask, include provenance, and return a compact structured result. This reduces prompt injection propagation and makes evaluations easier to attribute.
Failure and Cost Budgets
Multi-agent execution multiplies dependencies, so every run needs a shared budget.
deadline = min(request deadline, workflow SLA)
remaining calls = global call budget - completed calls
remaining tokens = global token budget - measured usage
allowed depth = configured maximum - current depth
Enforce at least:
- one end-to-end deadline propagated through
context.Context - per-model and per-tool timeouts shorter than the parent deadline
- maximum agent depth, model iterations, and tool calls
- bounded parallelism at provider and downstream-service boundaries
- output byte limits before results re-enter another model context
- retry policy based on idempotency and error class
- cancellation propagation and checkpoint rules for long-running work
Do not independently grant each child a full budget. Five children with a ten-call allowance each create a fifty-call system even if the parent appears limited to ten.
Evaluation Protocol
Compare coordination patterns on the same frozen task set. A single-agent baseline is mandatory because multi-agent designs often spend more tokens without improving the outcome.
| Dimension | Example measure |
|---|---|
| outcome quality | task-specific pass rate or rubric score |
| routing quality | correct owner, fallback rate, abstention quality |
| evidence quality | supported claims and citation precision |
| efficiency | wall time, model tokens, tool calls, child count |
| reliability | timeout, retry, duplicate side effect, resume success |
| security | denied unauthorized calls, cross-tenant leakage |
| operability | traces with complete parent-child attribution |
Anthropic's production report found large gains for breadth-first research tasks, but also reported substantially higher token use and weak fit for tightly coupled work. Those measurements are evidence for that workload, not a universal multiplier. Reproduce the comparison on your own task distribution.
Common Failure Modes
Every role receives every tool
Tool overload harms selection and expands blast radius. Give each specialist a minimal allowlist and enforce it in code.
The router silently falls back
A fallback can conceal classifier drift. Record the reason, confidence, selected route, and whether a human or deterministic rule overrode it.
Retrying the whole workflow
Whole-run retries duplicate completed side effects. Persist step status and idempotency keys, then retry only the failed operation when its contract permits it.
Delegation has no completion contract
A child that can search indefinitely will consume the global budget. Require an objective, output schema, stop condition, and maximum call count.
A handoff is mistaken for authorization
Receiving control does not grant access. Re-evaluate identity, tenant, object, and action permissions at every tool call.
Frequently Asked Questions
Should routing be implemented with Eino Graph or application code?
Use application code when the route follows trusted fields or stable rules. Use an Eino Graph when routing is part of a larger typed workflow with branches, persistence, or reusable components. Use a model only when semantic ambiguity justifies its cost and error rate.
When is full chat history appropriate for AgentTool?
Use adk.WithFullChatHistoryAsInput only when the child must reason about the conversation itself. For data lookup, calculation, review, or extraction, a narrow typed request is usually safer and cheaper.
Are parallel agents always faster?
No. They help only when subtasks are independent enough to run concurrently. Shared dependencies, rate limits, synthesis cost, and the slowest child can erase the latency benefit.
What should be traced?
Trace the parent run, route decision, child invocation, model call, tool call, retry, and final validation. Record identifiers and bounded metrics; avoid raw prompts, credentials, personal data, and unrestricted tool output.
Summary
Reliable Eino multi-agent systems are built from explicit boundaries, not from the number of agents. Start with deterministic routing, use AgentTool for bounded delegation, and adopt full-context transfer only when a measured workload justifies it. Share facts and budgets, isolate reasoning and authority, and evaluate the final outcome against a simpler baseline.
Related Resources
- Build Your First Eino ADK Agent
- Eino Components: ChatModel, Tool, and Retriever
- Eino Production Deployment and Observability
- AI Agent Glossary
- Multi-Agent Systems Glossary