Eino ADK: Agent Collaboration
Collaboration Overview
ADK defines collaboration and composition primitives to build multi‑agent systems. This page introduces collaboration modes, input context strategies, decision autonomy, and composition types with code and diagrams.
Collaboration Primitives
Collaboration mode
Mode Description Transfer Hand off the task to another agent. The current agent exits and does not wait for the child agent’s completion. ToolCall (AgentAsTool) Treat an agent as a tool, wait for its response, and use the output for subsequent processing. Input context strategy
Strategy Description Upstream full dialogue Receive complete conversation history from upstream agents. New task description Ignore upstream dialogue and generate a fresh summary as input for the child agent. Decision autonomy
Autonomy Description Autonomous Agent internally selects downstream agents (often via LLM) when needed. Even preset logic is considered autonomous from the outside. Preset Agent execution order is predetermined and predictable.
Composition Types
Context Passing
ADK provides two core mechanisms: History and SessionValues.
History
Concept
- History corresponds to the “Upstream full dialogue” strategy. Every
AgentEventproduced by upstream agents is saved into History. - When invoking a new Agent (Workflow/Transfer), History events are converted and appended to that Agent’s
AgentInput.
By default, assistant/tool messages from other agents are converted into user messages. This tells the current LLM: “Agent_A called some_tool with some_result. Now it’s your turn to decide.” It treats other agents’ behavior as external information rather than the current agent’s own actions, avoiding role confusion.
Filtering by RunPath
- When building
AgentInputfor an Agent, only include History events whoseRunPathbelong to the current Agent’sRunPath(equal or prefix).
Definition: RunPathA “belongs to” RunPathB when RunPathA equals RunPathB or RunPathA is a prefix of RunPathB.
Examples of RunPath in different orchestrations:
Customize via WithHistoryRewriter:
type HistoryRewriter func(ctx context.Context, entries []*HistoryEntry) ([]Message, error)
func WithHistoryRewriter(h HistoryRewriter) AgentOption
SessionValues
- Global KV store per run; thread‑safe helpers:
func GetSessionValues(ctx context.Context) map[string]any
func AddSessionValues(ctx context.Context, kvs map[string]any)
func GetSessionValue(ctx context.Context, key string) (any, bool)
func AddSessionValue(ctx context.Context, key string, value any)
Note: SessionValues are implemented via Context. Runner re‑initializes Context when starting a run, so calling AddSessionValues or AddSessionValue outside of Runner.Run will not take effect.
Inject initial values on run:
runner := adk.NewRunner(ctx, adk.RunnerConfig{Agent: agent})
iterator := runner.Run(ctx, []adk.Message{schema.UserMessage("xxx")},
adk.WithSessionValues(map[string]any{
PlanSessionKey: 123,
UserInputSessionKey: []adk.Message{schema.UserMessage("yyy")},
}),
)
Transfer SubAgents
- Emit a
Transferaction to hand off control:
event := adk.NewTransferToAgentAction("dest agent name")
- Register subagents before running:
func SetSubAgents(ctx context.Context, agent Agent, subAgents []Agent) (Agent, error)
- Notes:
- Transfer hands off control; parent does not summarize after child ends
- Child receives original input; parent output is context only
– Agents can implement OnSubAgents to handle registration callbacks.
// github.com/cloudwego/eino/adk/interface.go
type OnSubAgents interface {
OnSetSubAgents(ctx context.Context, subAgents []Agent) error
OnSetAsSubAgent(ctx context.Context, parent Agent) error
OnDisallowTransferToParent(ctx context.Context) error
}
Example: Weather + Chat via Transfer
Three ChatModelAgents: a router, a weather agent (tool‑enabled), and a general chat agent. The router uses Transfer to hand off requests based on intent.
import (
"context"
"fmt"
"log"
"os"
"github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/components/tool/utils"
"github.com/cloudwego/eino/compose"
)
func newChatModel() model.ToolCallingChatModel {
cm, err := openai.NewChatModel(context.Background(), &openai.ChatModelConfig{
APIKey: os.Getenv("OPENAI_API_KEY"),
Model: os.Getenv("OPENAI_MODEL"),
})
if err != nil {
log.Fatal(err)
}
return cm
}
type GetWeatherInput struct {
City string `json:"city"`
}
func NewWeatherAgent() adk.Agent {
weatherTool, err := utils.InferTool(
"get_weather",
"Gets the current weather for a specific city.",
func(ctx context.Context, input *GetWeatherInput) (string, error) {
return fmt.Sprintf(`the temperature in %s is 25°C`, input.City), nil
},
)
if err != nil {
log.Fatal(err)
}
a, err := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{
Name: "WeatherAgent",
Description: "This agent can get the current weather for a given city.",
Instruction: "Your sole purpose is to get the current weather for a given city by using the 'get_weather' tool. After calling the tool, report the result directly to the user.",
Model: newChatModel(),
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{weatherTool},
},
},
})
if err != nil {
log.Fatal(err)
}
return a
}
func NewChatAgent() adk.Agent {
a, err := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{
Name: "ChatAgent",
Description: "A general-purpose agent for handling conversational chat.",
Instruction: "You are a friendly conversational assistant. Your role is to handle general chit-chat and answer questions that are not related to any specific tool-based tasks.",
Model: newChatModel(),
})
if err != nil {
log.Fatal(err)
}
return a
}
func NewRouterAgent() adk.Agent {
a, err := adk.NewChatModelAgent(context.Background(), &adk.ChatModelAgentConfig{
Name: "RouterAgent",
Description: "A manual router that transfers tasks to other expert agents.",
Instruction: `You are an intelligent task router. Your responsibility is to analyze the user's request and delegate it to the most appropriate expert agent.If no Agent can handle the task, simply inform the user it cannot be processed.`,
Model: newChatModel(),
})
if err != nil {
log.Fatal(err)
}
return a
}
Wire them together and run:
import (
"context"
"fmt"
"log"
"os"
"github.com/cloudwego/eino/adk"
)
func main() {
weatherAgent := NewWeatherAgent()
chatAgent := NewChatAgent()
routerAgent := NewRouterAgent()
ctx := context.Background()
a, err := adk.SetSubAgents(ctx, routerAgent, []adk.Agent{chatAgent, weatherAgent})
if err != nil {
log.Fatal(err)
}
runner := adk.NewRunner(ctx, adk.RunnerConfig{
Agent: a,
})
// query weather
println("\n\n>>>>>>>>>query weather<<<<<<<<<")
iter := runner.Query(ctx, "What's the weather in Beijing?")
for {
event, ok := iter.Next()
if !ok {
break
}
if event.Err != nil {
log.Fatal(event.Err)
}
if event.Action != nil {
fmt.Printf("\nAgent[%s]: transfer to %+v\n\n======\n", event.AgentName, event.Action.TransferToAgent.DestAgentName)
} else {
fmt.Printf("\nAgent[%s]:\n%+v\n\n======\n", event.AgentName, event.Output.MessageOutput.Message)
}
}
// failed to route
println("\n\n>>>>>>>>>failed to route<<<<<<<<<")
iter = runner.Query(ctx, "Book me a flight from New York to London tomorrow.")
for {
event, ok := iter.Next()
if !ok {
break
}
if event.Err != nil {
log.Fatal(event.Err)
}
if event.Action != nil {
fmt.Printf("\nAgent[%s]: transfer to %+v\n\n======\n", event.AgentName, event.Action.TransferToAgent.DestAgentName)
} else {
fmt.Printf("\nAgent[%s]:\n%+v\n\n======\n", event.AgentName, event.Output.MessageOutput.Message)
}
}
}
Sample output:
>>>>>>>>>query weather<<<<<<<<<
Agent[RouterAgent]:
assistant:
tool_calls:
{Index:<nil> ID:call_SKNsPwKCTdp1oHxSlAFt8sO6 Type:function Function:{Name:transfer_to_agent Arguments:{"agent_name":"WeatherAgent"}} Extra:map[]}
finish_reason: tool_calls
usage: &{201 17 218}
======
Agent[RouterAgent]: transfer to WeatherAgent
======
Agent[WeatherAgent]:
assistant:
tool_calls:
{Index:<nil> ID:call_QMBdUwKj84hKDAwMMX1gOiES Type:function Function:{Name:get_weather Arguments:{"city":"Beijing"}} Extra:map[]}
finish_reason: tool_calls
usage: &{255 15 270}
======
Agent[WeatherAgent]:
tool: the temperature in Beijing is 25°C
tool_call_id: call_QMBdUwKj84hKDAwMMX1gOiES
tool_call_name: get_weather
======
Agent[WeatherAgent]:
assistant: The current temperature in Beijing is 25°C.
finish_reason: stop
usage: &{286 11 297}
======
>>>>>>>>>failed to route<<<<<<<<<
Agent[RouterAgent]:
assistant: I'm unable to assist with booking flights. Please use a relevant travel service or booking platform to make your reservation.
finish_reason: stop
usage: &{206 23 229}
======
Other OnSubAgents callbacks when registering child agents:
OnSetAsSubAgent: register parent info to the agentOnDisallowTransferToParent: whenWithDisallowTransferToParentis set, inform the agent not to transfer back to its parent
adk.SetSubAgents(
ctx,
Agent1,
[]adk.Agent{
adk.AgentWithOptions(ctx, Agent2, adk.WithDisallowTransferToParent()),
},
)
Deterministic Transfer
Wrap a subagent so it automatically transfers back to a target (e.g., supervisor) after finishing:
type DeterministicTransferConfig struct {
Agent Agent
ToAgentNames []string
}
func AgentWithDeterministicTransferTo(_ context.Context, config *DeterministicTransferConfig) Agent
Used by Supervisor to ensure subagents return control deterministically:
// github.com/cloudwego/eino/adk/prebuilt/supervisor.go
type SupervisorConfig struct {
Supervisor adk.Agent
SubAgents []adk.Agent
}
func NewSupervisor(ctx context.Context, conf *SupervisorConfig) (adk.Agent, error) {
subAgents := make([]adk.Agent, 0, len(conf.SubAgents))
supervisorName := conf.Supervisor.Name(ctx)
for _, subAgent := range conf.SubAgents {
subAgents = append(subAgents, adk.AgentWithDeterministicTransferTo(ctx, &adk.DeterministicTransferConfig{
Agent: subAgent,
ToAgentNames: []string{supervisorName},
}))
}
return adk.SetSubAgents(ctx, conf.Supervisor, subAgents)
}
Workflow Agents
WorkflowAgent runs agents according to a preset flow. ADK provides three base Workflow agents: Sequential, Parallel, and Loop. They can be nested to build more complex tasks.
By default, each agent’s input in a Workflow is generated via the History mechanism described earlier; you can customize AgentInput generation with WithHistoryRewriter.
When an agent emits an ExitAction event, the Workflow agent exits immediately, regardless of remaining agents.
See details and examples: /docs/eino/core_modules/eino_adk/agent_implementation/workflow
SequentialAgent
Run a series of agents in the provided order:
type SequentialAgentConfig struct {
Name string
Description string
SubAgents []Agent
}
func NewSequentialAgent(ctx context.Context, config *SequentialAgentConfig) (Agent, error)
LoopAgent
Based on SequentialAgent; after completing one run, it starts from the beginning again:
type LoopAgentConfig struct {
Name string
Description string
SubAgents []Agent
MaxIterations int
}
func NewLoopAgent(ctx context.Context, config *LoopAgentConfig) (Agent, error)
ParallelAgent
Run agents concurrently:
type ParallelAgentConfig struct {
Name string
Description string
SubAgents []Agent
}
func NewParallelAgent(ctx context.Context, config *ParallelAgentConfig) (Agent, error)












