TL;DR
An Eino ADK application is driven by Runner, not by calling a model-like agent.Run and expecting one message. In v0.9.21, ChatModelAgentConfig uses Instruction, ToolsConfig, and MaxIterations; Runner.Query or Runner.Run returns an AsyncIterator of AgentEvent values. Checkpointed interrupt/resume requires a CheckPointStore and stable checkpoint ID. These framework controls organize execution, but the application still owns authorization, budgets, idempotency, and outcome verification.
Key Takeaways
ChatModelAgentprovides the common ReAct model-tool loop; without tools it performs one model invocation.Runnerowns execution, event delivery, streaming mode, checkpointing, and resume entry points.MaxIterationsis a loop guard, not a complete token, time, cost, or side-effect budget.- DeepAgent is imported from
adk/prebuilt/deepand exposes high-impact capabilities that need sandboxing. - Human approval must bind the exact principal, action, arguments, policy revision, and expiry.
The Current ADK Contract
The pinned v0.9.21 package documentation and ChatModelAgent guide define these central types:
| Type | Role |
|---|---|
adk.Agent |
Common agent interface |
adk.ChatModelAgent |
ReAct-style model and tool loop |
adk.Runner |
Starts runs, emits events, checkpoints, and resumes |
adk.AgentEvent |
Message or control output plus error |
adk.ToolsConfig |
ToolsNode configuration and return behavior |
adk.CheckPointStore |
Persists resumable framework state |
Older examples using SystemPrompt, a top-level Tools field, MaxSteps, StreamRun, adk.NewDeepAgent, or an invented serializable AgentCheckpoint do not match the current API.
ChatModelAgent Configuration
The useful configuration fields in v0.9.21 include:
| Field | Purpose | Boundary |
|---|---|---|
Name / Description |
Agent identity and delegation metadata | Not authentication |
Instruction |
System instruction | Not authorization |
Model |
model.BaseModel implementation |
Must support tool options when tools are used |
ToolsConfig |
Tool set and execution behavior | Tool still needs server-side policy |
MaxIterations |
Upper bound on model generations | Default 20; not a cost SLA |
Handlers |
Current interface-based middleware | Ordering and data exposure need review |
ModelRetryConfig |
Model-call retry policy | Do not retry validation or policy denial |
ModelFailoverConfig |
Select another model after eligible failure | Re-evaluate behavior across models |
Name and Description become inputs when an Agent is exposed to another Agent. Treat them as untrusted metadata, not a permission declaration.
A Runnable Agent Without Provider Credentials
This complete example targets Eino Core v0.9.21. A deterministic mock implements BaseChatModel, so the ChatModelAgent and Runner path can be compiled and executed without a network call.
package main
import (
"context"
"fmt"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/components/model"
"github.com/cloudwego/eino/schema"
)
type fixedModel struct{}
func (fixedModel) Generate(
ctx context.Context,
input []*schema.Message,
_ ...model.Option,
) (*schema.Message, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
return schema.AssistantMessage("accepted: "+input[len(input)-1].Content, nil), nil
}
func (fixedModel) Stream(
ctx context.Context,
input []*schema.Message,
_ ...model.Option,
) (*schema.StreamReader[*schema.Message], error) {
message, err := (fixedModel{}).Generate(ctx, input)
if err != nil {
return nil, err
}
return schema.StreamReaderFromArray([]*schema.Message{message}), nil
}
func main() {
ctx := context.Background()
agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "bounded-assistant",
Description: "Returns one deterministic response.",
Instruction: "Answer once.",
Model: fixedModel{},
MaxIterations: 2,
})
if err != nil {
panic(err)
}
runner := adk.NewRunner(ctx, adk.RunnerConfig{Agent: agent})
events := runner.Query(ctx, "inspect incident")
for {
event, ok := events.Next()
if !ok {
break
}
if event.Err != nil {
panic(event.Err)
}
if event.Output == nil || event.Output.MessageOutput == nil {
continue
}
message, err := event.Output.MessageOutput.GetMessage()
if err != nil {
panic(err)
}
fmt.Println(message.Content)
}
}
Expected output:
accepted: inspect incident
Replace fixedModel with a pinned EinoExt provider only after adding credentials, timeout, rate-limit, retry, usage, and model-version tests.
Add Tools Without Expanding Authority
Tools belong inside adk.ToolsConfig, normally through compose.ToolsNodeConfig. The visible tool set should be derived per request from policy:
- Authenticate the caller outside the model.
- Resolve tenant, object scope, and data classification.
- Select the minimum Tool set for the task.
- Pass Tool descriptions to the model.
- Parse generated arguments strictly.
- Authorize the exact action again at execution.
- Apply idempotency, quotas, egress policy, and result-size limits.
- Record the effect ID and authoritative outcome.
ReturnDirectly controls Agent response flow; it does not make a Tool result trusted or authorized.
Interrupt and Resume
The HITL documentation models an interrupt as resumable execution state. A production flow needs:
- a
CheckPointStoreconfigured onRunner; - a stable
WithCheckPointIDvalue when starting the run; - an approval record tied to the authenticated reviewer;
- the exact Tool name, normalized arguments, object version, policy revision, and expiry;
Runner.Resumefor implicit resume orResumeWithParamsfor targeted data.
Do not serialize an imagined checkpoint struct from a blog example. Use the framework store contract for the pinned version, protect it as sensitive state, and test schema migration and expiry.
Approval is not the same as execution success. After resume, the downstream operation still needs idempotency and reconciliation.
When DeepAgent Is Justified
The current DeepAgents documentation exposes deep.New under github.com/cloudwego/eino/adk/prebuilt/deep. It can add planning, file operations, shell execution, and sub-agent delegation.
That larger capability surface is justified only when:
- representative tasks need decomposition or delegated context;
- the improvement beats a simpler ChatModelAgent or deterministic Workflow;
- file and shell backends are sandboxed with narrow mounts and egress;
- generated plans and TODOs are treated as working state, not authority;
- output, fan-out, iterations, time, and spend are bounded.
Planning does not guarantee a good plan. Reflection does not prove correctness. A sub-agent does not inherit access merely because the parent can describe a task.
State, Memory, and Checkpoints Are Different
| Concept | Purpose | Common mistake |
|---|---|---|
| Input messages | Context for one run | Assuming the Agent stores them forever |
| Session values | Request or workflow state | Treating them as an authorization source |
| Long-term memory | Product-managed durable facts | Storing model inference as user fact |
| Checkpoint | Resume framework execution | Assuming external side effects roll back |
| Trace | Observable evidence | Logging secrets or hidden reasoning |
The Agent Memory guide covers durable memory, while state persistence covers checkpoints and recovery.
Release Gates
- Compile against an exact Eino and EinoExt dependency graph.
- Test direct answer, one Tool Call, repeated Tool Calls, malformed arguments, and unknown Tool.
- Enforce
MaxIterations, wall time, Token, Tool, byte, and spend budgets independently. - Test cancellation before, during, and after Tool execution.
- Inject transient, permanent, unauthorized, and ambiguous failures.
- Verify checkpoint expiry, resume ownership, duplicate resume, and version migration.
- Compare ChatModelAgent and DeepAgent against a deterministic baseline.
- Review every permission increase separately from a model or Prompt change.
Frequently Asked Questions
Does ChatModelAgent keep conversation history automatically across requests?
Do not assume that. A run receives messages and can accumulate state inside that execution. Cross-request history needs an explicit Session or application store and a retention, deletion, and authorization policy.
What is a sensible MaxIterations value?
There is no universal number. Start from the shortest successful traces in a representative evaluation set, then choose a bound that limits worst-case harm and cost. Keep separate wall-time, Tool, Token, and byte limits.
Does enabling Runner streaming expose model tokens directly?
Runner emits typed AgentEvent values. Consumers must handle streaming and non-streaming message variants, Tool events, actions, errors, cancellation, and redaction rather than assuming every event is displayable text.
Should Tool errors be returned to the model?
Only return a bounded, sanitized, typed error that helps choose a safe next step. Do not expose stack traces, credentials, internal topology, or authorization details.
Is an approval prompt enough for a sensitive action?
No. Bind approval to the reviewer identity, exact action and arguments, object revision, policy revision, and expiry. Re-authorize at execution and make the effect idempotent.
Primary Sources
- Eino
v0.9.21package documentation - Eino ChatModelAgent guide
- Eino ADK Quickstart
- Eino Human-in-the-Loop guide
- Eino DeepAgents guide
- Eino Examples cookbook
- OWASP LLM06:2025 Excessive Agency