ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Rust 实现 Claude Code 与 Codex 跨机协作:消息队列驱动多 Agent 互唤醒

Rust 实现 Claude Code 与 Codex 跨机协作:消息队列驱动多 Agent 互唤醒

1. 为什么要把 Claude Code 和 Codex 接在一起用

1.1 两个 Agent 各自的脾气

Claude Code 和 Codex 是目前命令行里最常被拿来对比的两个 AI 编程 Agent。我两个都用了一段时间,说句实在话,它们不是替代关系,而是互补关系。

Claude Code 的长处在于长上下文理解和多文件重构。你给它一个模糊的需求,比如“把项目里所有跟用户鉴权相关的逻辑抽成一个独立模块”,它能自己翻文件、自己找依赖、自己改调用点,整个过程不需要你一步步喂。它的工具调用链比较深,适合做那种“想清楚再动手”的活。

Codex 的长处在于单点执行的确定性和沙箱隔离。你让它跑一条命令、改一个函数、生成一段测试,它给的结果很稳,而且默认在受限环境里跑,不会乱动你的系统。它更像一个听话的执行器,你指哪它打哪。

问题就出在这:我经常需要它们配合。比如让 Claude Code 做架构级的重构规划,规划完了让 Codex 去逐条执行;或者反过来,Codex 跑测试发现挂了,把失败信息丢给 Claude Code 去分析根因。以前我只能手动复制粘贴,一来一回效率极低,而且上下文一断,两边都“失忆”。

1.2 跨会话通信到底难在哪

很多人以为让两个 Agent 互相发消息很简单,不就是调个 API 吗。实际做下来,难点根本不在“发消息”,而在下面这几件事:

  • 会话状态怎么保持:Claude Code 和 Codex 各自维护自己的会话上下文,你从外部塞一条消息进去,它未必认,因为它不知道这条消息属于哪个会话、该接在哪轮对话后面。
  • 谁来“叫醒”谁:Agent 默认是被动等待输入的,你不给它一个触发信号,它就一直挂着。所谓“互相叫醒”,本质是要有一个常驻的调度层去监听事件、判断该唤醒哪个 Agent。
  • 跨机器的消息怎么走:如果两个 Agent 跑在同一台机器上,用本地 socket 或文件队列就够了;但一旦跨电脑,就得考虑网络传输、消息序列化、断线重连。
  • 消息格式怎么统一:Claude Code 和 Codex 对输入的理解方式不一样,你得设计一个中间层,把一方的输出翻译成另一方看得懂的输入。

我写这个工具的核心动机,就是把上面这四件事一次性解决掉,让两个 Agent 像两个同事一样能互相喊话、互相交接任务。

1.3 这个工具适合谁

如果你只是偶尔用一下 AI 写代码,那这个工具对你意义不大,手动复制粘贴完全够用。但如果你符合下面任意一条,它就值得你花时间搭起来:

  • 你同时用 Claude Code 和 Codex,且经常需要它们协作完成一个任务;
  • 你有多个开发机,希望在一台机器上触发的任务能在另一台机器上被 Agent 接手;
  • 你在做 AI Agent 相关的开发或研究,想看看多 Agent 协作在真实工程里怎么落地;
  • 你想让 Agent 之间形成“规划-执行-验证”的闭环,而不是每次都人工介入。

下面我按“整体设计思路 → 核心机制拆解 → 实操搭建 → 问题排查”的顺序,把这个工具从头到尾讲清楚。涉及具体实现的地方,我会给出可直接参考的代码和配置。

2. 整体设计思路与架构选型

2.1 为什么选消息队列而不是直接调 API

最开始我试过最朴素的做法:写个脚本,A 的输出直接作为参数去调 B 的命令行。跑了两天就放弃了,原因有三个。

第一,阻塞问题。Claude Code 处理一个复杂任务可能要几分钟,脚本会一直卡在那里等,期间什么都干不了。第二,可靠性问题。中间任何一步失败,整条链就断了,没有重试、没有补偿。第三,扩展性问题。我想加第三个 Agent 进来,或者想让消息跨机器走,这套直连的写法就得推倒重来。

所以我改成了消息队列 + 事件驱动的架构。核心思路是:每个 Agent 外面包一层“适配器”,适配器负责把 Agent 的输入输出翻译成统一的消息格式,然后所有消息都往一个队列里丢。调度器从队列里取消息,判断这条消息该给谁,再通过对应的适配器唤醒那个 Agent。

这个设计的好处是解耦。Agent 之间不直接认识对方,它们只认识队列。我想加新 Agent、想换传输方式、想加消息过滤,都只动调度层,不动 Agent 本身。

2.2 三层架构拆解

整个工具我分成三层,从下往上说。

传输层负责消息怎么从 A 到 B。同机通信用 Unix domain socket,跨机通信用 TCP 加一层简单的消息帧协议。我没上 Kafka、RabbitMQ 这种重型中间件,因为对个人开发者来说太重了,维护成本高,而且这个场景的消息量根本用不上。一个轻量的本地队列加 TCP 转发就够了。

调度层是核心,负责“谁该被叫醒”。它维护一张 Agent 注册表,记录每个 Agent 的 ID、类型、所在机器、当前状态(空闲/忙碌)。当一条消息进来,调度器根据消息里的目标字段决定路由,如果目标 Agent 正忙,消息就排队等待。

适配层是每个 Agent 的“翻译官”。Claude Code 适配器知道怎么把一条标准消息转成 Claude Code 能接受的输入格式,也知道怎么从它的输出里提取出有效内容。Codex 适配器同理。这一层是跟具体 Agent 强相关的,换 Agent 主要就是换这一层。

2.3 消息协议怎么定

消息格式我参考了常见的 Agent 通信协议思路,但做了简化。一条消息包含这几个字段:

字段类型说明
msg_idstring消息唯一 ID,用于去重和追踪
fromstring发送方 Agent ID
tostring目标 Agent ID,为空表示广播
typestring消息类型:task / result / query / notify
sessionstring会话 ID,用于关联同一任务的多轮消息
payloadobject实际内容,结构随 type 变化
timestampint发送时间戳,用于排序和超时判断

session字段是我踩坑之后加的。最开始没有它,结果两个 Agent 同时处理多个任务时,消息全串了,A 任务的回复跑到了 B 任务的上下文里。加上 session 之后,每个任务有独立的会话线,互不干扰。

type字段决定了接收方怎么处理。task是派活,接收方要执行;result是交活,接收方通常只记录;query是提问,期待对方回一条result;notify是纯通知,不需要回复。

2.4 为什么用 Rust 写核心

热词里有人问“基于 Rust 语言的 AI Agent”靠不靠谱,我用下来觉得这个选择是对的。核心调度层我用 Rust 写,原因很实际:

  • 并发模型省心:调度器要同时监听多个 Agent 的状态、处理队列、转发消息,Rust 的 async 加 channel 写起来很干净,不用担心数据竞争。
  • 单文件部署:编译出来就是一个二进制,扔到任何一台机器上都能跑,不需要装运行时。跨机器部署时这点特别爽。
  • 资源占用低:调度器常驻在后台,Rust 版本内存占用稳定在几十兆,换成某些带 GC 的语言,跑久了内存会飘。

适配层我留了脚本接口,因为不同 Agent 的调用方式变化快,用脚本改起来比重新编译快。核心用 Rust,边缘用脚本,这个组合我个人比较满意。

3. 核心机制拆解:叫醒、传话、跨机

3.1 “互相叫醒”是怎么实现的

Agent 本身不会主动醒来,所以“叫醒”这个动作必须由外部触发。我的做法是给每个 Agent 配一个守护进程,守护进程常驻,监听队列里属于自己的消息。

守护进程的逻辑很简单:从队列取消息 → 判断是不是给自己的 → 是的话,把消息转成 Agent 的输入格式 → 启动 Agent 进程 → 把输入喂进去 → 等 Agent 输出 → 把输出打包成消息丢回队列。

这里有个关键细节:Agent 进程是每次唤醒时新起的,还是常驻的。我两种都试过。常驻的好处是启动快、上下文连续;坏处是状态容易脏,一个任务出错可能污染后续任务。新起的好处是干净、隔离好;坏处是每次都要重新加载上下文,慢。

最后我选了混合模式:同一个 session 内的多轮消息复用同一个 Agent 进程,session 结束后进程销毁。这样既保证了单任务内的上下文连续,又避免了跨任务污染。

3.2 消息怎么从一方“翻译”给另一方

Claude Code 和 Codex 对输入的理解不一样,直接转发原文经常出问题。我举个例子:Claude Code 输出的重构方案里可能带一堆 Markdown 格式的说明文字,Codex 拿到这种输入会懵,因为它期待的是明确的指令。

所以适配层要做内容提取和格式转换。我的做法是在消息 payload 里区分“给机器看的”和“给人看的”两部分。给机器看的是结构化的指令,比如{"action": "run_tests", "target": "src/auth"};给人看的是自然语言说明。转发时只把结构化部分传给对方 Agent,自然语言部分存起来供人查阅。

这个设计还有个额外好处:可审计。所有消息都留了记录,出问题的时候能回溯到底哪一步传错了。

3.3 跨电脑通信的落地方式

跨机器这块,我用的是最朴素的方案:中心调度 + TCP 转发。

假设你有两台机器,机器 A 跑 Claude Code,机器 B 跑 Codex。你在两台机器上都装调度器的客户端,客户端连到同一个调度中心(可以跑在第三台机器,也可以跑在其中一台)。消息从 A 的客户端发出,经调度中心路由,到 B 的客户端,再唤醒 B 上的 Codex。

为什么不搞点对点直连?因为点对点要处理 NAT 穿透、要维护连接状态,复杂度高。中心调度虽然多了一跳,但逻辑简单、好调试,而且调度中心天然就是消息的审计点。

传输安全方面,我在 TCP 之上加了一层简单的加密和校验,防止消息在网络上被篡改。具体实现不展开,思路就是用对称加密保护 payload,用 HMAC 做完整性校验。

3.4 会话隔离与并发控制

前面提到 session 字段解决消息串线的问题,这里展开说下并发控制。

调度器维护一个会话表,每个 session 记录当前状态:进行中、等待回复、已完成。同一 session 内的消息严格串行处理,保证顺序;不同 session 之间并行,互不影响。

还有个超时机制。如果一条query消息发出去,超过设定时间没收到result,调度器会标记这个 session 为异常,并通知发起方。超时时间我默认设 5 分钟,因为复杂任务确实可能跑很久,设太短会误判。

注意:并发数不要开太大。我一开始把并发调到 10,结果两个 Agent 同时抢同一份文件,改出了冲突。后来限制到 3,稳定多了。Agent 协作不是并发越高越好,关键是任务之间有没有资源竞争。

4. 实操搭建:从零把工具跑起来

4.1 环境准备与依赖安装

先说环境。我主力是 macOS,但也在 Ubuntu 上跑过,两边都 OK。Windows 的话建议用 WSL,原生 Windows 下 Unix socket 支持不完整,会踩坑。

需要装的东西:

  • Rust 工具链(编译调度器核心)
  • Claude Code(官方安装方式,装完确认claude命令能用)
  • Codex(同样确认命令行可用)
  • Python 3.10+(适配层脚本用)

Rust 安装用官方脚本就行,装完cargo --version能出版本号就对了。Claude Code 和 Codex 的安装各自按官方文档走,这里不重复,重点确认它们能在终端里正常启动、正常接受输入。

4.2 调度器核心的编译与配置

调度器的配置文件我用 TOML,放在~/.agent-bridge/config.toml。一个最小配置长这样:

[center] listen = "0.0.0.0:7788" secret = "your-shared-secret" [agents.claude] type = "claude-code" machine = "local" adapter = "adapters/claude.py" [agents.codex] type = "codex" machine = "remote-b" adapter = "adapters/codex.py"

secret是跨机通信的共享密钥,两台机器要一致。machine字段标记 Agent 在哪台机器上,local表示本机,其他值对应远程客户端注册的机器名。

编译调度器:

cd agent-bridge cargo build --release

编译完二进制在target/release/agent-bridge。启动:

./agent-bridge --config ~/.agent-bridge/config.toml

启动后它会打印监听地址和已注册的 Agent 列表。看到两个 Agent 都注册上了,说明配置没问题。

4.3 适配器脚本的编写要点

适配器是脚本,每个 Agent 一个。以 Claude Code 适配器为例,核心要做三件事:接收标准消息、转成 Claude Code 输入、把输出转回标准消息。

import json import subprocess import sys def handle(msg): payload = msg["payload"] # 把结构化指令转成自然语言提示 prompt = build_prompt(payload) # 调用 claude 命令行 result = subprocess.run( ["claude", "-p", prompt], capture_output=True, text=True, timeout=600 ) # 把输出打包成标准消息 return { "msg_id": new_id(), "from": "claude", "to": msg["from"], "type": "result", "session": msg["session"], "payload": {"raw": result.stdout}, "timestamp": now() }

build_prompt是关键,它决定了结构化指令怎么变成 Agent 看得懂的话。我的经验是提示要具体、要带上下文,别指望 Agent 猜。比如{"action": "run_tests"}要展开成“请在项目根目录运行测试套件,把失败的用例和错误信息完整返回”。

Codex 适配器结构一样,只是调用命令和输出解析不同。Codex 的输出格式更规整,解析起来反而简单。

4.4 跨机部署的完整步骤

假设机器 A 跑调度中心加 Claude Code,机器 B 跑 Codex。

机器 A 上:启动调度中心,确认listen地址是0.0.0.0:7788,防火墙放行这个端口。启动 Claude Code 适配器,注册到本地调度中心。

机器 B 上:装调度器客户端,配置里center指向机器 A 的 IP 和端口,secret跟 A 一致。启动客户端,它会连到 A 的调度中心并注册自己。然后启动 Codex 适配器。

验证连通性:在机器 A 上发一条测试消息给 Codex,看机器 B 上 Codex 有没有被唤醒、有没有回消息。我第一次配的时候卡在防火墙,消息发出去没反应,查了半天才发现是端口没放行。

4.5 一个完整的协作流程演示

光说机制太抽象,走一个真实流程。

场景:我在机器 A 上让 Claude Code 分析一个 bug,分析完让它把修复任务派给机器 B 的 Codex 执行。

第一步,我给 Claude Code 发指令:“分析 src/auth 目录下的登录失败问题,找出根因,把修复方案整理成结构化任务。”

第二步,Claude Code 分析完,输出一段分析加一个结构化任务。适配器把结构化任务打包成task类型消息,to字段填codex,丢进队列。

第三步,调度中心收到消息,查注册表发现 codex 在机器 B,转发过去。

第四步,机器 B 的客户端收到消息,唤醒 Codex 适配器,Codex 执行修复。

第五步,Codex 执行完,输出结果打包成result消息,to字段填claude,原路返回。

第六步,机器 A 的 Claude Code 收到结果,可以继续做验证或收尾。

整个过程我只需要发第一条指令,后面全自动。这就是“互相叫醒、互发消息”的实际价值。

5. 常见问题与排查技巧实录

5.1 消息发出去了但对方没反应

这是最常见的问题,排查顺序我总结成一张表:

排查点检查方法常见原因
调度中心是否收到看调度中心日志发送方适配器没连上
路由是否正确看消息的 to 字段和注册表Agent ID 拼写错误
目标机器是否在线ping 或看客户端心跳网络断了或客户端挂了
目标 Agent 是否被唤醒看目标机器进程列表适配器脚本报错退出
Agent 是否卡住看 Agent 进程 CPU 占用任务太复杂或死循环

我遇到最多的是适配器脚本报错。脚本里任何未捕获的异常都会导致适配器静默退出,消息就石沉大海。后来我在适配器外面包了一层异常捕获,出错时把错误信息作为result发回去,至少让发起方知道出问题了。

5.2 会话串线怎么定位

会话串线的表现是:A 任务的回复跑到了 B 任务的上下文里,Agent 开始答非所问。

定位方法:把调度中心的消息日志按 session 分组打印,看有没有消息的 session 字段跟它实际所属的任务对不上。我遇到过一次,原因是适配器在生成回复消息时,session 字段是从一个全局变量读的,并发时被覆盖了。改成从当前消息里透传 session 就解决了。

经验:session 字段一定要透传,不要重新生成。发起方生成一次,后续所有相关消息都带着它,直到任务结束。

5.3 Agent 输出格式不稳定怎么办

Claude Code 和 Codex 的输出都是自然语言为主,格式不固定。你让它输出 JSON,它可能给你包一层 Markdown 代码块;你让它输出纯文本,它可能加一堆解释。

我的处理方式是容错解析。解析器先尝试直接解析 JSON,失败就尝试剥离 Markdown 代码块再解析,再失败就用正则提取关键字段。三层兜底下来,绝大多数输出都能处理。

如果实在解析不出来,就把原始输出整个存下来,标记为“需人工处理”,不要让流程卡死。Agent 协作系统里,能降级处理比追求完美解析更重要。

5.4 跨机延迟高怎么优化

跨机通信的延迟主要来自三块:网络往返、消息序列化、Agent 启动。

网络往返没法优化太多,除非你把两台机器放同一个局域网。序列化我用的 JSON,够用,如果追求极致可以换 MessagePack,但收益有限。真正能优化的是Agent 启动。

前面说过我用了混合模式,同 session 复用进程。实测下来,复用进程比每次新起能省 3 到 5 秒。对于多轮交互的任务,这个优化很值。

还有个技巧:预热。调度器启动时,可以预先拉起一个空闲的 Agent 进程待命,消息来了直接喂给它,省掉冷启动时间。这个我还在试验,效果不错但内存占用会高一些。

5.5 安全与权限的注意事项

Agent 能执行命令,这意味着它能改文件、能跑脚本。跨机场景下,一台机器上的 Agent 可能被另一台机器上的消息触发去执行操作,这里面的权限控制必须想清楚。

我的做法是白名单加确认。适配器里维护一个允许执行的操作白名单,白名单外的操作需要人工确认。跨机消息默认只允许读操作和测试操作,写操作要显式授权。

另外,secret密钥不要硬编码在配置里明文存,用环境变量注入。跨机通信的加密密钥定期轮换。这些是基本的安全习惯,别嫌麻烦。

6. 我踩过的坑和几条实在建议

6.1 别一上来就搞跨机

我最开始就是直接上跨机,结果调试成本极高。一个 bug 你分不清是本地逻辑问题还是网络问题,排查起来两头跑。

后来我改成先本机跑通,再拆到跨机。本机用 Unix socket,逻辑验证完,再把传输层换成 TCP,其他代码不动。这样每一步的变量都可控,出问题好定位。

6.2 日志要打够,但要能关

调试期我把日志打到最详细,每条消息的收发、每次 Agent 的唤醒都记。上线后日志量太大,磁盘扛不住,又加了日志级别开关。

建议从一开始就设计好日志分级:error只记异常,info记关键流程,debug记全部细节。默认跑info,排查问题时临时开debug。

6.3 Agent 的“脾气”要摸清

Claude Code 和 Codex 在不同任务上的表现差异很大。有的任务 Claude Code 一次就过,Codex 要来回好几次;有的反过来。

我的建议是给每个 Agent 配不同的超时和重试策略。Claude Code 适合长任务,超时设长点;Codex 适合短任务,超时短、重试多。这些参数在适配器里配,不要写死。

6.4 这个工具后续还能怎么扩展

目前这个工具解决的是两个 Agent 的协作。往大了想,它可以扩展成多 Agent 编排平台:加更多 Agent 类型、加任务依赖图、加可视化面板。

我最近在试的一个方向是让 Agent 自己决定该叫醒谁。现在的路由是我在消息里写死to字段,未来可以让 Agent 根据任务内容自己判断该找哪个 Agent 协作。这需要给 Agent 一份“同事能力清单”,让它知道每个 Agent 擅长什么。

还有个方向是任务持久化。现在消息在内存队列里,调度器重启就丢了。加上持久化之后,任务可以跨重启恢复,这对长时间运行的任务很重要。

6.5 给想复现的朋友的几句实话

这个工具不算复杂,但也不是一两个小时能搞定的。如果你只是想体验一下两个 Agent 协作,我建议先用最简单的脚本直连跑通,感受一下价值,再决定要不要上这套完整架构。

如果你决定搭,从本机单 Agent 开始,先让一个 Agent 能被消息唤醒并回复,再加第二个,再拆跨机。每一步都验证通过再往下走,别跳步。

最后,Agent 协作的价值不在于技术多炫,而在于它真的能帮你省时间。如果搭完之后你发现手动复制粘贴更快,那说明你的任务场景还不适合自动化,别硬上。工具是为人服务的,不是反过来。

RELATED READING

延伸阅读

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