终端 AI Agent 之间如何可靠通信:原生 PTY 与 API 封装对比

Claude Code、Codex 和 Pi 是为坐在键盘前的人设计的,而不是为了接收其他 Agent 的消息。本文讲清楚 Agent 互相通信时会出什么问题、三种解决思路,以及如何保证每条消息既不丢失、也不会被投递两次。

要点速览
  • 交互式 Agent CLI 没有接收外部消息的接口。想把消息送进去,要么用 API 封装把它替换掉,要么往它的终端里输入。
  • API 封装会丢掉官方 CLI 的工具、配置和订阅;直接用 tmux send-keys 虽然保住了 CLI,却会丢消息、输错窗口、冲掉你写到一半的草稿。
  • 可靠投递需要六项保证:先落盘、认准目标、同一时间只有一个写入者、绝不覆盖人的输入、遵守每个 CLI 自己的提交规则、如实回执。
  • Team Agent 用追加式收件箱文件加原生 PTY 输入实现了这六项保证,消息以普通提示词的形式到达 Agent。

01CLI Agent 之间通信为什么这么难

Web 服务有 API,交互式编程 Agent 有的是终端用户界面:一个输入框、一块不断重绘的屏幕,外加一堆快捷键。这对你来说很顺手,对另一个程序来说却很别扭。凡是尝试过让两个 Agent 协作的人,都会遇到这些问题:

  • 没有入站通道。官方并没有提供从外部向运行中的 Claude Code 或 Codex 会话推送消息的方式。
  • 每个 CLI 的提交方式都不一样。有的会特殊处理粘贴文本,有的需要在回车前稍等片刻,多行输入在每个 CLI 里的表现都不同,而且这些规则还会随版本变化。
  • Agent 经常在忙。任务进行中收到的消息必须等待,但不能丢。
  • 人和 Agent 共用一个窗口。如果你正写到一半,另一个 Agent 的消息落进了同一个输入框,两边的内容都会被毁掉。
  • 终端地址会被复用。tmux 重启后,像 %3 这样的窗格编号可能已经属于一个完全不同的程序。
  • 没有确认回执。按下回车之后,CLI 不会告诉另一个程序“已收到”。你只知道自己输入了什么。

02Agent 间通信的三种思路

1. API 封装

绕过 CLI,直接调用模型 API,自己写一套 Agent 循环。你得到了干净的编程接口,却也丢掉了 CLI 最有价值的几乎所有东西:内置工具、项目配置、权限模型、斜杠命令、会话历史,以及你已经付费的订阅登录。从此按 token 计费,而且这个 Agent 的表现也和你每天在用的那个不一样了。

2. 直接往终端里敲

把每个 CLI 放进 tmux,用 tmux send-keys 往里输入。真正的 CLI 保住了,演示时一行命令也确实能跑通:

tmux send-keys -t %3 "请审查最新的一次提交" Enter

可一到日常使用就漏洞百出:多行文本只提交了一半;粘贴还没处理完,回车就先到了;Agent 正忙,文字就堆在输入框里;你正在打字,草稿就成了一团乱码;%3 被复用了,消息就发给了错误的程序。而一旦出错,没有任何地方记得这条消息存在过。

3. 带保证的原生 PTY 投递

保留真正的 CLI 和它的终端,但在“打字”外面包一层投递协议,补上存储、身份校验和安全规则。PTY(伪终端)是每个交互式程序读取按键的虚拟终端设备,向它写入内容就完全等同于打字。Team Agent 采用的就是这种方式。

API 封装直接 send-keys原生 PTY + 收件箱
使用官方 CLI 及其工具否是是
沿用你的订阅登录否,按 API 计费是是
Agent 在忙或不在时消息不丢取决于你的代码否是,先落盘
保护你写到一半的草稿不适用否是
输入前校验目标不适用否是
避免重复投递视情况而定否是,结果不明时绝不重复输入
可读的消息历史视情况而定否普通的追加式文件

03Team Agent 如何投递一条消息

无论是 Agent 调用 send_message 工具,还是你执行 team-agent send backend "…",投递都按固定顺序进行:

  1. 先落盘。在输入任何内容之前,消息会作为一条完整记录追加到收件人的收件箱文件中。从这一刻起,无论后面发生什么,消息都不会丢。记录只会追加,从不修改。
  2. 认准目标终端。Team Agent 会确认目标窗格仍然属于这个 Agent,而不只是“存在一个同号的窗格”。被复用的地址会被拒绝,而不是硬往里输入。
  3. 同一时间只有一个写入者。对同一个目标的输入是串行的,两个发送方的按键永远不会交错成一段乱码。
  4. 绝不覆盖人的输入。如果输入框里已经有内容,比如你正在打字,Team Agent 会在有限时间内(大约一分钟)等它清空。等不到就不输入,消息安然留在收件箱里。
  5. 遵守每个 CLI 的“方言”。先粘贴文本,再按该 CLI 需要的规则提交。例如 Claude Code 和 Cursor 在回车前停顿 100 毫秒,Grok 停顿 150 毫秒,Pi 和 Codex 不需要停顿。只发送一次回车,不做盲目重试。
  6. 如实回执。发送方会得知实际发生了什么:已提交、已延后、目标不存在,或者结果未知。只要结果不明,消息就绝不会被输入第二次,因为重复的指令比迟到的指令更糟糕。
已提交不等于已完成

输入并按下回车,只能证明消息到达了 Agent 的输入框,并不能证明 Agent 已经做完了工作。所以“回复”是单独的、明确的一步。

04回复是怎么回来的

每名队员都会获得三个小工具,通过 MCP(Claude Code、Codex 和 Pi 都支持的模型上下文协议)提供:

  • send_message:直接给其他队员或组长发消息。
  • report_result:提交已完成的任务,附带状态、测试和风险说明。
  • get_team_status:查看团队成员及各自的状态。

结果会先写入组长的收件箱,再按同样的安全投递步骤显示到组长的终端里。如果组长正在忙,结果就在收件箱里等着,不会凭空消失。除此之外,Agent 不会被添加任何东西,它们自己的工具和配置保持原样。

05在命令行中使用

# 给队员派任务(先落盘,再输入到它的终端)
team-agent send backend "给 GET /v1/invoices 加上分页,并补上测试"

# 只存入收件箱,不输入到对话中
team-agent send backend "有空时看一下这个" --mailbox

# 查看组长收到的最新回复
team-agent inbox leader -n 3

# 查看每名队员的供应商、活动状态和健康状况
team-agent status

收件箱就是普通的追加式文件,消息历史很容易审计和备份。不需要打开、加锁或迁移任何数据库。

06Agent 间消息系统的设计清单

想自己实现?下面这些特性最重要,按“缺了它最常引发真实事故”的顺序排列:

  1. 先持久化,再投递。在产生任何副作用之前,先把消息存下来。
  2. 屏幕上最多出现一次。无法确认结果的尝试,绝不重新输入。
  3. 认身份,不认位置。校验终端地址背后的进程,而不只是地址本身。
  4. 按目标加锁。对每个终端的输入串行化;绝不为了给一个 Agent 发消息而锁住所有 Agent。
  5. 保护草稿。确认输入框为空;人的草稿永远优先。
  6. 每个 CLI 一套提交规则。把粘贴和回车的行为作为每个 CLI 的配置数据,而不是靠猜。
  7. 如实回执。区分“已提交”“已延后”“已不在”和“未知”,不要一律归为“已投递”。
  8. 可读的存储。使用开发者能用普通工具直接查看的格式,出问题时一眼就能看清。

07常见问题

多个 Claude Code Agent 之间怎样互相通信?

Claude Code 这类交互式 CLI 本身没有接收其他 Agent 消息的机制。Team Agent 这样的协调工具会给每个 Agent 提供发消息和汇报结果的小工具,先把每条消息存进收件人的收件箱,再把它输入到收件人的终端里,让消息以普通提示词的形式到达。

怎样从另一个终端给正在运行的 Claude Code 或 Codex 会话发消息?

如果会话运行在 tmux 中,可以用 tmux send-keys 向它输入,但你得自己处理多行文本、回车时机、目标窗格的身份,以及输入框里已有的内容。Team Agent 把这些检查封装成了一条命令:team-agent send <agent> "消息"。

什么是 PTY?

PTY(伪终端)是交互式程序读取键盘输入、绘制屏幕所用的虚拟终端设备。终端模拟器和 tmux 都会创建 PTY。向 Agent 的 PTY 写入内容,就等同于在它的窗口里打字。

接收方 Agent 正在忙,消息会丢吗?

采用“先落盘再投递”的设计就不会。Team Agent 会先把每条消息写入收件人的追加式收件箱,再尝试输入,所以即使 Agent 正在忙、终端已经不在或者投递需要等待,消息都还在。

为什么不直接调用模型 API,而要驱动 CLI?

API 封装相当于重写一遍 Agent 循环:你会失去官方 CLI 的工具、配置、权限模型和订阅登录,还要按 token 付费。通过终端驱动真正的 CLI,一切都保持你配置好的样子。

延伸阅读