Eino Study Notes — Appendix 1
Eino Study Notes — Appendix 1: The Flow Module — ReAct Agent and MultiAgent
This note was generated with AI assistance, but the code-reading order should be fine (all examples use the official Eino examples; personally, I find the official tutorials a bit hard to follow).
In principle, I think the appendices are just for reference — you can skim them and look things up when needed.
Appendix 1: The Flow Module — ReAct Agent and MultiAgent
📂 Source repo: github.com/cloudwego/eino-examples↗
📖 Series docs: Intro Notes | Agentic Advanced | Appendix 1 Flow | Appendix 2 Components | Appendix 3 Tools
📁 Code root:
flow/agent/📖 Prerequisites: This appendix assumes you have already mastered the content of the Intro Notes (sections 1–7) and Agentic Advanced.
At a Glance: Flow vs ADK — Two Systems
Eino provides two parallel ways to build Agents:
| Dimension | ADK (Intro Notes / Agentic Advanced) | Flow (this appendix) |
|---|---|---|
| Entry point | adk.NewChatModelAgent / adk.NewTypedChatModelAgent | react.NewAgent / host.NewMultiAgent / Compose Graph |
| Abstraction level | High-level wrapper, works out of the box | Mid-to-low level, fine-grained control over every node |
| Orchestration | Agent + Runner + Middleware (declarative) | Graph + Node + Branch + State (imperative) |
| Use cases | Quickly build standard Agent apps | Custom control flow, multi-Agent collaboration, fine-grained state management |
🧠 How to choose: Prefer ADK for scenarios it can cover. Use Flow when you need to customize ReAct loop behavior, dynamically modify model parameters at runtime, or manage multi-Agent state-graph transitions.
Technical Selection Decision Tree
graph TD
Q["What do you want to do?"] --> Q1{"Standard single Agent
Q&A + tools + memory?"}
Q1 -->|Yes| ADK["Use ADK
Intro Notes §3 / Agentic Advanced §3"]
Q1 -->|No| Q2{"Need custom
ReAct loop behavior?"}
Q2 -->|Yes| REACT["Use react.NewAgent
this appendix §1/§2/§4"]
Q2 -->|No| Q3{"Need multi-Agent collaboration?"}
Q3 -->|Orchestration mode| SUP["Quick: ADK Supervisor
Fine-grained: Host MultiAgent
this appendix §6"]
Q3 -->|Pipeline| PE["Plan-Execute
this appendix §7"]
Q3 -->|Complex state graph| CUSTOM["Custom Graph+State
this appendix §9"]
Q3 -->|No| Q4{"Need browser/sandbox tools?"}
Q4 -->|Yes| MANUS["See Manus Agent
this appendix §8"]📇 Quick-Reference Card for This Appendix
react.NewAgent({ToolCallingModel, ToolsConfig}) → Stream/Generatereact.WithMessageFuture() → observe intermediate steps (no Callback needed)host.NewMultiAgent({Host, Specialists}) → orchestration-style multi-AgentCustom Graph + State → Plan-Execute / Deer-Go1. ReAct Agent Basics
📁
flow/agent/react/📖 Official docs: https://www.cloudwego.io/zh/docs/eino/core_modules/flow_integration_components/react_agent_manual/↗
1.1 What is ReAct
ReAct (Reasoning + Acting) is the core loop pattern of an Agent:
User input → ChatModel thinks → Need to call a Tool? ├─ Yes → Execute Tool → Tool result back to ChatModel → keep thinking └─ No → Output final answerEino wraps this loop as react.NewAgent — create it in one line, ready to use out of the box.
1.2 Minimal Example: Restaurant Recommendation
import ( "github.com/cloudwego/eino/flow/agent/react" "github.com/cloudwego/eino/compose")
rAgent, err := react.NewAgent(ctx, &react.AgentConfig{ ToolCallingModel: arkModel, ToolsConfig: compose.ToolsNodeConfig{ Tools: []tool.BaseTool{restaurantTool, dishTool}, },})
// Streaming call (same Recv+EOF pattern as ChatModel.Stream)sr, _ := rAgent.Stream(ctx, []*schema.Message{ schema.SystemMessage("You are an assistant that helps users find restaurants..."), schema.UserMessage("I'm in Beijing, recommend spicy dishes, at least 2 restaurants"),})defer sr.Close()
for { msg, err := sr.Recv() if errors.Is(err, io.EOF) { break } fmt.Print(msg.Content)}💡 This is functionally equivalent to the ChatModelAgent + Runner from Intro Notes section 3, but
react.NewAgentexposes more low-level control points.
1.3 Two Important Extension Points
MessageModifier: Inject a System Prompt into each ReAct loop iteration:
react.NewAgent(ctx, &react.AgentConfig{ MessageModifier: func(_ context.Context, input []*schema.Message) []*schema.Message { return append([]*schema.Message{schema.SystemMessage(sys)}, input...) },})StreamToolCallChecker: Customize Tool-call detection (some models don’t return ToolCall in the first chunk):
toolCallChecker := func(ctx context.Context, sr *schema.StreamReader[*schema.Message]) (bool, error) { defer sr.Close() for { msg, err := sr.Recv() if errors.Is(err, io.EOF) { break } if len(msg.ToolCalls) > 0 { return true, nil } } return false, nil}1.4 Visual Debugging: ExportGraph + Mermaid
anyG, opts := rAgent.ExportGraph()gen := visualize.NewMermaidGenerator("flow/agent/react")g := compose.NewGraph[[]*schema.Message, *schema.Message]()g.AddGraphNode("react_agent", anyG, opts...)g.AddEdge(compose.START, "react_agent")g.AddEdge("react_agent", compose.END)g.Compile(context.Background(), compose.WithGraphCompileCallbacks(gen))2. Agentic ReAct Agent (Responses API Version)
📁
flow/agent/react/agentic/
2.1 Core Differences
| Dimension | Basic ReAct | Agentic ReAct |
|---|---|---|
| Model | model.BaseChatModel | model.AgenticModel |
| Message | *schema.Message | *schema.AgenticMessage |
| Tool node | compose.NewToolNode | compose.NewAgenticToolsNode |
| Branch decision | Check ToolCalls | Check FunctionToolCall in ContentBlocks |
| Server-side tools | ❌ | ✅ |
2.2 Key Innovation: ToolReturnDirectly
config := &AgentConfig{ Model: am, ToolsConfig: compose.ToolsNodeConfig{ Tools: []tool.BaseTool{summarizeTool, locationTool}, }, ToolReturnDirectly: map[string]struct{}{ "summarize_news": {}, // ← summarize_news finishes execution and ends directly },}2.3 Handling by ContentBlock Type
msg, _ := schema.ConcatAgenticMessages(msgs)
for _, block := range msg.ContentBlocks { switch block.Type { case schema.ContentBlockTypeReasoning: fmt.Printf("[Reasoning] %s\n", block.Reasoning.Text) case schema.ContentBlockTypeServerToolCall: fmt.Printf("[Server tool] %s\n", block.ServerToolCall.Name) case schema.ContentBlockTypeAssistantGenText: fmt.Printf("[Output] %s\n", block.AssistantGenText.Text) }}3. Short-Term Memory
📁
flow/agent/react/memory_example/
3.1 MemoryStore Interface
type MemoryStore interface { Write(ctx, sessionID string, msgs []*schema.Message) error Read(ctx, sessionID string) ([]*schema.Message, error) Query(ctx, sessionID, text string, limit int) ([]*schema.Message, error)}| Implementation | Storage | Use case |
|---|---|---|
InMemoryStore | Process memory | Development/testing |
RedisStore | Redis | Production |
3.2 Multi-Turn Conversation Pattern
store := memory.NewInMemoryStore()sessionID := "session:demo"
// Each conversation turnprev, _ := store.Read(ctx, sessionID) // Restore historyeff := append(prev, schema.UserMessage(turn)) // Concatenate new inputsr, _ := agent.Stream(ctx, eff, msgFutureOpt) // Execute_ = store.Write(ctx, sessionID, append(eff, produced...)) // Persist3.3 Observing Intermediate Steps with MessageFuture
msgFutureOpt, msgFuture := react.WithMessageFuture()
// Consume the intermediate message streamiter := msgFuture.GetMessageStreams()for { sr, ok, _ := iter.Next() if !ok { break } full, _ := schema.ConcatMessages(readAllChunks(sr)) fmt.Printf("Intermediate message: role=%s content=%s\n", full.Role, full.Content)}4. Dynamic Options
📁
flow/agent/react/dynamic_option_example/
4.1 The Pain Point
The ReAct Agent uses the same model parameters in every iteration. But you might want thinking mode enabled on the first round and disabled in later rounds.
4.2 Core Mechanism: Wrap ChatModel + ProcessState
type ChatModel struct { Model model.BaseChatModel GetOptionFunc OptionFunc // ← Returns dynamic options based on State}
func (d *ChatModel) Generate(ctx context.Context, input []*schema.Message, opts ...model.Option) (*schema.Message, error) { var dynamicOpts []model.Option
compose.ProcessState[*State](ctx, func(_ context.Context, state *State) error { dynamicOpts = d.GetOptionFunc(ctx, input, state) state.Iteration++ return nil })
return d.Model.Generate(ctx, input, append(dynamicOpts, opts...)...)}4.3 Dynamic Option Function Example
func getDynamicOptions(ctx context.Context, input []*schema.Message, state *State) []model.Option { if state.Iteration >= 1 { // From the second round on: disable thinking, forbid Tool calls return []model.Option{ ark.WithThinking(&arkModel.Thinking{Type: arkModel.ThinkingTypeDisabled}), model.WithToolChoice(schema.ToolChoiceForbidden), } } // First round: allow Tool calls return []model.Option{ model.WithToolChoice(schema.ToolChoiceAllowed), model.WithTools(toolInfos), }}5. Unknown Tool Handling (Unknown Tool Handler)
📁
flow/agent/react/unknown_tool_handler_example/
rAgent, _ := react.NewAgent(ctx, &react.AgentConfig{ ToolsConfig: compose.ToolsNodeConfig{ Tools: []tool.BaseTool{sumTool}, UnknownToolsHandler: func(ctx context.Context, name, input string) (string, error) { return fmt.Sprintf( "unknown tool: %s; try again with the correct tool name", name, ), nil }, },})The model hallucinates and calls a non-existent tool → no crash, returns a hint → the model self-corrects.
6. Multi-Agent: Host Mode
📁
flow/agent/multiagent/host/journal/📖 Official docs: https://www.cloudwego.io/zh/docs/eino/core_modules/flow_integration_components/multi_agent_hosting/↗
6.1 Concept
One Host (dispatcher) + multiple Specialists (experts):
User → Host (analyze intent) ├─ "Write journal" → WriteJournalSpecialist ├─ "Read journal" → ReadJournalSpecialist └─ "Query journal" → AnswerWithJournalSpecialist6.2 Full Implementation
import "github.com/cloudwego/eino/flow/agent/multiagent/host"
h := &host.Host{ChatModel: chatModel, SystemPrompt: "..."}
hostMA, _ := host.NewMultiAgent(ctx, &host.MultiAgentConfig{ Host: *h, Specialists: []*host.Specialist{writer, reader, answerer},})
// Multi-turn conversationout, _ := hostMA.Stream(ctx, []*schema.Message{schema.UserMessage(input)}, host.WithAgentCallbacks(cb), // Can listen to HandOff events)6.3 Comparison with ADK Supervisor
| Dimension | Host MultiAgent (Flow) | Supervisor (ADK) |
|---|---|---|
| Location | flow/agent/multiagent/host | adk/prebuilt/supervisor |
| Dispatch | Host ChatModel decides who to assign to | Supervisor Agent decides who to assign to |
| Abstraction | Mid-to-low level (Compose Graph) | High level (ADK wrapper) |
| Use case | Need custom Specialist structure | Quickly build multi-Agent systems |
7. Multi-Agent: Plan-Execute Mode
📁
flow/agent/multiagent/plan_execute/
7.1 Concept
A three-stage collaboration pattern:
Planner → Executor ⇄ Reviser ↓ Output "final answer" → END7.2 Core Configuration
config := &Config{ PlannerModel: deepSeekModel, // Planner model ExecutorModel: arkModel, // Executor model (must support ToolCalling) ReviserModel: deepSeekModel, // Reviser model ToolsConfig: compose.ToolsNodeConfig{Tools: toolsConfig}, MaxStep: 100,}💡 Different roles can use different models — Planner/Reviser use cheaper reasoning models, Executor uses a model that supports Tool Calling.
8. Full Application: Manus Agent
📁
flow/agent/manus/
8.1 Overview
Manus Agent demonstrates how to build an Agent with the following capabilities using the Compose Graph API:
- 🖥️ Command-line execution: Run Python code in a Docker sandbox
- 🌐 Browser operation: Web browsing and interaction based on Playwright
- 🔍 Search engine: DuckDuckGo integration
- 👤 Human feedback: Interrupt execution, wait for user confirmation, then continue
- 📊 Observability: Dual tracing via Langfuse + CozeLoop
8.2 Interrupt-Driven Human Feedback Loop
for { result, err := agent.Invoke(ctx, input, compose.WithCheckPointID("1"), compose.WithRuntimeMaxSteps(20), )
info, ok := compose.ExtractInterruptInfo(err) if ok { // Interrupted — show the result, wait for user confirmation fmt.Print("Do you want to continue? (y/n): ") // ... read user input ... continue // Auto Resume } fmt.Printf("[FinalResult]: %s", result) break}8.3 Tool Ecosystem
// Command-line tool (Docker sandbox)sandbox.NewDockerSandbox(ctx, &sandbox.Config{...})
// Browser toolbrowseruse.NewBrowserUseTool(ctx, &browseruse.Config{...})
// Search engineduckduckgo.NewSearch(ctx, &duckduckgo.Config{...})9. Full Application: Deer-Go (Research Team Collaboration)
📁
flow/agent/deer-go/Reference: https://github.com/bytedance/deer-flow↗
9.1 Overview
Deer-Go is a state-graph-based multi-Agent research team that simulates a real research collaboration workflow.
9.2 Core Mechanism: State-Driven Subgraph Transitions
After each subgraph finishes executing, it overwrites state.Goto to specify the next Agent to run:
func agentHandOff(ctx context.Context, input string) (next string, err error) { compose.ProcessState[*model.State](ctx, func(_ context.Context, state *model.State) error { next = state.Goto return nil }) return next, nil}
// All Agent nodes share the same Branch functiong.AddBranch(consts.Coordinator, compose.NewGraphBranch(agentHandOff, outMap))g.AddBranch(consts.Planner, compose.NewGraphBranch(agentHandOff, outMap))9.3 Team Composition
Coordinator → BackgroundInvestigator → Planner → ResearchTeam ├─→ Researcher ├─→ Coder └─→ Planner (iterate) └─→ Reporter → Human → Coordinator10. Example Navigation
| Directory | Topic | Core knowledge | Complexity |
|---|---|---|---|
react/ | ReAct Agent basics | react.NewAgent, Stream, ExportGraph | ⭐ |
react/agentic/ | Agentic ReAct | Custom Graph ReAct, ToolReturnDirectly | ⭐⭐⭐ |
react/memory_example/ | Short-term memory | MemoryStore interface, MessageFuture | ⭐⭐ |
react/dynamic_option_example/ | Dynamic options | ChatModel wrapper, ProcessState | ⭐⭐⭐ |
react/unknown_tool_handler_example/ | Unknown tools | UnknownToolsHandler | ⭐ |
multiagent/host/journal/ | Host Multi-Agent | Host + Specialist, HandOff callback | ⭐⭐ |
multiagent/plan_execute/ | Plan-Execute | Planner→Executor⇄Reviser | ⭐⭐⭐ |
manus/ | Manus Agent | Docker sandbox, browser, Interrupt+Resume | ⭐⭐⭐⭐ |
deer-go/ | Deer-Go research team | State-graph transitions, multi-subgraph collaboration, Goto-driven | ⭐⭐⭐⭐ |
Common import paths
// ReAct Agent"github.com/cloudwego/eino/flow/agent/react" // react.NewAgent, WithMessageFuture
// Multi-Agent"github.com/cloudwego/eino/flow/agent/multiagent/host" // host.NewMultiAgent, Host, Specialist
// Compose low-level"github.com/cloudwego/eino/compose" // Graph, Branch, ProcessState, ToolNode"github.com/cloudwego/eino/schema" // Message, AgenticMessage, ToolInfo
// Callbacks"github.com/cloudwego/eino/callbacks" // Handler, HandlerBuilder"github.com/cloudwego/eino/utils/callbacks" // NewHandlerHelper📇 Quick-Reference Card for This Appendix
Flow module core methods: react.NewAgent({ToolCallingModel, ToolsConfig}) host.NewMultiAgent({Host, Specialists}) compose.NewGraphBranch(fn, outMap) compose.ProcessState[T](ctx, fn) — read/write Graph State
Selection: Standard→ADK | Custom loop→react | Multi-Agent dispatch→host | State graph→Graph+State📂 Source repo: github.com/cloudwego/eino-examples↗
📖 Keep reading:
- Intro Notes — Eino ADK from Hello World to Compose orchestration
- Agentic Advanced — Responses API / AgenticMessage / Typed generics
- Appendix 1: Flow Module — ReAct Agent / Multi-Agent / state graph ← You are here
- Appendix 2: Components Module — A/B routing / HTTP logging / retrieval augmentation / document parsing
- Appendix 3: Lambda and Debugging Tools — Lambda writing / Devops / Mermaid visualization