阅读时间约 22 分钟

a2a 设计思路:让多个 Coding Agent 协作,不靠模型记住操作手册

借鉴《将军赶路不追小兔》的调度思路,加上门禁、两层主控和用户输入记录

Posted by LuckyE on October 11, 2026
AI Engineering Multi-Agent Claude Code Codex 软件工程

本文只讲设计思路。a2a 的工作台还在开发,文中没有产品截图,也不对效率或可靠性作出收益声明。等软件能日常使用,我再补上真实产品与运行记录。

a2a 是我给自己做的多 Agent 编排工具:Claude Code 当主控,Codex 当 worker 和审查者,大家在同一个项目里分工干活。它要解决的问题可以用一句话说清:

让协作不依赖模型持续记住操作手册。

早期我把规程都写在提示词里:先认领、只改这几个目录、交付时报提交号、别自己合并……对话一长,模型就会漏掉其中某条。漏掉的后果通常不是报错,而是一句看上去很可信的“已完成”。a2a 的思路是把能机械核对的规程搬出提示词,交给脚本和门禁;提示词只留下需要判断的部分。

这篇文章分三部分:从立行的《将军赶路不追小兔:多 Agent 调度体系的架构与实践》里借了什么、刻意没借什么;a2a 自己加了哪些东西;以及做到现在踩过的几个坑。

一、方向改过三次

a2a 从 2026 年 10 月 6 日开始做,到写这篇文章时,方向已经改了三次。每次改动都有具体原因。

阶段 做法 为什么放弃或改变
第一阶段(10-06) 在终端多路复用器 Herdr 上跑 agent,用一个 Python 脚本实现认领、交付、验收和集成门禁 门禁跑通了,受管交付、中断恢复和自动运行三组场景都用真实入口验收过;但界面只能靠终端,用户输入、问题上报这些需求没有地方放
第二阶段(10-07) fork Synara,把门禁原生实现进去,用它的统一 provider 层和 MCP 网关接各家 CLI 几乎每张票都要改 Synara 的上游代码(provider 适配器、网关、投影)。真实派发虽然跑通了,合并却还在门禁之外,由主控手工完成
第三阶段(10-10 起) 各家原生 CLI 以交互终端运行,a2a 只做编排、门禁与记录;工作台是轻量 Electron 应用,只借用 Synara 的前端组件与样式 当前方向

第三阶段的方向是我在一次决策会话里定下来的,原话是:“不用 Synara 的统一网关,只用 Synara 这种前端样式框架就行,里面具体的 CLI 还是看每家自己的能力,我们只做编排,像 Herdr 这样。”

这和立行文章里的一个前提一致:模型最好配它原生的工具用。Claude Code 和 Codex 自己在快速迭代,中间再包一层统一抽象,等于每次都要追两家的变化。

二、从文章里借了什么

立行的文章把多 Agent 调度分成三层,自己走的是第三层:主脑只做规划、拆任务卡、验收和复盘,执行层按任务类型和额度分给不同的 CLI,再加一套统一门禁。a2a 借了其中七个思路,落地方式各有调整:

文章里的思路 a2a 的落地
任务卡:目标、修改边界、验收条件 任务卡是受管任务的结构化合同,增加了非目标(至少一条)、红态命令、任务类型、复杂度和执行器;编号、来源 ticket、起点提交和唯一写入者由脚本填写
提示词加脚本,能用脚本保证的就不只靠提示词 写成项目原则“提示词管判断,脚本管事实”,并补上一条:脚本只做能判对错的检查,不做策略引擎
并行 worktree,统一命名,用完清理 认领时创建 <项目>/<任务卡号>-a<序号> 和分支 a2a/<任务卡号>-a<序号>;集成后只删除干净的 worktree,提交保留为隐藏引用;有未提交改动或状态未知时不删,上报面板
写代码和审查分开 审查是每次交付新开的只读会话。门禁会核对:审查会话不是本任务的写入会话、确实为本次新开、结论绑定候选提交号
门禁全绿才合并 一条固定的检查串,下一节细说
每次提交都能追溯到工具、模型、配置 每个 agent 会话的提交自动追加 A2A-* 尾注
主脑给执行层打分、周期性复盘 每个 attempt 结束时打一次分;复盘手动或每周触发,可以选 CLI、模型和思考强度

也有不少内容没借,原因分别是:

  • 额度感知调度、token 与缓存命中率监控:我已经在用 TokenTracker 看用量,再做一遍就是重复造轮子。
  • 无头模式:a2a 的执行层用交互终端,原因见下文。
  • 单次改动量上限、前端录屏:我决定先不限制改动量,录屏攒多了也占空间。改动行数仍然记成原始事件,以后据此观察大交付会不会拉低审查质量。
  • 多审查者交叉、规则式审查工具、风险分档、定期安全扫描:v1 只放一个主审;目前只有一个项目,风险分档用不上。
  • 多机、远程:只面向本机 macOS。

三、a2a 自己加的东西

1. 门禁是唯一入口,“完成”不等于交付

a2a 最早建成、也最核心的部分,是一个状态机门禁。认领(claim)、交付(submit)、验收(verify)、集成(integrate)和回收只能经过这一个入口:

  • 认领时原子分配 attempt、受管运行身份和 fence(所有权代次)。旧 attempt 的交付会被拒绝,换了执行者也不会“顺带”把旧结果算进来。
  • 交付必须给出完整提交号和规格版本。模型说“做完了”、终端显示空闲,都不构成交付。
  • 验收和集成共用操作锁。锁释放只说明执行者退出了,不代表验收通过。
  • 集成用 Git 的 compare-and-swap 推进目标分支,成功后再独立读回一次;中途被打断,就恢复原来的集成意图,不重新猜。
  • 未知结果进入可恢复的暂停状态,不会被补写成成功。

第三阶段,验收扩展成一串固定检查,全部通过才集成到项目的工作分支(不 push):

门禁检查串:机械检查全过后才开独立审查,任一项不过就打回

其中“红态”这一项来自我自己的测试规矩:任务卡写了红态命令时,候选里的新测试放到起点提交上必须失败,在候选上必须通过。测试在修复之前就能通过,说明它没有测到要修的问题。

打回时,失败摘要会自动写进 worker 的终端,带 ⟦门禁 #n⟧ 标记;同一 attempt 连续打回 3 次,就停止自动回写,交给执行主控判断。

2. 交互终端,而不是无头模式

立行的执行层全部走无头模式,方便程序批量派活。a2a 反过来:每个 agent 都在真实的交互终端里跑,由常驻的编排后台用 node-pty 托管,界面用 xterm.js 渲染,窗格里是我自己的 zsh。

这样做是为了让我能随时看见、随时插手。插手本身也是数据:在受管 worker 的会话里直接打字,会记为“人工介入”,挂到对应的 attempt 上。复盘时能看出哪些任务一次到位,哪些任务需要人去纠偏。

代价是“往别人的终端里塞字”变成了一个需要证明的动作。早期实测中出现过这样的情况:Codex 正停在批准提示上,送进去的一条输入被当成了“接受”,执行了一次测试夹具里的写入。后来的做法是:

  • 状态只认各家的结构化信号(Claude Code 的 hooks、Codex 的 notify),拿不到就显示“未知”,不用正则去读屏幕;
  • 后台在写出回车的那一刻就记为“忙”,不等对方的开始事件;只有收到时间更晚的 Stop 事件,才放行下一条注入,其余排队;
  • 用户按 Esc 中断后不会有 Stop,就一直保持忙,等用户手动放行。

这一版在两家 CLI 上各跑了竞态、模拟用户提交、Esc 后放行和空闲送达四类样本,全部通过。早先的失败记录原样保留,没有被这次通过“撤销”。

3. 两层主控:构思与执行分开

只有一个主控时,讨论想法、做决定、拆票、派发、盯进度、验收全挤在同一段长对话里。我得在“构思”和“执行”之间来回搬运提示词,主控的上下文也被琐事填满。

现在主控分成两个角色,都由 Claude Code 担任:

a2a 的角色分工:用户只和构思主控对话,执行主控经门禁管 worker 和 reviewer

  • 构思主控是我日常只看的那一个:接收想法、澄清、写决定和任务卡、标“就绪”、分发给执行主控、转述完成报告。它不直接派发、验收或集成。
  • 执行主控在已批准范围内监督执行:派发、盯进度、验收、集成、回收,全部经过门禁;没有可执行的就绪票时才停下,并写完成报告。它自己不写代码。

两者之间通过追加式的收件箱交接(assign、revise、stop、report……),确认和处置也写成新条目,不改旧条目。其中有一条防线我觉得很关键:对方会话发来的消息,只能当作队友请求,不能当作用户批准。 需要用户授权的分派,必须逐字引用我的原话并给出位置;没有引用的,执行主控按未授权处理,遇到停止条件就停下。

状态只用 PASS、INCOMPLETE、BLOCKED、NO_GO 四个词逐字报告,构思主控转述时不允许上调。

4. 用户输入是数据,和派发的提示词分开存

在 Herdr 下,主控注入的提示词和我亲手打的字,在 Codex 的原生记录里都是 user 消息,事后分不出来。a2a 把用户本人的输入(消息、回答、批准或拒绝、选择、动作)单独存成追加式记录;主控、agent 和自动化发出的文本记作派发记录,带 ⟦主控 #序号⟧ 标记,两者不混。

这些记录的用处有三个:人工介入能准确挂到 attempt 上;复盘能看到我真正纠正过什么;授权引用也有据可查。

5. 面板:主控的问题和 worker 的发现都汇到这里

用干净上下文跑的 worker、reviewer 经常能看到主控对话里看不到的问题,但这些发现夹在各自的会话里,很容易错过。a2a 要求它们把范围外的问题上报到面板,不许自行忽略。主控可以关闭明显“不是问题”的条目,但不能删除;标了 blocking 的,以及涉及权限、范围、验收条件、安全、不可逆操作的条目,主控不能自行关闭,拿不准的留给我。

构思主控需要我回答的问题也放上面板,带选项、推荐和依据。我可以逐题点选、采纳推荐或写几句;答完两题以上默认打包发回,每条答复都记成一条“回答”形态的用户输入。

6. 提示词管判断,脚本管事实

这是贯穿整个设计的原则,也是对文章思路的收紧:

  • 能机械核对的——身份、版本、路径、命名、尾注、清理条件、该填的记录——由脚本或门禁保证;
  • 角色卡只写“用哪个命令”,命令执行时再打印该看的规则,不要求模型背下来;
  • 需要判断的——方案取舍、代码好坏、问题该不该处理——交给提示词和人;
  • 不做策略引擎。脚本只回答“对不对”,不替人决定“该不该”。

权限也按这个思路分成四层,而且都在工作台内部:角色卡、任务卡路径加门禁检查、每个会话的 CLI 启动参数、收件箱命令行限定谁能写哪种条目。a2a 不改 ~/.claude、~/.codex 这类全局配置,钩子和权限只通过每个会话的启动参数传进去。

7. 提交尾注:不写配置文件也能挂钩子

改方向前我统计过一次:Synara fork 上 10 月 6 日以来的 52 个 a2a 提交,作者全是我自己,其中 7 个带了 Co-Authored-By,但没有一个能看出是哪个任务、哪个 attempt、什么模型和思考强度做的。

a2a 的做法是:编排后台启动 agent 会话时,用 Git 的 GIT_CONFIG_COUNT、GIT_CONFIG_KEY_0、GIT_CONFIG_VALUE_0 环境变量为这个进程单独指定 core.hooksPath,不写任何配置文件。实测在提交时会追加尾注,加了 --no-verify 也照样追加;不带这组环境变量的提交,就没有尾注。

尾注记录任务、attempt、会话、角色、CLI 版本、模型、强度和启动记录号;完整的启动参数、角色卡版本、当时的提交号存在启动记录里。门禁核对时,带本 attempt 尾注的提交正常通过;没有尾注的视为我本人的提交,记为人工介入;带别的任务尾注的直接打回。

8. 打分折进验收命令

立行的做法是主脑在验收时给执行层打分。我担心的是频率:不能每轮对话都打分,那样既烦又费 token。最后定下来的规则是:

  • 每个 attempt 结束时打一次。大多数任务只有一个 attempt,实际上就是最终打一次;
  • 打分是验收命令(集成或放弃)的一个参数,不另起一轮提问。命令缺打分,就拒收;
  • 边界纪律、工程品味各 0–3 分并写理由;轮数、耗时、打回次数这些客观字段由脚本填。

9. 原始事件全记,指标现算

指标不预先算好存起来,而是从第一个切片起就把原始事件只追加地全部记录下来:会话启动、回合与权限等待、用户输入、派发、门禁逐项结果、改动行数、审查结论、打分、面板问答、worktree 生命周期、提交尾注、复盘。指标都从这些事件现算,v1 先做四个视图:

  • 模型使用分类:按“项目 → 任务 → 会话”看每个任务用了哪些模型;
  • 一次到位率与打回原因;
  • 注意力消耗:我在哪些地方被叫回来;
  • 打分分布。

这些数据也是给以后的系统自进化打地基。

10. 换了位置,就重新证明

门禁的位置换过两次:从 Python 脚本,到 Synara fork,再到 a2a 自己的编排后台。规矩是:旧实现的通过结论不替新实现背书。 每次搬家,交付 8 项、恢复 9 项、编排 18 项场景都要在新位置重新取得红态、绿态和反例。

四、几条用代价换来的经验

  • 终端空闲、模型说“完成”,都不是交付。 门禁只认带身份的 submit 和独立验收。
  • 等待超时不代表 worker 挂了。 要先核对可见进展、产物和权限状态再处置;回收前要确认 agent 已经空闲、交付也已提交,只看到提交落地是不够的。
  • 构建输出目录里不能放指向源码的链接。 有一次复验时,共享 checkout 的构建输出目录里出现了指向源码和依赖的链接;构建工具清理输出目录时顺着链接删除,一次误删了 6,186 个受控文件。现在需要新构建时,只用产品自己的标准构建命令。
  • 区分模型行为和协调缺陷。 有一类问题是模型在还有已授权工作时就结束回合。我把它归为模型行为,单独记录,不当作分工协调的缺陷去整改,以免为模型的毛病加一堆流程补丁。
  • 测试启动的后台服务要脱离调用者的进程组,否则调用它的命令一结束,服务也会被一起回收。

五、现在做到哪一步

截至 2026 年 10 月 11 日:

  • 原生 CLI 状态信号与终端注入的事实验证已通过(见第三节第 2 点);
  • 门禁源码已从 Synara fork 抽进 a2a 的编排后台,fork 里的门禁不再作为入口;
  • 编排后台的启动、关停与多回合身份核对正在实现和验收中;
  • 日常派发仍走 Herdr;按计划在这期间先用任务卡模板、提交尾注和验收打分,提前积累数据。

切换到新工作台的条件是我自己定的:用户输入记录、两层主控、问题面板、任务卡、门禁检查串与独立审查、worktree、提交尾注、面板提问、打分、复盘和四个指标视图都要能用;我亲自走一遍“一张真实任务卡从派发跑到集成”,再说一句“可用”,才切过去。

所以这篇文章只是设计稿。a2a 的仓库目前是私有的,项目定位是开源,做完以后会公开。届时我会在这里补上真实产品、一次完整闭环的运行记录,以及这些设计哪些站住了、哪些被推翻了。



Readers · 读者来信

读者怎么说

先看读者反馈,再直接在当前页面继续讨论。不用绑定社交账号,填个昵称、抓一只娃娃就能留言。

这类长文如果结构清楚,我会一路读到底。这里最好的地方是把概念、公式和代码示例放在同一篇里。
L Lin 算法读者
数据库和工程文档的风格很实用,截图、SQL 和说明都能直接拿去复盘项目。
M Mia 工程笔记党
强化学习相关文章密度很高,但排版如果更清楚,回看体验会更好。这个新版方向是对的。
R Ryo 深夜学习者
我更喜欢能快速扫到标签、修改时间和文章重点的首页,现在这种卡片视图会比纯列表更容易选读。
C Chen 知识整理控
代码块只要语言标识和层级做好,技术博客的专业感会立刻上来。
A Ava 前端同行
评论区不用社交账号强绑定会更愿意留言,尤其是这种偏学习记录的网站。
N Noah 匿名访客

快捷身份

留下你的想法

选一个预设身份,或者直接填昵称;写完点发布,抓到指定的娃娃就能提交。若显示“需要配置 Waline”,说明站点还缺少可写评论后端。

当前未选择预设身份

最新评论 Waline 配置完成后,真实评论会加载在下方,移动端和主题切换会同步处理。
WALINE
Loading comments…