Go 复刻 Claude Code —— 分阶段详细实现方案
基于本地 claude-code 源码深度分析 + Eino 框架集成
语言:Go 1.22+ 框架:Eino(字节跳动 CloudWeGo)
技术选型总览
| 层次 | 技术 | Eino 包路径 | 对应原版 |
|---|---|---|---|
| CLI 框架 | cobra | — | Commander.js |
| 终端 UI | bubbletea + lipgloss | — | Ink + React |
| Claude 模型 | eino-ext/components/model/claude | claude.NewChatModel() | Anthropic SDK |
| ReAct Agent 主循环 | eino/flow/agent/react | react.NewAgent() | 自研 QueryEngine |
| 工具接口 | eino/components/tool | tool.InvokableTool | Tool.ts 接口 |
| 工具注册 | eino/compose | compose.ToolsNodeConfig | 自研注册表 |
| 流式输出 | eino/schema | schema.StreamReader | AsyncGenerator |
| 多步骤 Workflow | eino/compose | compose.NewGraph() | — |
| 多轮对话记忆 | 自研 / eino-ext memory | memory.InMemoryStore | AppState.messages |
| Callback / 可观测 | eino/callbacks | callbacks.AppendGlobalHandlers | GrowthBook/OTel |
| MCP 工具 | eino-ext/components/tool/mcp | — | MCP 客户端 |
| 状态管理 | 自研 Store(sync.RWMutex) | — | AppStateStore.ts |
| 配置 | viper | — | settings.json |
| 文件锁(Task V2) | github.com/gofrs/flock | — | lockfile |
关键依赖包版本(来自 eino-examples go.mod)
github.com/cloudwego/eino v0.8.5
github.com/cloudwego/eino-ext/components/model/claude latest
github.com/cloudwego/eino-ext/components/model/ark v0.1.65
github.com/cloudwego/eino-ext/components/tool/mcp latest
github.com/cloudwego/eino-ext/callbacks/cozeloop v0.2.0
github.com/charmbracelet/bubbletea v1.x
github.com/spf13/cobra v1.x
github.com/spf13/viper v1.x
github.com/gofrs/flock v0.xPhase 1:骨架搭建 —— 最简对话跑通
目标
claude-go 启动后可与 Claude API 进行流式多轮对话,输出实时显示。
claude-code 的做法
main.tsx 启动流程(精简):
- 并行预取:
startMdmRawRead()+startKeychainPrefetch()+ GrowthBook 初始化 - Commander.js 解析 CLI 参数(
--model、--verbose、--permission-mode等) - 读取
~/.config/claude-code/下的配置文件 - 初始化
AppStateStore(中心化不可变状态) - 渲染 Ink React 树(TUI 入口)
消息流处理(query.ts + QueryEngine.ts):
- 使用
async generator模式(async function*)产出流事件 - 事件类型:
text_delta/tool_use_start/tool_result/message_stop - 每轮结束后检查是否有工具调用,有则继续循环,无则结束
Go 实现方案(Eino)
Eino 替代关系:原版用自研 QueryEngine.ts 驱动消息循环,Go 版直接用 react.NewAgent() + claude.NewChatModel(),流式输出通过 agent.Stream() → sr.Recv() 循环实现,无需自己解析 SSE。
项目结构(Phase 1):
claude-go/
├── main.go
├── cmd/root.go # cobra 根命令,--model/--verbose 等 flags
├── config/
│ ├── config.go # Config 结构体
│ └── loader.go # 读写 ~/.claude/settings.json(viper)
├── agent/
│ └── agent.go # 封装 react.NewAgent + claude.NewChatModel
└── ui/
└── simple.go # Phase 1:stdin/stdout 交互,后续换 bubbletea核心依赖与初始化流程:
cobra解析--model、--verbose、--permission-mode等 CLI 参数viper读取~/.claude/settings.json,合并 CLI 参数(CLI 优先)claude.NewChatModel(ctx, &claude.Config{APIKey, Model, MaxTokens})创建模型react.NewAgent(ctx, &react.AgentConfig{ToolCallingModel: model, ToolsConfig: ...})创建 Agent- 启动 bubbletea TUI(Phase 1 先用 stdin/stdout)
多轮对话:消息历史传递方式
eino-examples memory_example 展示了两种方式:
- 方式 A(推荐):
MessageModifier在每次调用前自动注入系统提示 + 历史消息 - 方式 B:维护
[]*schema.Message切片,每轮手动append后传入agent.Stream()
流式输出处理:
sr, err := agent.Stream(ctx, messages)
defer sr.Close()
for {
msg, err := sr.Recv()
if errors.Is(err, io.EOF) { break }
fmt.Print(msg.Content) // 逐 token 打印
}验收标准:claude-go 启动 → 输入文字 → 流式输出 Claude 回复 → 多轮上下文保持
Phase 2:工具系统 —— Eino InvokableTool
目标
接入 Eino 工具体系,实现 BashTool、FileTools、GrepTool、GlobTool,支持多轮工具调用。
claude-code 的做法
Tool.ts 接口:
type Tool = {
name: string
description: string
inputSchema: ZodSchema // → JSON Schema 自动生成
call(args, context): Promise<ToolResult>
checkPermissions(args, context): Promise<PermissionResult>
isReadOnly(args): boolean
isConcurrencySafe(args): boolean
}BashTool 执行流程(BashTool.tsx):
- 解析输入(command / timeout / run_in_background)
- 权限检查:
bashToolHasPermission()→ AST 解析命令树 → 规则匹配 - 只读约束检查:
checkReadOnlyConstraints() - 执行:
exec(command, { timeout, cwd, env }) - 输出处理:超大输出持久化到文件(避免 context 爆炸)
- Exit code 语义解释:
grep返回 1 表示"无匹配"而非错误
GrepTool:
- 直接调用系统
rg(ripgrep) - 默认排除
.git、.svn、.hg等 VCS 目录 - 输出模式:
content/files_with_matches/count - 默认结果限制:250 条(防止 context 爆炸)
FileEditTool 核心逻辑:
old_string在文件中必须唯一(多次出现需replace_all=true)- 创建新文件:
old_string=""+ 文件不存在 - 验证等价性:防止 old_string === new_string 无效编辑
- 编辑后生成 unified diff 返回给 LLM
Go 实现方案(Eino)
Eino Tool 接口(参考 eino-examples/flow/agent/react/tools/tools.go):
每个工具实现两个方法:
Info(ctx) (*schema.ToolInfo, error)— 描述工具名称、参数 SchemaInvokableRun(ctx, argumentsInJSON string, opts ...tool.Option) (string, error)— 执行,入参和返回值均为 JSON 字符串
参数 Schema 定义(用 schema.NewParamsOneOfByParams):
BashTool 参数:
command (string, required) — shell 命令
timeout (number) — 超时毫秒,默认 120000
description (string) — 人类可读描述
run_in_background (bool) — 后台运行
FileReadTool 参数:
file_path (string, required)
offset (number) — 起始行
limit (number) — 读取行数
FileEditTool 参数:
file_path (string, required)
old_string (string, required)
new_string (string, required)
replace_all (bool)
GlobTool 参数:
pattern (string, required) — glob 匹配式,如 "**/*.go"
path (string) — 搜索根目录
GrepTool 参数:
pattern (string, required) — 正则表达式
path (string) — 搜索路径
glob (string) — 文件过滤,如 "*.go"
output_mode (string) — content / files_with_matches / count
-i (bool) — 忽略大小写
head_limit (number) — 结果条数上限,默认 250工具注册到 Agent(compose.ToolsNodeConfig):
react.NewAgent(ctx, &react.AgentConfig{
ToolCallingModel: claudeModel,
ToolsConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{
bashTool, fileReadTool, fileWriteTool,
fileEditTool, globTool, grepTool,
},
},
MessageModifier: func(ctx, msgs) []*schema.Message {
return append([]*schema.Message{schema.SystemMessage(systemPrompt)}, msgs...)
},
})BashTool 执行要点:
- 用
exec.CommandContext执行,支持超时取消 - exit code 语义:
grep/rg返回 1 = "无匹配"(非错误),diff返回 1 = "文件有差异"(非错误) - 输出超过阈值(如 100KB)时截断并提示
FileEditTool 执行要点:
old_string出现次数 = 0 → 报错old_string出现次数 > 1 且replace_all=false→ 报错提示用户加更多上下文old_string为空且文件不存在 → 创建新文件- 成功后返回 unified diff 字符串
验收标准:Claude 能读取/编辑文件,执行 shell 命令,多轮调用工具
Phase 3:权限系统 —— 五层防护
目标
工具执行前检查权限,支持 allow/deny/ask 规则,危险操作弹框审批。
claude-code 的做法
权限规则来源(优先级从低到高):
policySettings(组织策略) < userSettings(~/.claude)
< projectSettings(.claude/settings.json)
< envVar < cliArg < session(运行时添加)权限规则语法:
"Bash"— 允许所有 Bash 命令"Bash(git:*)"— 允许所有 git 子命令"Bash(npm:install)"— 仅允许npm install"WebFetch(domain:github.com)"— 允许访问 github.com
BashPermissions 核心流程(bashPermissions.ts):
- 用 tree-sitter AST 解析命令树(识别管道、子命令)
- 检查强制安全规则(裸 shell 前缀等危险模式)
- 逐条匹配 alwaysDeny → alwaysAllow → alwaysAsk 规则
- 未匹配时根据 Mode 决定默认行为
- 可选:调用外部分类器(AI 自动分类安全性)
权限审批 TUI 流程:
- 引擎挂起,向 UI 发送
PermissionRequest - UI 渲染弹框(工具名 + 参数预览 + y/n 选项)
- 用户按键 → 写入回复 channel → 引擎继续
Go 实现方案
权限与 Eino Tool 的集成点:
Eino 的 InvokableTool.InvokableRun() 本身不感知权限,权限逻辑需要包裹在外层。做法是用一个 PermissionMiddleware 包装每个工具:在 InvokableRun 真正执行前,先调用权限引擎,结果为 deny 则直接返回错误,结果为 ask 则通过 channel 暂停等待用户审批。
可利用 Eino compose.ToolsNodeConfig 的 ToolCallMiddlewares 字段统一注入,无需改动每个 Tool 本身:
ToolsConfig: compose.ToolsNodeConfig{
Tools: allTools,
ToolCallMiddlewares: []compose.ToolMiddleware{permissionMiddleware},
}权限数据结构:
Mode:default(询问敏感操作)/plan(只读自动放行)/bypass(全放行)Rule:{ToolName, Pattern, Source},Pattern 示例:"git:*"、"domain:github.com"- 规则来源优先级(低→高):
policy < user < project < envVar < cliArg < session
规则引擎匹配顺序:
- 只读工具 + plan 模式 → 直接 allow
- bypass 模式 → 直接 allow
- 逐条检查 DenyRules → deny
- 逐条检查 AllowRules → allow
- 默认 → ask
Bash 命令匹配规则:
"Bash"→ 匹配所有命令"Bash(git:*)"→ 命令以git开头即匹配"Bash(npm:install)"→ 命令精确以npm install开头
TUI 权限审批(与 bubbletea 集成):
- 权限中间件通过
chan PermRequest向 UI 发送审批请求(含工具名、参数预览、回复 channel) - bubbletea Model 收到消息后渲染弹框,用户按
y/n写入回复 channel - 权限中间件阻塞在回复 channel 上,收到结果后继续或中止工具执行
验收标准:执行 rm -rf 等危险命令前弹框,规则持久化到 ~/.claude/settings.json
Phase 4:斜杠命令系统 + 完整 TUI
目标
实现 /help、/compact、/clear、/config、/cost 等斜杠命令,完善 TUI 体验。
claude-code 的做法
命令类型(commands.ts):
type Command =
| LocalCommand // 本地 JS 函数,直接执行
| PromptCommand // 展开为 LLM 提示(Skill 的本质)
| ExternalCommand // 外部脚本执行命令注册:所有命令在 commands.ts 静态导入,通过功能标志做条件加载(构建时死代码消除)。
Compact 命令实现(services/compact/compact.ts):
三层压缩机制:
- 会话内存压缩(最快):仅压缩本轮新增消息
- 微压缩(中间层):合并连续工具调用为摘要
- 完整压缩(最慢):调用 LLM 生成完整会话摘要,保留关键信息
压缩后恢复:
- 最多恢复 5 个最近编辑过的文件内容(token 预算 50K)
- 最多恢复相关技能内容(token 预算 25K)
- 压缩边界消息作为历史分隔符
Go 实现方案
命令类型(对应 claude-code):
LocalCommand:本地 Go 函数,直接执行(/clear、/cost、/help)PromptCommand:展开为 LLM 提示后送入 Agent(即 Skill,见 Phase 7)
命令注册表:map[string]CommandHandler,key 包含别名。/ 前缀路由:先查命令表,再查 Skill 表,均未命中则提示未知命令。
内置命令要点:
/clear:清空[]*schema.Message历史切片,刷新 TUI 视口/cost:读取ResponseMeta.Usage累计值,格式化输出 token 数和估算 USD 花费/help:列出所有注册命令和已加载 Skill/compact:调用 Phase 5 压缩服务,传入可选自定义指令/config:打开配置编辑界面(bubbletea 全屏表单)
Bubbletea TUI 核心组件(参考 bubbles 库):
textarea.Model:多行输入,Enter提交,Alt+Enter换行viewport.Model:消息历史区域,支持鼠标滚轮spinner.Model:Agent 生成中动画- 权限弹框:自定义覆盖层,
y/n键响应
与 Eino Agent 的集成:
- 用户提交输入 →
agent.Stream(ctx, messages)→ goroutine 中sr.Recv()循环 - 每收到一个 chunk → 发送
tea.Msg给 bubbletea Update 循环 → 追加到 viewport - 工具执行时显示工具名 + 参数摘要(通过 Callback 的
OnStart钩子获取)
验收标准:/clear /cost /compact /help 可用,多行输入、滚动历史正常工作
Phase 5:上下文压缩(Compact)
目标
Token 超过阈值时自动压缩对话历史,保留核心信息,支持手动 /compact 命令。
claude-code 的做法
自动触发阈值:
- 当消息 token 总量超过模型上下文窗口的约 70% 时触发
- 可手动通过
/compact [自定义指令]触发 - 二次触发(reactive compact):压缩后仍超限则再次压缩
压缩系统提示(发给 LLM):
Compress the conversation into a detailed summary preserving:
1. All technical decisions and their rationale
2. Current task status and next steps
3. Key code changes made
4. Important file paths and their purposes
5. Any errors encountered and solutions tried
Keep code snippets that are likely to be referenced again.
Target: ~4000 tokens压缩后恢复(buildPostCompactMessages):
const POST_COMPACT_MAX_FILES_TO_RESTORE = 5 // 最多恢复 5 个文件
const POST_COMPACT_TOKEN_BUDGET = 50_000 // 恢复文件总 token 预算
const POST_COMPACT_MAX_TOKENS_PER_FILE = 5_000 // 单文件最多 5K tokens
const POST_COMPACT_SKILLS_TOKEN_BUDGET = 25_000 // 技能内容 token 预算压缩消息结构:
[user]: [Compact boundary: conversation compressed above]
[assistant]: <summary>压缩后摘要...</summary>
[user]: [Restored file: src/main.go]<file content>Go 实现方案
与 Eino 的集成方式:
压缩本质是"用 Claude 生成摘要",直接复用已有的 claude.NewChatModel,不走 Agent 工具循环,用 model.Generate() 单次调用即可(不需要 react.Agent)。
压缩流程:
- 检查当前
[]*schema.Message的估算 token 是否超过阈值(上下文窗口 × 70%) - 构建压缩提示,调用
chatModel.Generate(ctx, messages)获取摘要 用摘要替换历史消息,结构为:
[user] → "[Compact boundary: conversation compressed above]" [assistant] → <summary>摘要内容</summary> [user] → "[Restored file: path/to/file.go]\n<文件内容>" (最多 5 个,50K token 预算)- 更新 TUI 显示压缩提示
Token 估算:无需精确,简单估算:字符数 ÷ 3(英文约 4 字符/token,中文约 1.5 字符/token,保守取 3)。超过阈值才触发,偏差 10-20% 不影响功能。
自动触发:在 sr.Recv() 循环结束后(每轮 Agent 响应完毕)检查一次,超限则在下一轮发送前先压缩。
验收标准:长对话自动压缩,/compact 手动触发,压缩后对话继续正常
验收标准:长对话自动压缩,/compact 命令可手动触发,压缩后对话可继续
Phase 6:CLAUDE.md + 记忆系统
目标
读取项目 CLAUDE.md 注入上下文,支持 ~/.claude/memory/ 持久记忆目录。
claude-code 的做法
CLAUDE.md 发现规则:
- 从当前工作目录向上逐级查找
CLAUDE.md - 查找
~/.claude/CLAUDE.md(用户全局记忆) - 所有找到的文件按层级注入系统提示的
<project_instructions>标签中
记忆目录结构(memdir/memdir.ts):
~/.claude/projects/<project-slug>/memory/
├── MEMORY.md ← 索引入口(最多 200 行、25KB),每次对话都加载
├── user_role.md ← 用户信息
├── feedback_xxx.md ← 反馈记忆
└── project_xxx.md ← 项目信息MEMORY.md 约束:
- 最多 200 行(超出截断 + 警告)
- 最多 25KB(按字节截断至最后完整行)
- 内容作为记忆索引,每次对话自动注入
自动记忆提取(extractMemories.ts):
- 在每轮对话结束后(stop hook)异步触发
- 触发条件:距上次提取超过阈值 + 有足够新消息(默认 ≥8)
- 使用 forked agent(共享提示缓存,节省 token)生成新记忆
- 记忆类型:reference / decision / gotcha / snippet / debug_info / learned / todo
Go 实现方案
CLAUDE.md 发现规则:从 cwd 向上逐级查找 CLAUDE.md,加上 ~/.claude/CLAUDE.md(全局),全部拼接注入系统提示的 <project_instructions> 标签中。
MEMORY.md 截断规则(严格遵守原版):
- 超过 200 行 → 保留前 200 行,追加截断警告
- 超过 25KB → 截断到最后一个完整行,追加截断警告
与 Eino 的集成:系统提示通过 MessageModifier 注入。每次 agent.Stream() 调用前,MessageModifier 读取最新的 CLAUDE.md 和 MEMORY.md 拼成 system prompt,确保记忆内容始终最新:
react.AgentConfig.MessageModifier = func(ctx, msgs) []*schema.Message {
sys := buildSystemPrompt(cwd) // 含 CLAUDE.md + MEMORY.md
return append([]*schema.Message{schema.SystemMessage(sys)}, msgs...)
}自动记忆提取:每轮 Agent 响应完毕(sr.Recv() 循环结束)后异步触发,条件:新增消息 ≥ 8 条且距上次提取超过阈值。用独立的 chatModel.Generate() 调用(非 Agent 循环)生成记忆条目,写入 ~/.claude/projects/<slug>/memory/ 目录,并更新 MEMORY.md 索引。
验收标准:项目 CLAUDE.md 自动注入,~/.claude/memory/ 下的文件被读取并注入系统提示
Phase 7:Skill 技能系统
目标
支持用户自定义技能(~/.claude/skills/)和内置打包技能,通过 /skill-name 调用。
claude-code 的做法
技能加载三层次:
层 1:打包技能(BundledSkill):
- 编译进二进制(通过 Go embed 或运行时注册)
- 注册时机:程序初始化
层 2:文件系统技能:
- 位置:
~/.claude/skills/<skill-name>/SKILL.md - 格式:Markdown + YAML frontmatter
- 加载:程序启动时扫描
层 3:动态技能发现:
- 触发:用户操作某类文件(如
.tsx文件触发 React 相关技能) - 机制:
activateConditionalSkillsForPaths()按路径匹配激活
SKILL.md frontmatter 格式:
---
name: commit
description: Create a git commit following project conventions
aliases: [git-commit]
whenToUse: When you want to commit changes
argumentHint: "optional: commit message context"
allowedTools: [Bash, FileRead]
model: claude-sonnet-4-6
context: fork # inline | fork(fork = 子代理执行)
---
# Commit
Review staged changes and create a conventional commit.
$ARGUMENTS执行模式:
inline:展开 SKILL.md 内容为用户消息,主代理继续处理fork:启动独立子代理(有独立 token 预算),结果返回主代理
Go 实现方案(Eino)
Skill 定义结构(对应 frontmatter):
name, description, aliases, argumentHint
allowedTools []string — 限制该 Skill 可用工具
model string — 覆盖默认模型
context string — "inline" | "fork"加载器:启动时扫描 ~/.claude/skills/ 和项目 .claude/skills/ 目录,解析每个 SKILL.md 的 YAML frontmatter,注册到 Skill 注册表(与斜杠命令表合并)。内置 Skill 用 embed.FS 打包进二进制。
两种执行模式与 Eino 的对应:
- inline 模式:将 SKILL.md 正文(替换
$ARGUMENTS)拼为用户消息,追加到messages切片,再调用主react.Agent的Stream(),Skill 内容在主对话 token 预算内消耗。 - fork 模式:用
react.NewAgent()创建新的独立 Agent 实例,AllowedTools过滤只注册 frontmatter 指定的工具子集,model字段可覆盖默认模型。调用agent.Generate()同步等待,结果作为工具返回值传回主 Agent。
斜杠命令路由集成:
/commit args → 查命令表(未命中)→ 查 Skill 表(命中 commit)
→ inline: 展开消息 / fork: 启动独立 react.Agent验收标准:
~/.claude/skills/commit/SKILL.md创建后,/commit可用- fork 模式技能在独立 react.Agent 中执行,不占用主对话 token
- 内置打包技能(如
/commit)默认可用
Phase 8:TodoWrite + Task 系统
目标
实现两套任务追踪系统:V1(TodoWrite,适用 SDK/非交互)和 V2(Task 工具集,适用交互 CLI + 多代理)。
claude-code 的做法
V1(TodoWriteTool)—— 内存+AppState:
- 数据存储在
AppState.todos[agentId]中(内存,非持久化) - 每次调用替换整个列表(幂等操作)
- 全部完成(allDone)时清空列表
- 触发验证代理提示(3 个以上任务无验证步骤时)
V2(Task 工具集)—— 文件系统持久化:
- 每个任务存为独立 JSON 文件:
~/.claude_code/config/tasks/{listId}/{id}.json - 并发控制:文件锁(lockfile),重试 30 次
- 高水位标记:防止删除后 ID 重用
- 任务拦截图(blockedBy / blocks):支持依赖关系
- 所有者机制:同伴代理声明任务并独占执行
任务状态机:
pending → in_progress → completed
↓
failed
↓
killed任务 ID 生成策略:
任务 ID = max(现有文件最大 ID, 高水位标记) + 1Go 实现方案(Eino)
TodoWriteTool V1(InvokableTool,内存存储):
- 实现
tool.InvokableTool接口,注册到主react.Agent的ToolsNodeConfig.Tools InvokableRun解析 JSON 参数(todos []TodoItem),全量替换 AppState 中当前 agentID 对应的 todo 列表- 全部 completed 时自动清空;≥3 个任务且无 verif 步骤时在返回值中追加提示
Task V2 工具集(各自实现 InvokableTool):
TaskCreateTool:加文件列表锁 → 生成新 ID(高水位 +1)→ 写{id}.jsonTaskUpdateTool:加单任务锁 → 读取 JSON → 合并 status/owner 字段 → 写回TaskListTool:读取目录所有.json→ 过滤_internal→ 过滤已完成的 blockedBy → 返回TaskGetTool:读取单个任务 JSON 返回TaskStopTool:更新 status 为killed,向后兼容旧shell_id参数
并发控制要点(gofrs/flock):
- 创建任务用列表级锁(
.lock),保证 ID 唯一 - 更新/读取用任务文件锁(
{id}.json.lock) - 锁重试:30 次,间隔 5-100ms
TUI 任务面板:监听 AppState 变化,在输入框上方渲染任务列表,用字符区分状态:
○ pending ◐ in_progress ● completed ✗ failed验收标准:
TodoWriteTool在 SDK 模式下管理任务列表TaskCreate/Update/List可在 CLI 中创建和更新任务- 任务状态在 TUI 底栏实时显示
- 文件锁保证多代理并发安全
Phase 9:MCP 协议客户端
目标
支持通过 MCP 协议动态接入外部工具服务(stdio / SSE / HTTP)。
claude-code 的做法
支持的传输类型:
StdioClientTransport:本地进程(最常用)SSEClientTransport:Server-Sent EventsStreamableHTTPClientTransport:HTTP 流WebSocketTransport:WebSocket
工具转换流程:
client.listTools()→ 获取 MCP 工具定义- 将 MCP ToolInfo 转换为 Claude Tool 格式
- 注册到工具注册表(动态工具)
- MCP 工具名前缀:
mcp__serverName__toolName
Go 实现方案(Eino)
直接使用 eino-ext 官方 MCP Tool 组件:eino-examples 的 go.mod 中已包含 github.com/cloudwego/eino-ext/components/tool/mcp/officialmcp,可直接将 MCP server 的工具接入 Eino 工具体系,无需手写 MCP 客户端。
接入流程:
- 读取
~/.claude/settings.json中的mcp_servers配置(command/args 或 url) - 用
officialmcp包连接 MCP server,获取[]tool.BaseTool - 将这批工具动态追加到
ToolsNodeConfig.Tools,工具名自动加前缀mcp__<serverName>__<toolName> - 重新创建(或热更新)
react.Agent使其感知新工具
支持的传输方式(与 eino-ext 对齐):
- stdio:启动本地子进程,最常用(如
npx @modelcontextprotocol/server-filesystem) - SSE / HTTP:远程 MCP server
/mcp 命令:列出已连接的 MCP server 和工具数量,支持 add/remove/restart 子命令
验收标准:settings.json 中配置 MCP server 后,其工具在 Agent 中可直接调用
Phase 10:多代理系统(Eino 核心)
目标
用 Eino 实现子代理(AgentTool)、团队并发执行,支持代理间消息传递。
claude-code 的做法
AgentTool 输入:
{
description: string // 3-5 字任务描述
prompt: string // 任务提示词
subagent_type?: string // "general-purpose" | "explore" | "plan" | "verify"
model?: string // 模型覆盖
run_in_background?: bool // 后台运行
isolation?: "worktree" // 工作树隔离
}协调器工作流(coordinatorMode.ts):
Phase 1 Research (并行): → 多个 explore 代理并发调研
Phase 2 Synthesis: → 主代理整合研究结果
Phase 3 Implementation: → worker 代理分批修改文件
Phase 4 Verification: → verify 代理测试修改结果并发原则:
- 读取任务(research)→ 自由并行
- 写入任务(implementation)→ 按文件集合串行化(避免冲突)
- 验证任务 → 可与实现并行(不同文件)
Go 实现方案(Eino)
AgentTool(InvokableTool):
- 参数:
description、prompt、subagent_type、model、run_in_background InvokableRun内部调用react.NewAgent()创建独立子 Agent 实例- 子 Agent 使用与主 Agent 相同的
claude.NewChatModel,但可覆盖 model 和 allowedTools - 同步模式:
agent.Generate(ctx, messages)等待完成,返回结果字符串 - 后台模式:goroutine 中
agent.Generate(),用 Phase 8 的TaskCreateTool注册后台任务 ID,主 Agent 可用TaskOutputTool轮询结果
不同子代理类型对应的系统提示:
general-purpose:全功能,默认工具集explore:只读工具(FileRead、Glob、Grep),专注代码调研plan:只读工具 + 输出结构化计划,禁止修改文件verify:Bash(仅运行测试)+ FileRead,专注验证
协调器模式(Eino compose.Graph):
用 compose.NewGraph 把多代理工作流编排为 DAG,参考 eino-examples/compose/graph/ 的模式:
START → research(并行多个 explore Agent,goroutine fan-out)
→ synthesize(主 chatModel 整合研究结果)
→ implement(串行 worker Agent,按文件集合分批)
→ verify(verify Agent 跑测试)→ END- 并行研究:
compose.NewGraph中用多个 Lambda 节点 + 并行边实现 fan-out - 串行写入:用
AddEdge顺序连接,防止多 Agent 同时修改同一文件冲突 - 中断支持:
compose.WithInterruptBeforeNodes在 implement 前暂停,等待用户确认
多轮记忆(eino-examples memory 模式):
子 Agent 的消息历史通过 memory.InMemoryStore 按 agentID 隔离存储,参考 eino-examples/flow/agent/react/memory_example 的 Read/Write 模式。
验收标准:
AgentTool可启动子代理执行独立任务- 后台运行的代理通过 Task 系统追踪状态
- 协调器模式支持并行研究 + 串行实施
各阶段依赖关系
Phase 1 (API + CLI)
↓
Phase 2 (工具系统 + Eino)
↓
Phase 3 (权限系统) Phase 6 (CLAUDE.md)
↓ ↓
Phase 4 (TUI + 斜杠命令) ←──────┘
↓
Phase 5 (Compact) Phase 7 (Skill)
↓ ↓
Phase 8 (Task/Todo) ←──────────┘
↓
Phase 9 (MCP)
↓
Phase 10 (多代理)关键依赖包
| 包 | 版本 | 用途 |
|---|---|---|
github.com/cloudwego/eino | v0.8.5 | 核心框架:Agent、Graph、Tool 接口、Schema |
github.com/cloudwego/eino-ext/components/model/claude | latest | Claude/Anthropic ChatModel |
github.com/cloudwego/eino-ext/components/tool/mcp/officialmcp | latest | MCP 工具接入 |
github.com/cloudwego/eino-ext/callbacks/cozeloop | v0.2.0 | 可观测性 Callback |
github.com/charmbracelet/bubbletea | v1.x | TUI 框架 |
github.com/charmbracelet/bubbles | v0.x | TUI 组件(textarea/viewport/spinner) |
github.com/charmbracelet/lipgloss | v1.x | TUI 样式 |
github.com/spf13/cobra | v1.x | CLI 框架 |
github.com/spf13/viper | v1.x | 配置管理 |
github.com/gofrs/flock | v0.x | 文件锁(Task V2 并发控制) |
gopkg.in/yaml.v3 | v3.x | Skill frontmatter 解析 |
github.com/sergi/go-diff | v1.x | unified diff 生成(FileEdit 返回值) |
参考示例:eino-examples/
flow/agent/react/— ReAct Agent 基础用法、动态选项、多轮记忆compose/graph/tool_call_agent/— 自定义工具调用 Graphcompose/graph/react_with_interrupt/— 中断/检查点compose/graph/async_node/— 异步节点(后台任务)components/tool/mcptool/— MCP 工具接入
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。