ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

通过 MCP 实现 Codex 与 Claude Code 的多智能体协作调度

通过 MCP 实现 Codex 与 Claude Code 的多智能体协作调度

过去两周我一直在折腾一件听起来有点绕的事:让 Codex 给 Claude Code 发任务。

起因很现实。我手头一个中型项目,同时开着几个 AI 编程代理,有的擅长拆需求、出整体方案,有的适合在小范围里改代码、补测试,还有的只用来跑文本生成和文档整理。问题是它们各自为政,上下文完全隔离,我本人成了它们之间的“人肉消息队列”,天天把 A 生成的计划复制粘贴给 B,再把 B 的产物转回给 A。这显然不是正确的姿势。

后来我把一个叫 Termexo 的 MCP 服务器接进来,用一套统一的协议把 5 种 Agent 全部纳管,最终实现了“Codex 做调度器,Termexo 做接线员,Claude Code 等 5 个 Agent 做执行端”的多智能体协作链路。这篇文章把整条链路拆开讲:先从为什么需要跨 Agent 发任务说起,然后记录 Termexo 的安装与 5 种 Agent 的接入差异,再带你追踪一条委派指令从 Codex 出发到 Claude Code 跑完任务的完整旅程,最后是三种任务交接模式、踩坑清单和一些调优心得。如果你正从“单 Agent 干活”迈向“多 Agent 编排”,这篇能帮你省掉不少摸索时间。

1. 为什么非要让一个 Agent 给另一个 Agent 发任务

1.1 任务错配是日常,工具链却是割裂的

我越来越觉得,编程代理不是一个万能执行体,更像是一个“能力偏科”的协作者。用 Codex 做主控的时候,它的优势在于把一个大需求拆成若干子任务、保持全局上下文、按计划推进;而 Claude Code 在实际操作中给我的印象是,它在一个具体代码库里做小步重构、补测试、写局部说明文档非常稳,跟项目的耦合度更低,更“听话”。

于是真实项目里我不会只开一个代理,而是哪块活儿适合谁就让谁上手。听起来很合理,对吧?问题在于,每个代理的上下文是彼此隔离的,它们之间不存在“通信”这个能力。你没有办法在 Codex 的会话里直接说“再把这份任务转给 Claude Code 做一下”,因为 Codex 根本不知道 Claude Code 的存在。

你可以说,那就手动复制粘贴,当中间商。短任务还行,任务一多,整个工作流就变成一坨乱麻。我试过最崩溃的一次,是某个 Agent 认真完成了上一步,却把最终结果写在了对话中间位置,输出一长,前面的结论就被上下文窗口挤掉了,我又得从头核对一遍。在这个场景里,人成了不可靠的缓存,成了系统的瓶颈。

1.2 MCP 正好补上了这块缺口

MCP(Model Context Protocol)这套协议,原本解决的是“让 Agent 能调用外部工具”的问题。它的工具注册、参数校验、结果回传机制,几乎就是为“把另一个 Agent 变成工具”量身准备的。现在很多编程代理已经支持配置 MCP 客户端,Codex 里只要声明好 server,它就能自动发现一组工具,并在合适的时机调用它们。

所以这个标题可以拆成一句话:Termexo 是一个把“终端命令”翻译成 MCP 工具的服务器。Codex 是主控客户端,Claude Code 是执行端 CLI,Termexo 坐中间做协议翻译和路由。

这里最妙的一点是,目标 Agent 不需要做任何改造。只要它能用非交互方式跑任务——也就是能在命令行里一次性接收任务文本并输出结果——它就能被纳管。这意味着不管是闭源商业代理,还是本地小模型跑出来的工具脚本,只要有一个可调用的 CLI,都能被装进同一套调度体系。

当然,这种设计也不是没有代价。最直接的问题是:每一次跨 Agent 调用都是进程级的开销,Termexo 必须处理超时、日志、错误码、空输出这些脏活。如果你的 Agent 启动特别慢,体验就会很难受。这个我放到后面踩坑部分细讲。

2. 把 5 种 Agent 接进 Termexo:安装、配置与两种接入姿势

2.1 Termexo 在整套架构里到底扮演什么

用一句话定位:Termexo 是“MCP 世界”和“Agent CLI 世界”之间的桥。它向上用 MCP 协议给 Codex 暴露工具,向下维护一个 Agent 目录,里面记录每个 Agent 的命令行、工作目录、模型偏好、权限边界。

当 Codex 决定把一个任务派给某个 Agent 时,它会向 Termexo 发一个 tool call。Termexo 拿到参数后,按agent_id找到对应命令,启动子进程,把任务文本作为 prompt 传进去,然后捕获 stdout 和 stderr,再按照预设规则把结果转换回 MCP 响应。整个流程对 Codex 来说就像调用一个普通函数,只是这个“函数”背后站着的是一整个 Agent。

我在自己环境里的物理拓扑是这样的:

  • 最上层:Codex,负责接收用户需求、拆解任务、决定用哪个 Agent、验收结果;
  • 中间层:Termexo MCP 服务器,暴露dispatch_agent工具;
  • 底层:5 个 Agent CLI,各自绑定独立的工作目录和权限边界。

这套结构的好处是层次分明。主控只操心“派什么活”,执行端只操心“怎么把活干完”,至于它们之间的通信细节、输出解析、超时重试,全部收敛在 Termexo 这一层。

2.2 安装与最小配置:从零到跑通

Termexo 这类工具在社区里已经有现成实现,我用的版本是 Python 写的,安装比较简单:

pip install termexo-mcp termexo --init

--init会在~/.termexo/下生成一份默认配置。要在 Codex 里启用 Termexo,只需要在 MCP 配置文件中注册:

{ "mcpServers": { "termexo": { "command": "termexo-server", "args": ["--config", "~/.termexo/agents.json"], "env": {} } } }

真正有信息量的是agents.json。这是一份 Agent 目录,告诉 Termexo 外部有哪些 Agent 可以用、怎么调用、在哪个目录下执行、超时多久。我拿其中一个 entry 举例:

{ "agents": { "claude": { "command": "claude", "args": ["-p"], "cwd": "/workspace/code", "timeout": 300, "env": { "MAX_THINKING_TOKENS": "8000" } } } }

这里的-p是 Claude Code 的非交互模式参数,意思是“把任务文本作为参数传入,执行完直接输出结果,不进入交互对话框”。cwd是执行 Agent 时的工作目录,这一步相当关键,它决定了这个 Agent 能看到哪些文件、会在哪个仓库里操作。timeout是单次任务的最长执行时间,超过就会被 Termexo 杀掉并返回超时错误。

2.3 5 种 Agent 的接入差异与名单

我在配置里挂了 5 个 Agent,它们能力互补,同时也代表了几种不同的“接口形态”。这里直接给出我环境里的名单,名字是我自己起的,你完全可以根据手头有什么替换成别的:

序号agent_id底层命令典型用途接入时的特殊点
1claudeclaude -p "task"局部重构、补测试、写文档流式输出,需要等完整结束标志
2local_reasonerlocal-agent --json本地小模型做轻量推理输出较慢,但适合不需要大上下文的子任务
3fix_agentfix-agent --diff-only生成代码补丁,不直接改文件输出是 diff 文本,需要保留完整格式
4doc_agentdoc-agent生成 README、注释、changelog输入可以是目录路径,不必给代码正文
5review_agentreview-agent --auto按预设规则做代码审查输出是 JSON 数组,需要结构化解码

为什么要凑这 5 种?我不是为了凑数,而是为了覆盖不同的“返回类型”。Claude Code 返回的是自然语言描述,local_reasoner返回 JSON,fix_agent返回 diff 文本,review_agent返回结构化 JSON 数组,doc_agent返回的是长文档。Termexo 的解析层需要针对不同返回类型做不同的后处理,我的做法是在每个 Agent entry 里加一个response_parser字段:

{ "agents": { "review_agent": { "command": "review-agent", "args": ["--auto"], "cwd": "/workspace/code", "timeout": 120, "response_parser": "json_array" } } }
> 提示:如果你的目标 Agent 只支持交互式终端,不能通过 `-p` 这类参数直接接受任务文本,建议先在外面包一层薄薄的 CLI 适配脚本,把 stdin 接住再转成 Agent 能理解的输入。没必要为了接入 MCP 去改 Agent 本体代码。

3. 一条委派指令的完整旅程:从 Codex 到 Claude Code

3.1 工具调用链路的每一跳

搞清楚了配置,我们需要理解一条委派指令究竟是怎么穿行整条链路的。我拆成 7 个环节:

  1. Codex 根据当前对话判断需要做某事,比如“补一个回归测试”;
  2. Codex 的 MCP client 把dispatch_agent工具调用发给 Termexo;
  3. Termexo 校验agent_id是否在目录里,参数是否齐全;
  4. Termexo 根据配置构造子进程命令,在工作目录中启动对应 Agent CLI;
  5. 目标 Agent 执行任务,输出结果到 stdout;
  6. Termexo 捕获 stdout/stderr,按response_parser做结构化解码;
  7. 解码后的结果返回给 Codex,Codex 把它当作一次普通工具调用的返回值继续推理。

从 Codex 的视角看,它就是调用了一个函数;

从 Termexo 的视角看,它做了一次进程编排;

从目标 Agent 的视角看,它只是安静地完成了一次命令行任务。

整条链路最容易被忽略的其实是第二步到第三步之间的参数校验。MCP 协议本身对参数是有类型约束的,但 Codex 在自由发挥时经常会把一些附加说明塞进task字段之外的参数里。所以我在 Termexo 的配置里开了strict_schema: true,只允许工具定义里声明过的字段传入,其他字段一律忽略。这一手帮我挡掉了不少“Agent 突然多收了一个不认识参数”的诡异问题。

3.2 一次真实的跨 Agent 任务:补回归测试

我拿一个具体场景来说明。假设主任务是这样的:修正用户表查询里的空指针问题,并补充回归测试。Codex 的规划能力会让它选择这么拆:核心修复它自己做,回归测试派给 Claude Code。

于是 Codex 内部生成的一个工具调用大概长这样:

{ "tool": "dispatch_agent", "arguments": { "agent_id": "claude", "task": "在 /workspace/code 的 tests/ 下新增 test_user_repository.py,覆盖空指针回归场景,运行 pytest 并报告结果。验收标准:新增测试能稳定通过。", "cwd": "/workspace/code", "timeout": 300 } }

Termexo 收到后实际执行的命令是:

cd /workspace/code && claude -p "在 /workspace/code 的 tests/ 下新增 test_user_repository.py,覆盖空指针回归场景,运行 pytest 并报告结果。验收标准:新增测试能稳定通过。"

Claude Code 干完活之后,会在 stdout 输出一段总结,通常包含它改了哪个文件、测试覆盖了哪些分支、测试结果如何。Termexo 会尽可能干净地把这段话捕获,然后作为工具调用的返回值交还给 Codex。

这一步里有一个容易掉进去的坑:Codex 会把返回值当作“既成事实”,如果 Claude Code 只是嘴上说“测试通过”,但没真正运行 pytest,Codex 也分辨不出来。所以我的习惯是在任务描述里明确要求“必须贴出 pytest 的实际输出末尾三行”,让结果里带上可验证的实证信息。

3.3 结果回传的两种模式

Termexo 支持两种结果回传方式,我分别用在不同场景:

模式实现方式适用场景我的经验
全量回传直接把 stdout 前 N 个字符作为结果返回目标 Agent 会输出描述性结论限制 N 一般在 8000 以内,避免撑爆主控上下文
结构化回传配置response_parser,从输出中提取指定字段目标 Agent 输出 JSON 或 diff必须给出示例解析规则,否则解析器会猜

结构化回传对 review_agent 特别重要。它输出的原始 JSON 可能有一大堆,但如果 Codex 只需要错误列表,那 Termexo 可以预先提取出errors[]数组回传。这样主控 Agent 拿到的数据量小、信噪比高,后续决策也更快。

4. 跨 Agent 任务交接的三种实战模式

4.1 RPC 模式:一问一答

RPC 模式是最直观的一种,Codex 发起一次调用,同步等待结果,就像调函数一样。适合单次、短小的任务,比如“让 doc_agent 给当前目录写一段 30 行的 README 快速开始”,或者“让 local_reasoner 把这个报错信息翻译成人话”。

这种模式的实现成本最低,Termexo 只需要同步阻塞地跑一个子进程,得到结果后返回。但它有一个天然限制:主控 Agent 在等待期间不能做任何事,如果目标 Agent 执行时间很长,整个推理过程会被卡住。所以长任务一定要慎用 RPC 模式。

4.2 Job 模式:异步派活与轮询

我试过最典型的场景是:“让 fix_agent 在几百个文件里统一改 import 路径”。这种任务耗时可能超过 10 分钟,如果同步等待,Codex 很可能会因为上下文过长而开始胡言乱语。

解决办法是让 Termexo 支持异步工具。它在dispatch_agent的 schema 里声明了一个额外字段:async: true。Codex 第一次调用时,Termexo 立刻返回一个job_id,然后后台慢慢跑;Codex 拿着job_id再调用job_status查询进度,直到返回completed。

{ "tools": [ { "name": "dispatch_agent", "async": true }, { "name": "job_status", "description": "查询异步任务状态", "inputSchema": { "job_id": "string" } } ] }

这里的关键点是:MCP 工具本身并不区分同步和异步,区分方式完全取决于返回结果里有没有job_id。Codex 看到返回里有job_id,就会自然地把这个工具当作异步任务来处理。Termexo 只需要在工具定义里把返回 schema 声明清楚。

4.3 Pipeline 模式:多 Agent 接力

三种模式里我玩得最上头的是 Pipeline 模式。简单说,就是把多个 Agent 按顺序串成一条流水线,前一个 Agent 的输出直接作为后一个 Agent 的输入。Codex 担任编排者,不需要自己动手做中间加工。

举个例子,我有一条写 changelog 的流水线:

  1. doc_agent 读 git log,生成 changelog 初稿;
  2. review_agent 审查初稿,挑出措辞含糊、缺版本号的地方;
  3. claude 根据审查意见润色,输出最终版本。

在 Codex 里,我给它一个伪代码级的提示就能完成这种编排:

pipeline = [ ("doc_agent", "生成 changelog 初稿,基于 git log 最近 20 条"), ("review_agent", "审查初稿,输出需要修改的点"), ("claude", "根据审查意见润色最终版并保存") ] for agent_id, task in pipeline: result = dispatch(agent_id, task)

Codex 读到这段代码,就会照着顺序依次调用 Termexo 暴露的工具。真正让我意外的是,它在流水线中间遇到 review_agent 返回“初稿太薄,缺结构”这样的结果时,会主动回去让 doc_agent 补充内容,而不是把错误结果往后传。这种“编排者的判断力”是 Pipeline 模式能成立的前提。

5. 踩坑清单:提示词污染、权限边界和上下文穿越

5.1 最先爆的坑:权限边界

我的第一个教训出在权限上。刚开始我图省事,所有 Agent 的cwd都设置成同一个项目目录,想着它们都在同一个仓库里干活,应该没啥问题。结果发现 fix_agent 和 claude 同时开工时,会互相覆盖对方刚刚生成的文件。

这其实不是 Agent 的问题,而是“多个执行者共享同一个工作区”的天然冲突。解决办法是在 Termexo 里给每个 Agent 划分独立的工作目录,比如:

  • claude:/workspace/code/workspaces/claude
  • fix_agent:/workspace/code/workspaces/fix
  • doc_agent:/workspace/code/workspaces/doc

主控 Codex 看到的是整个项目,但具体到每个执行 Agent,它们只拥有自己那份工作区的写权限。跨目录的合并操作由 Codex 统一决定,而不是让 Agent 自己去乱碰。这相当于在 Agent 之间画了清晰的边界,很多莫名其妙的问题都消失了。

> 注意:Termexo 里还有一个 `allowed_paths` 字段,用来限制目标 Agent 能读取的目录列表。哪怕 Agent 本身能力再强,也建议在这个字段上做收紧,而不是交给 Agent 自己自律。

5.2 提示词污染:错误信息被主控 Agent 当素材

第二个坑非常隐蔽,属于“系统性设计缺陷”。当 Termexo 执行的子进程报错时, stderr 里会有大量堆栈信息、 Python traceback、各种环境变量。Termexo 如果把这些东西原封不动地回传给 Codex,Codex 会把这些内容当成“新的任务素材”来解读,于是它有可能基于一段错误堆栈编造出一个看似合理的修复方案。

我遇到过一次很经典的:Claude Code 跑测试时因为环境变量没设好失败了,Termexo 把整个 traceback 传给 Codex,Codex 没意识到这是执行失败,反而在那里分析 traceback 里某个无关变量名,最后得出一个完全跑偏的结论。

解决方式很简单,在 Termexo 配置里限制错误回传的字符数:

{ "max_error_chars": 2000, "error_prefix": "[EXECUTION FAILED]" }

加上[EXECUTION FAILED]前缀后,Codex 会被明确告知“这是失败信号”,而不是普通输出。只回传最后 2000 字符,也防止了堆栈信息把主控的上下文窗口撑爆。

5.3 上下文穿越:不要把你的会话心得塞给执行 Agent

第三个坑,我管它叫“上下文穿越”。Codex 在执行委派时,容易把当下对话里的上下文,连同自己的推理过程、中间草稿、历史尝试,一股脑塞进task字段里。

这会导致什么问题?目标 Agent 收到的是掺杂了大量无关信息的任务描述,它会分不清哪些是最终指令、哪些只是主控的思考过程,结果就是产出偏离预期。我见过最离谱的一次,是目标 Agent 把主控的思考过程当成了用户需求,直接照着那个思路去改代码,改出了一个完全没用的版本。

我的经验是,在 task 描述里坚守隔离原则:只写四样东西——

  1. 明确的目标指令;
  2. 验收标准;
  3. 可用工具和工作目录;
  4. 输出格式要求。

不要写主控自己的推理过程,不要写“我本来想怎样怎样”,也不要把对话历史里那些来回尝试的过程转述过去。给执行 Agent 的信息越干净,它的执行力就越强。这个原则后来被我写成了 Termexo 的任务描述模板,Codex 派活之前必须按模板渲染,效果立竿见影。

5.4 长任务的超时与空输出

最后一个坑是运维层面的。目标 Agent 执行超过timeout后会被 Termexo 杀掉,此时 stdout 可能是空的,退出码非零。如果不做特殊处理,Codex 拿到一个空字符串,大概率会当成“任务完成了,但没有任何结果”——然后它就开始编造结果。

我的处理方式是在 Termexo 里对“超时且空输出”这个组合返回一个特殊的结构化错误码:

{ "error": "TIMEOUT_EMPTY_OUTPUT", "message": "agent claude 执行超时,未产生任何输出", "job_id": "xxx" }

当 Codex 看到error字段,它就会意识到必须重新规划,比如增加 timeout、缩小任务范围,或者换个 Agent 来干。这里如果只是回传“超时”两个字,Codex 也可能搞不明白该怎么处理,所以一定要给出明确的原因和建议动作。

排查这类问题,我的标准链路是三步:先看 Termexo 的日志确认子进程是否被启动;再看目标 Agent 的日志确认它是否卡在某个环节;最后看 Codex 的调用记录确认它传进来的task是否合理。哪一个环节看起来正常,就说明问题出在下一个环节。

6. 方案取舍与调优:为什么我最后选了 Termexo 这类终端桥

6.1 三种接线方案对比

在做这套东西之前,我其实对比过三种接线方案:

方案实现思路优点缺点
直连模式两个 Agent 通过读写同一个项目文件来间接协作不需要额外中间层需要大量人工中转,上下文割裂严重
MCP 终端桥用 Termexo 这类工具把 Agent CLI 封装成 MCP 工具目标 Agent 零改造,天然适配现有 MCP 生态每次调用都有进程开销,需要处理超时和解析
自建 HTTP API给每个 Agent 包一层 HTTP 服务,主控调用 REST 接口灵活、可控、可加鉴权工作量大,每个 Agent 都要单独写服务

我最后选了 MCP 终端桥,最主要的原因就是“目标 Agent 零改造”。我不需要维护一堆常驻的 HTTP 服务,也不用担心某个 Agent 的 API 变了导致全线崩溃,Termexo 把整个差异遮挡在配置层后面。当然它的代价也很明显:每次调用都是冷启动一个子进程,启动慢的 Agent 会很拖累体验。这个我靠后面的常驻会话优化解决了一部分。

6.2 让这套链路变稳的 4 个优化点

如果你打算照着这个思路搭一套多 Agent 协作链路,我建议一开始就加上下面这 4 个优化,能帮你躲开大部分坑:

第一,优先选支持会话复用模式的 Agent。有些 Agent CLI 支持--session或类似参数,可以在多次调用中复用同一个进程,避免每次都重新加载模型、重新读上下文。Termexo 里可以配置session_id字段,把连续任务导向同一个会话。实测下来,单次调用冷启动要 5 秒的话,复用会话能压到 1 秒以内。

第二,给每个 Agent 挂一个专用工作目录,绝对不要共享。多个执行者共享一个目录,等于允许它们互相踩踏。只要把目录分开,大部分“文件被改了但不知道是谁改的”的问题自动消失。

第三,把任务描述模板化。不要让 Codex 每次自由发挥写 task 描述,而是在 Termexo 侧定义好模板,比如task_template,Codex 只需要填变量,模板负责控制格式。这样既能保证执行 Agent 收到的指令结构一致,也能有效防止上下文穿越问题。

第四,给工具调用加上 precheck 钩子。Termexo 可以在真正启动目标 Agent 之前,先执行一条快速检查命令,比如确认目标目录存在、目标文件没被其他 Agent 锁定、git 状态是否干净。如果检查失败,直接返回错误给 Codex,省得把一个注定失败的任务发下去。

这套体系我实际跑了两周,最大的感受是:多 Agent 协作真正难的不是技术接线,而是“任务边界”的划分。Termexo 帮我把 Agent 之间的通信变成了干净的函数调用,但真正决定这条链路稳不稳的,是每个 Agent 拿到手的任务是否清晰、权限是否收敛、输出是否可验证。建议你按我上面说的顺序先把一个最小闭环跑通——让 Codex 通过 Termexo 给 Claude Code 派一个补测试的小任务——再逐步加 Agent、加流水线,你会比我当初少走很多弯路。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进