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

  1. What Changed in Eino v0.9.21
  2. Three Coordination Patterns
  3. Recommended Architecture
  4. Runnable AgentTool Example
  5. Context, State, and Authority
  6. Failure and Cost Budgets
  7. Evaluation Protocol
  8. Common Failure Modes
  9. Frequently Asked Questions
  10. 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.NewChatModelAgent for a model-backed agent loop
  • adk.NewAgentTool for wrapping an adk.Agent as an Eino tool
  • adk.NewRunner for running an agent and consuming events
  • adk.WithCallbacks for 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.

flowchart LR A["Validated request"] --> B["Policy router"] B -->|"billing"| C["Billing specialist"] B -->|"technical"| D["Technical specialist"] B -->|"unknown"| E["Safe fallback"] C --> F["Normalized result"] D --> F E --> F

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:

  1. The parent chooses a specialist using tool metadata.
  2. The child receives a bounded input instead of inheriting every message by default.
  3. The child returns text through the tool result.
  4. 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.

Use application code for invariants and an agent only for decisions that require semantic judgment.

flowchart TD A["Request + identity"] --> B["Authentication and policy"] B --> C{"Deterministic route?"} C -->|"yes"| D["Selected specialist"] C -->|"no"| E["Orchestrator ChatModelAgent"] E --> F["Specialist AgentTool"] D --> G["Output validation"] F --> G G --> H["Audit + response"]

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.

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

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

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

Primary Sources