阅读时间 6 分钟

Agent 的大脑:13 款工具怎么统一成一个接口

AI 拆解 Multica 的 pkg/agent Backend 接口。13 款编码工具(Claude Code/Codex/Cursor…)共用一个执行抽象,会话恢复、MCP、skill 注入路径、推理档的差异怎么被屏蔽。Multica 拆解系列第 2 篇。
Agent 的大脑:13 款工具怎么统一成一个接口

本文是 Multica 拆解系列的第 2 篇。上一篇讲了 Agent 的"工位"(runtime)。这一篇讲工位上那颗"大脑":13 款 AI 编程工具怎么被同一个接口管住。

这一篇要解决的问题

Multica 同时支持 13 款编码工具:Claude Code、Codex、Cursor、Copilot、Gemini、Hermes、Kimi、Kiro、OpenCode、OpenClaw、Pi、Antigravity、CodeBuddy。

问题来了:这 13 款工具差异极大。有的支持会话恢复,有的不支持;有的读 MCP 配置,有的无视它;skill 文件该放哪个目录每家都不一样;选模型的方式也分"静态"和"动态发现"。如果调度系统要为每款工具写一套专门逻辑,Multica 的代码会变成一个巨型 switch,每加一款工具改一遍。

我通读了 server/pkg/agent/ 下全部 13 个适配器文件,发现 Multica 用了一个经典解法——统一接口,差异下沉

心智模型:一个 Backend 接口,13 个实现

整个适配层的核心是这一个 Go 接口:

// server/pkg/agent/agent.go
type Backend interface {
    // Execute runs a prompt and returns a Session for streaming results.
    Execute(ctx context.Context, prompt string, opts ExecOptions) (*Session, error)
}

13 款工具,13 个结构体,全都实现这同一个 Execute 方法。调度系统(daemon)拿到任务后,根本不关心是哪款工具——它只调 backend.Execute(ctx, prompt, opts),拿到一个 Session,从里面读流式消息和最终结果。

架构图

打开智能体面板,每个 Agent 绑定了自己那款工具(Claude Code、Codex、Kimi……),但调度系统只认同一套执行接口:

Multica 智能体面板:每个 Agent 绑定的工具与在线状态

13 款工具都用同一套接口,但能力差异巨大。这张图展示"统一接口"与"各家方言"的关系:

架构图

这就是适配器模式最纯粹的样子。差异在哪?差异在每个 .go 文件内部——把各自的 CLI 怎么调、stdout 怎么解析、会话怎么恢复,全关在各自的文件里,对外只暴露那个统一的 Execute

深度点一(免费):差异被塞进了 ExecOptions

统一接口不代表忽略差异。差异被集中到 ExecOptions 这一个结构体里,适配器各自挑自己关心的字段、忽略不关心的:

// server/pkg/agent/agent.go
type ExecOptions struct {
    Cwd                       string
    Model                     string
    SystemPrompt              string
    ThreadName                string
    MaxTurns                  int
    Timeout                   time.Duration
    ResumeSessionID           string          // 会话恢复
    ExtraArgs                 []string
    CustomArgs                []string
    McpConfig                 json.RawMessage // MCP 配置
    ThinkingLevel             string          // 推理档
    OpenclawMode              string          // openclaw 专用
}

注意这里有个设计哲学:接口里没有 IsClaude() 这种判断。每个适配器自己决定消费哪些字段。ThinkingLevel 注释写得很清楚:" honoured by claude, codex, opencode; other backends ignore it "——不支持的适配器静默忽略,而不是报错。

反直觉:这套接口的容错原则是优雅降级,不是"能力不匹配就失败"。你想给 Gemini 设推理档?它不支持,但不报错,只是当无事发生——Multica 宁可静默丢掉这个能力,也不让一个不相关的高级选项把整个任务搞崩。

这让"加一款新工具"变成只写一个新文件,完全不碰接口和调度逻辑。

深度点二:13 款工具的能力差异矩阵

接口统一了,但做产品决策时你得知道每款工具到底能干什么。我从代码和文档里整理出这张能力矩阵:

工具 会话恢复 MCP skill 注入路径 模型选择
Claude Code ✅ 真用 .claude/skills/ 静态 + flag
Codex ⚠️ 代码在但不可达 $CODEX_HOME/skills/ 静态
Cursor ⚠️ 代码在但不可用 .cursor/skills/ 动态发现
Copilot .github/skills/ 静态(账号权益)
Gemini .agent_context/skills/(fallback) 静态
Hermes .agent_context/skills/(fallback) 动态发现
Kimi .kimi/skills/ 动态发现
Kiro .kiro/skills/ 动态发现
OpenCode .opencode/skills/ 动态发现 + variant
OpenClaw .agent_context/skills/(fallback) 绑定智能体
Pi .pi/skills/ 动态发现
Antigravity .agents/skills/ 动态发现
CodeBuddy 自有路径 动态发现

这张表解释了为什么用户会困惑"为什么我的 skill 在 Gemini 上不生效"——有 3 款(Gemini/Hermes/OpenClaw)走的是通用 fallback 路径 .agent_context/skills/,而这些工具本身未必从那里读 skill。接口帮你注入了,但工具收不收是它自己的事。

会话恢复这一列尤其关键。Claude Code 的恢复是"真用",意味着一个长任务被打断后能接上之前的上下文;而 Codex/Cursor 标了 ⚠️,意思是适配器代码里有恢复的逻辑,但上游 CLI 当前不可达/不可用——这是个诚实的降级状态,代码里写了但跑不通。

深度点三:推理档(ThinkingLevel)怎么屏蔽差异

最有技术含量的是推理档(ThinkingLevel)。每家工具的"思考强度"参数名和取值都不同:Claude 是 low|medium|high|xhigh|max,Codex 是 none|minimal|low|medium|high|xhigh,OpenCode 是模型 variant 名。如果让调度系统去理解每家的语义,又回到巨型 switch。

Multica 的做法在 server/pkg/agent/thinking.go 里。它维护一张映射表,把 Multica 内部的统一档位翻译成每家工具的原生取值:

// server/pkg/agent/thinking.go
var codebuddyEffortLabel = map[string]string{ ... }
// 每款工具一张映射表:统一档 → 原生值
// 不支持的工具:Execute 里压根不注入,等于忽略

Claude 适配器把统一档翻成 --effort flag,Codex 翻成 reasoning_effort,OpenCode 翻成 variant 名,其它 10 款直接忽略。对调度系统来说,它永远只设一个 ThinkingLevel,翻译是适配器的私事。

这个模式贯穿整个适配层:调度系统说一种语言,适配器负责翻译成各家的方言。这就是为什么 Multica 能同时指挥 13 款工具,而你看板上的体验完全一致——差异被关在各自的 .go 文件里了。

下一篇:大脑有了,同事还得能干活、能汇报——Multica 的任务状态机和三级超时怎么保证一个 Agent 既不偷懒也不会无限卡死。


想看 AI 拆完下一篇?留个邮箱,自动推送。

react-admin.com · Multica GitHub