Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
cloudwego avatar

Eino Agent

  • 211 installs
  • 785 repo stars
  • Updated August 3, 2026
  • cloudwego/eino-ext

Build AI agents that reason, call tools, handle retries, and manage multi-turn conversational logic.

About

Constructs AI agents in Go using the Eino ADK with ChatModelAgent (ReAct), middleware (filesystem, tool search, summarization), and resilience patterns (Cancel, Retry, Failover). Use when building agent workflows with decision loops and tool integration.

  • ChatModelAgent with ReAct pattern, middleware system, Cancel/Retry/Failover mechanisms
  • TurnLoop for multi-turn execution with preemption and event-driven lifecycle

Eino Agent by the numbers

  • 211 all-time installs (skills.sh)
  • Ranked #2,807 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cloudwego/eino-ext --skill eino-agent

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs211
repo stars785
Last updatedAugust 3, 2026
Repositorycloudwego/eino-ext

What it does

Build AI agents that reason, call tools, handle retries, and manage multi-turn conversational logic.

Files

SKILL.mdMarkdownGitHub ↗

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.

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

TypeDescriptionDecision
ChatModelAgentReAct pattern: LLM reasons, calls tools, loops until doneDynamic (LLM)
DeepAgentPre-built agent with planning, filesystem, sub-agentsDynamic (LLM)
TurnLoopPush-based event loop for multi-turn execution with preemption and lifecycle managementRuntime
Custom AgentImplement the TypedAgent interface directlyCustom

ChatModelAgent Quick Start

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.

// 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):

ModeBehavior
CancelImmediate (0)Abort immediately, stream terminated
CancelAfterChatModelWait for current model call to finish
CancelAfterToolCallsWait 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.

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.

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.

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:

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:

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

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

MiddlewarePackagePurpose
FileSystemadk/middlewares/filesystemFile ops (read/write/edit/glob/grep) + shell
ToolSearchadk/middlewares/dynamictool/toolsearchDynamic tool selection via regex search
ToolReductionadk/middlewares/reductionTruncate/clear large tool results
Summarizationadk/middlewares/summarizationCompress long conversation history
PlanTaskadk/middlewares/plantaskTask creation and progress tracking
Skilladk/middlewares/skillSkill-based progressive disclosure
PatchToolCallsadk/middlewares/patchtoolcallsFix dangling tool calls in history
Agents.mdadk/middlewares/agentsmdInject Agents.md instructions into model input

AgentAsTool

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

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

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.