# Eino Agent

> Eino ADK agent construction, middleware, and runner. Use when a user needs to build an AI Agent, configure ChatModelAgent with ReAct pattern, use middleware (filesystem, tool search, tool reduction, summarization, plan-task, skill, agents.md), set up the Runner for event-driven execution, implement human-in-the-loop with interrupt/resume, use Cancel/Retry/Failover for model resilience, build push-based multi-turn loops with TurnLoop, or wrap agents as tools. Covers ChatModelAgent, DeepAgents, and TurnLoop.

- Skill: `cloudwego/eino-agent` (Agent Skill, multi-file: 8 files)
- Install (CLI): `npx skillmds@latest add cloudwego/eino-agent`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cloudwego/eino-agent/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: cloudwego (https://skillmd.com/u/cloudwego)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cloudwego/eino-agent

---


# Eino ADK Overview

Import: `github.com/cloudwego/eino/adk`

The Agent Development Kit (ADK) provides a framework for building agents in Go. The ADK is generically parameterized by `MessageType` to support both classic `*schema.Message` and the new `*schema.AgenticMessage`. Prefer `*schema.AgenticMessage` for new usage.

```go
type MessageType interface {
    *schema.Message | *schema.AgenticMessage
}

type TypedAgent[M MessageType] interface {
    Name(ctx context.Context) string
    Description(ctx context.Context) string
    Run(ctx context.Context, input *TypedAgentInput[M], options ...AgentRunOption) *AsyncIterator[*TypedAgentEvent[M]]
}

// Convenience aliases for classic message type
type Agent = TypedAgent[*schema.Message]
```

# Agent Types

| Type | Description | Decision |
|------|-------------|----------|
| ChatModelAgent | ReAct pattern: LLM reasons, calls tools, loops until done | Dynamic (LLM) |
| DeepAgent | Pre-built agent with planning, filesystem, sub-agents | Dynamic (LLM) |
| TurnLoop | Push-based event loop for multi-turn execution with preemption and lifecycle management | Runtime |
| Custom Agent | Implement the TypedAgent interface directly | Custom |

# ChatModelAgent Quick Start

```go
import (
    "context"
    "fmt"
    "log"

    "github.com/cloudwego/eino-ext/components/model/openai"
    "github.com/cloudwego/eino/adk"
    "github.com/cloudwego/eino/components/tool"
    "github.com/cloudwego/eino/components/tool/utils"
    "github.com/cloudwego/eino/compose"
)

func main() {
    ctx := context.Background()

    // 1. Create a tool
    searchTool, _ := utils.InferTool("search_book", "Search books by genre",
        func(ctx context.Context, input *struct {
            Genre string `json:"genre" jsonschema_description:"Book genre"`
        }) (string, error) {
            return `{"books": ["The Great Gatsby"]}`, nil
        })

    // 2. Create model (BaseModel[M], not ToolCallingChatModel)
    cm, _ := openai.NewChatModel(ctx, &openai.ChatModelConfig{
        APIKey: "your-key", Model: "gpt-4o",
    })

    // 3. Create agent
    agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
        Name:        "BookRecommender",
        Description: "Recommends books",
        Instruction: "You recommend books using the search_book tool.",
        Model:       cm,
        ToolsConfig: adk.ToolsConfig{
            ToolsNodeConfig: compose.ToolsNodeConfig{
                Tools: []tool.BaseTool{searchTool},
            },
        },
    })

    // 4. Run with Runner
    runner := adk.NewRunner(ctx, adk.RunnerConfig{Agent: agent})
    iter := runner.Query(ctx, "recommend a fiction book")
    for {
        event, ok := iter.Next()
        if !ok {
            break
        }
        if event.Err != nil {
            log.Fatal(event.Err)
        }
        if event.Output != nil && event.Output.MessageOutput != nil {
            msg, _ := event.Output.MessageOutput.GetMessage()
            fmt.Printf("Agent[%s]: %v\n", event.AgentName, msg)
        }
    }
}
```

# Cancel Mechanism

Cancel provides safe, controllable termination of agent execution.

```go
// Create a cancel function alongside the run
cancelOpt, cancelFn := adk.WithCancel()
iter := runner.Query(ctx, "do something", cancelOpt)

// ... iterate events ...

// Cancel at a safe point (cancelFn is non-blocking, Wait blocks until complete)
handle, ok := cancelFn(adk.WithAgentCancelMode(adk.CancelAfterChatModel))
if ok {
    handle.Wait()
}
```

**CancelMode** (bitmask):

| Mode | Behavior |
|------|----------|
| `CancelImmediate` (0) | Abort immediately, stream terminated |
| `CancelAfterChatModel` | Wait for current model call to finish |
| `CancelAfterToolCalls` | Wait for current tool calls to finish |

**Cancel options:**
- `WithAgentCancelMode(mode)` -- set safe point
- `WithAgentCancelTimeout(d)` -- escalate to immediate if safe point not reached in time
- `WithRecursive()` -- propagate cancel into nested AgentTool agents

Cancel produces a `CancelError` on the event stream with checkpoint data for later resumption.

# Model Retry

Output-based retry with full control over retry decisions.

```go
agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
    // ...
    ModelRetryConfig: &adk.ModelRetryConfig{
        MaxRetries: 3,
        ShouldRetry: func(ctx context.Context, retryCtx *adk.RetryContext) *adk.RetryDecision {
            // Retry based on output content (e.g., empty response, bad finish reason)
            if retryCtx.Err != nil {
                return &adk.RetryDecision{Retry: true, Backoff: time.Second}
            }
            if retryCtx.OutputMessage == nil || retryCtx.OutputMessage.Content == "" {
                return &adk.RetryDecision{Retry: true, Backoff: time.Second}
            }
            return &adk.RetryDecision{Retry: false}
        },
    },
})
```

**RetryContext** provides: `RetryAttempt`, `InputMessages`, `OutputMessage` (full concatenated response), `Err`.

**RetryDecision** controls: `Retry`, `ModifiedInputMessages`, `AdditionalOptions`, `Backoff`, `RejectReason`.

When streaming, a `WillRetryError` is emitted on the stream to signal retry is occurring.

# Model Failover

Dynamic model switching when primary model fails or produces unsatisfactory output.

```go
agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
    // ...
    ModelFailoverConfig: &adk.ModelFailoverConfig[*schema.Message]{
        MaxRetries: 2,
        ShouldFailover: func(ctx context.Context, outputMessage *schema.Message, outputErr error) bool {
            return outputErr != nil // failover on any error
        },
        GetFailoverModel: func(ctx context.Context, failoverCtx *adk.FailoverContext[*schema.Message]) (
            model.BaseModel[*schema.Message], []*schema.Message, error) {
            // Return a different model and optionally modified input
            return backupModel, failoverCtx.InputMessages, nil
        },
    },
})
```

Failover interacts with Retry: when both are configured, `FailoverContext.LastErr` will be a `*RetryExhaustedError` if retry was also exhausted.

# TurnLoop

Push-based event loop for multi-turn agent execution with preemption, idle timeout, and graceful shutdown.

```go
import "github.com/cloudwego/eino/adk"

loop := adk.NewTurnLoop(adk.TurnLoopConfig[string, *schema.Message]{
    GenInput: func(ctx context.Context, loop *adk.TurnLoop[string, *schema.Message], items []string) (*adk.GenInputResult[string, *schema.Message], error) {
        // Convert pushed items into agent input
        combined := strings.Join(items, "\n")
        return &adk.GenInputResult[string, *schema.Message]{
            RunCtx:   ctx,
            Input:    &adk.TypedAgentInput[*schema.Message]{Messages: []*schema.Message{schema.UserMessage(combined)}},
            Consumed: items,
        }, nil
    },
    PrepareAgent: func(ctx context.Context, loop *adk.TurnLoop[string, *schema.Message], consumed []string) (adk.Agent, error) {
        return myAgent, nil
    },
    OnAgentEvents: func(ctx context.Context, tc *adk.TurnContext[string, *schema.Message], events *adk.AsyncIterator[*adk.AgentEvent]) error {
        for {
            event, ok := events.Next()
            if !ok {
                break
            }
            if event.Err != nil {
                return event.Err
            }
            // Process events (e.g., send to client)
        }
        return nil
    },
})

// Start the loop
loop.Run(ctx)

// Push items (non-blocking, returns (ok bool, resolved <-chan struct{}))
loop.Push("user message 1")

// Preempt current turn with new input
loop.Push("urgent message", adk.WithPreempt[string, *schema.Message](adk.AfterChatModel))

// Stop gracefully
loop.Stop(adk.WithGraceful())

// Wait for exit and get final state
exitState := loop.Wait()
```

**Key concepts:**
- `Push()` queues items; the loop batches and processes them via `GenInput`
- Preemption: cancel current turn at a safe point and start new turn with pending items
- Idle timeout: `UntilIdleFor(d)` auto-stops after no items for duration `d`
- Graceful shutdown: `WithGraceful()`, `WithGracefulTimeout(d)`, `WithImmediate()`
- Exit state: `TurnLoopExitState` contains `ExitReason`, `UnhandledItems`, `InterruptedItems`

# Runner

The Runner manages agent lifecycle, context passing, and interrupt/resume:

```go
runner := adk.NewRunner(ctx, adk.RunnerConfig{
    Agent:           myAgent,
    EnableStreaming:  true,
    CheckPointStore: myStore,  // for interrupt/resume
})

// Query (convenience for single user message)
iter := runner.Query(ctx, "hello")

// Run (full control over input messages)
iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("hello")})
```

# Middleware System

Middleware extends ChatModelAgent behavior. Configure via `Handlers` field:

```go
agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
    // ...
    Handlers: []adk.ChatModelAgentMiddleware{fsMiddleware, summarizationMW},
})
```

Eight built-in middleware types (see reference/middleware.md for details):

| Middleware | Package | Purpose |
|-----------|---------|---------|
| FileSystem | `adk/middlewares/filesystem` | File ops (read/write/edit/glob/grep) + shell |
| ToolSearch | `adk/middlewares/dynamictool/toolsearch` | Dynamic tool selection via regex search |
| ToolReduction | `adk/middlewares/reduction` | Truncate/clear large tool results |
| Summarization | `adk/middlewares/summarization` | Compress long conversation history |
| PlanTask | `adk/middlewares/plantask` | Task creation and progress tracking |
| Skill | `adk/middlewares/skill` | Skill-based progressive disclosure |
| PatchToolCalls | `adk/middlewares/patchtoolcalls` | Fix dangling tool calls in history |
| Agents.md | `adk/middlewares/agentsmd` | Inject Agents.md instructions into model input |

# AgentAsTool

Wrap any Agent as a Tool for use by another agent:

```go
subAgent := createMySubAgent()
agentTool := adk.NewAgentTool(ctx, subAgent)

parentAgent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
    ToolsConfig: adk.ToolsConfig{
        ToolsNodeConfig: compose.ToolsNodeConfig{
            Tools: []tool.BaseTool{agentTool},
        },
    },
})
```

# Human-in-the-Loop

ChatModelAgent supports interrupt and resume for human approval, clarification, and feedback. See reference/human-in-the-loop.md for details.

Key pattern: tool returns `compose.Interrupt(ctx, info)` to pause, then `runner.ResumeWithParams(ctx, checkpointID, params)` to continue.

# Instructions to Agent

- Default to `ChatModelAgent` for most use cases (single agent with tools)
- Use `Runner` to execute agents -- never call `agent.Run()` directly in production
- Middleware order matters: PatchToolCalls first, then Reduction, then Summarization
- Use `DeepAgent` (`adk/prebuilt/deep`) when you need built-in planning + filesystem + sub-agents
- Use `AgentAsTool` or DeepAgents' SubAgents when a sub-agent needs isolated context (no shared history)
- Use `TurnLoop` when building interactive applications that need preemption, idle management, or push-based input
- `Model` field accepts `model.BaseModel[M]` -- for classic path use any `BaseChatModel`, for agentic path use `AgenticModel`
- Cancel, Retry, and Failover can all be combined; failover wraps retry
- `WithCancel()` is a run option, not a config option -- create fresh per-run

## Reference Files

Read these files on-demand for detailed API, examples, and advanced usage:

- [reference/chat-model-agent.md](reference/chat-model-agent.md) -- ChatModelAgentConfig reference, ReAct pattern, ToolsConfig, streaming, Cancel/Retry/Failover details
- [reference/deep-agents.md](reference/deep-agents.md) -- DeepAgent concept, config, architecture, comparison with ChatModelAgent
- [reference/middleware.md](reference/middleware.md) -- All 8 middleware types with interface, config, and examples
- [reference/runner-and-events.md](reference/runner-and-events.md) -- Runner creation, AgentEvent/AgentOutput, event iteration patterns
- [reference/agent-as-tool.md](reference/agent-as-tool.md) -- Wrapping an Agent as a Tool for use by another agent
- [reference/human-in-the-loop.md](reference/human-in-the-loop.md) -- Interrupt APIs, ResumableAgent, CheckPointStore, resume patterns
- [reference/filesystem.md](reference/filesystem.md) -- Filesystem Backend interface, Local and AgentKit implementations, usage with DeepAgent

