文章提出 Sentinel 多Agent开发模式:编排者不写产品代码,专业Agent按需轮换,所有完成声明必须由实际产物验证。它针对上下文压缩造成的遗漏和错误事实传播,建立可审计的任务账本与验证关卡。
一个编码 AI 智能体完成了自己的工作通道,写了一份整洁的报告,并告诉我,它已经实现了我要求的九项内容。我在账本里记下了九,然后继续推进。后来,在做一件无关的事情时,我亲自枚举了这些产物,数出来只有七项。
之后并没有发生什么惊天动地的事。没有人发布带有缺陷的代码;我及时发现了问题。但当时,这个数字已经写进了循环中其他环节所读取的文件,这意味着后续任务已经引用了它,也意味着一个错误事实获得了出处。这是我反复思考的一类故障,因为它不是模型故障,也无法通过改进提示词来修复。AI 智能体根据自身经过压缩的工作上下文生成报告,而压缩恰恰会丢失那些你希望审计的细节。它并没有撒谎。它只是在做总结,而总结会把数字取整。
这篇文章介绍了我实际用来开发 vectr 的流程。vectr 是一款语义代码搜索和工作记忆工具,而我只有一台 MacBook 和一个订阅。整个流程由一个不编写任何产品代码的编排 AI 智能体、轮流上场且用完即弃的专家 AI 智能体,以及位于中间的一道关卡组成;在有产物提供佐证之前,这道关卡会把每一项声明都视为道听途说。先介绍角色,因为角色是简单的部分。然后介绍规则,规则才是困难的部分,因为每一条规则背后都发生过一次事故。
先从约束说起,因为正是约束让这件事变得有趣。我只有一台机器、一个 Claude 订阅,以及一份长到超出我注意力承受范围的任务列表。使用单个 AI 智能体时,我的吞吐量受限于自己阅读其工作成果的速度。这个限制真实存在,而且很低。仔细阅读 diff 比生成 diff 更慢;自从模型能力变强以来,情况一直如此。
于是你会选择并行化。五个 AI 智能体、五项任务、五个分支。我把其中每一个称为一条工作通道:一个 AI 智能体、一项范围明确的任务、一个分支,从头到尾无人监督地运行。“工作通道”这个词在本文中会承担很多含义。这个方案的第一个版本一直运行得非常漂亮,直到你尝试合并。此时,你会发现一件从来没人画进架构图的事情:并行化会以完全相同的速度增加产出和未经验证的声明。五条工作通道会产生五份 diff 和五份报告。diff 是真实的。报告则是由编写这些 diff 的同一个过程,根据途中已经被压缩过一两次的上下文,对 diff 所做的总结。
diff:两个代码版本之间逐行显示的变更集合。它代表 AI 智能体实际做了什么,而不是它声称自己做了什么。
“模型产生了幻觉”在这里是错误的诊断,它会让你跑去调整提示词,而真正的问题是结构性的。一个执行长期任务的子 AI 智能体填满了上下文窗口,随后上下文被压缩;它继续工作,又被压缩一次。等它写下“我实现了九项内容”时,这个“九”并不是计数结果,而是对某次计数的回忆,是从“总结的总结”中重构出来的。让一个人连续工作八小时后仅凭记忆撰写状态报告,你也会得到同一类错误;这就是我们发明提交日志的原因。
子 AI 智能体:由另一个 AI 智能体启动、负责完成一项范围明确的任务的下级 AI 智能体,拥有自己独立的对话和上下文窗口。
上下文窗口:模型一次能够关注的固定数量的 token。AI 智能体当下知道的一切都存在其中;任何从中掉出去的信息都会丢失,除非此前已被记录下来。
压缩:对过长的对话进行总结,以腾出上下文空间。总结会保留大意,却会丢失精确的数量、签名和行号。
AI 智能体很便宜,验证却不便宜。如果你的流程让工作量和验证量同步扩展,那你其实什么都没有并行化;你只是把队列从生成环节挪到了审查环节,同时让队列变得更长。设置关卡的全部意义,就是让验证成本低于生成成本,从而使二者能够以不同的速度扩展。
最终形成的模式包含一个长期存活的 AI 智能体和许多短期存活的 AI 智能体。我把长期存活的那个称为“哨兵”。它不编写任何产品代码。它的全部职责是界定工作范围、启动专家 AI 智能体、攻击它们的产出,以及决定哪些内容可以合并。本文接下来的一切,都是这种职责拆分所带来的结果;同样也是因为我曾多次拆错,最终才找到有效的方式。
这个循环中会运行三类 AI 智能体。区分它们的并不是能力,因为它们都是同一模型家族的实例。真正的区别在于:对于每一类 AI 智能体提供的信息,我愿意在不检查的情况下相信多少。
它编写任务简报、启动工作通道、审计结果、维护账本并执行合并。它由两条规则定义,而这两条规则都是不可或缺的。
第一,它不编写任何产品代码。实际原因是上下文:审计四条工作通道是整个系统中最消耗上下文的工作,而一个同时深度参与重构的编排者,早已把这些上下文消耗掉了。结构性原因则是:如果审查者自己的 diff 也包含在待合并内容中,它就会希望合并成功。我不认为语言模型会像人一样把这种情况体验为利益冲突,但我宁愿不要在关卡上验证这一点。
第二,任何需要付费或不可逆的操作,都由哨兵执行。包括推送、打标签、发布,以及会消耗共享配额窗口的评估运行。不是因为专家 AI 智能体会鲁莽地执行这些操作,而是因为这些操作无法撤销,我希望它们只能从唯一一个地方发起。工作通道可以提议发布,但不能实际执行发布。
每个 AI 智能体只负责一项范围明确的任务,各自使用一个 git worktree 分支;在提交报告前必须确保完整测试套件全部通过,并按照我在简报中指定的结构返回报告。工作通道结束时,AI 智能体也随之结束。不存在一个跨任务积累知识的长期编码 AI 智能体,这是有意为之:积累下来的上下文是一种未经审计的状态,而未经审计的状态正是整个流程试图尽量减少的东西。
git worktree:从同一个代码仓库检出的第二个工作目录,位于自己的分支上,与其他工作目录共享同一个对象存储。两个 worktree 无法相互覆盖对方的文件。
对于用完即弃的编码 AI 智能体,一个显而易见的反对意见是:每个新 AI 智能体都必须从头重新了解代码库,并为此付出完整的 token 成本。这个反对意见是正确的,而且成本真实存在。这是整个循环中效率最低的地方。唯一让它尚可接受的原因,是工作发现会被记录到后续工作通道可以检索的位置,而不必重新发现一遍(第 11 节)。如果没有这一点,使用用完即弃的 AI 智能体将完全无法辩护。
编码 AI 智能体会收到严格限定的简报:任务、允许修改的文件、绝对不能越过的边界,以及报告必须采用的确切格式。如果给中等能力的模型一份含糊的简报,它往往会漂亮地解决一个相邻的问题。
报告格式中有两个细节,对我的帮助超过了简报中其他所有内容的总和。要求粘贴原始命令输出,而不是让工作通道转述命令输出了什么,因为数字取整正是在转述中发生的。还要让它提供报告所描述的提交 SHA。如果没有固定的 SHA,审计就会与工作通道形成竞态:在我阅读期间,它可能又推送了一次提交,于是我验证的是一个已经不复存在的状态。
TASK one sentence, one outcome, no "and also"
WHERE worktree wt-<name>, branch feat-<name>
TOUCH src/searcher/*.py, tests/test_searcher.py
RAILS do not touch the index schema; no destructive git;
no new dependencies; no changes outside TOUCH
DONE full suite green in your worktree
REPORT 1. commit SHA
2. `git diff --name-only main...HEAD`, output pasted verbatim
3. suite summary line, pasted verbatim (passed/failed/skipped/collected)
4. anything you tried that did not work, one line each
5. write your key findings to shared memory before you finish
它可以完整显示在一个屏幕上,编写只需十分钟,其中大部分时间都花在 TOUCH 和 RAILS 两行上。正是这两行阻止工作通道“好心地”重构三个模块之外的某些内容;这类故障往往会把一次干净利落的合并变成耗费整个下午的工作。
它们是独立的 AI 智能体,唯一职责是在各自独立的维度上攻击工作成果。在我的循环中,这些维度包括:检索是否真的返回了正确内容;目标 AI 智能体是否真的采用了为它构建的工具,还是将其忽略;以及它在交互轮次、token 和实际耗时方面,是否优于不使用工具的基线。三个不同的问题由三个不同的 AI 智能体负责。我刻意没有让同一个审查者同时考虑这三个方面,因为当一个审查者肩负三项任务时,它会在最容易检查的维度上找到最容易发现的故障,然后就此停止。
让评审真正发挥作用的指令,是向他们提供分支和变更说明,而不是 diff。拿到 diff 的评审只会审查 diff;拿到一个可运行的构建,并被告知尽管去破坏它的评审,则会真正使用产品、探索 diff 之外的区域,并找到这个 diff 在相隔两个模块的地方破坏的功能。真正的缺陷正是从这里被发现的。
评审有两道护栏。第一,评审轮次必须设上限,因为拥有无限轮次的对抗者永远能再找到一个吹毛求疵的问题,导致产品永远无法发布。第二,评审发现的问题必须经过独立验证,才能阻断任何流程,因为评审虚构缺陷的概率,大致与编码人员虚构完成情况的概率相当。一个虚构的缺陷如果阻止了合并,就会导致整个循环额外重跑一遍,而这是代价最高的一类错误。
大多数多智能体架构图看起来都像装配线:工作向右流动,每个工位增加一些东西,最后一个工位负责发布。但真正运转的厨房更贴切。厨师们在不同工位上并行工作,所有菜品都会经过出餐口的总厨:他不亲自做菜,却品尝每一道菜,并把不合格的菜退回去。总厨并不是流水线中的一个阶段;恰恰是因为有他,整条流水线才得以并行运转,因为只有他能够决定什么叫“完成”。厨房忙到不堪重负时,积压会发生在出餐口,而这恰恰说明设计正在发挥作用,并非设计失效。
每一项任务、每一个决策、每一次合并以及每一个关卡结论,都写进同一个文件中,只追加,绝不重写。对于个人项目来说,这听起来很官僚,但它是整个循环中最有价值的产物。
原因在于,对话无法长久保存。聊天窗口会被压缩,一旦发生这种情况,第四十条消息中的精确数字就会被一段转述取代。会话会结束,机器会休眠,AI 智能体会在话说到一半时消失。账本是系统中唯一不会衰减的记忆,因此也只有它有资格作为已经发生之事的权威记录。
只追加的重要性超乎想象。可修改的状态文件允许人们悄悄纠正错误记录。这听起来像一项功能,直到你意识到:一个被悄悄修正的错误什么也教不会你。当数量九最终被证实是七时,修正内容被写成了原始说法下方的一条新记录,旁边同时保留了最初的说法以及解决争议的枚举结果。正是这条记录促成了这项规则。一个掩盖错误的文件只会产生一次微小而整洁的编辑,却不会催生任何规则。
[2026-07-14] TASK retrieval-rerank-blend
brief: blend owning-class prior into rerank; touch searcher + index only
lane: coder / worktree wt-rerank / branch feat-rerank-blend
report: "8 call sites updated, suite green (412 passed)"
AUDIT grep -c over the branch diff: 8 call sites [matches]
AUDIT suite re-run on MAIN checkout: 412 passed, 0 skipped [matches]
verdict: MERGE c1f9a2e
[2026-07-14] CORRECTION to entry [2026-07-11] docs-sweep
ledger recorded 9 updated files on the lane's report
enumeration of the branch diff: 7 files
root cause: report written post-compaction; count was recalled, not counted
rule added: no merge on a lane's claim, enumerate the artifacts
这条记录完成了两件事。首先,它记录的是证据,而不只是结论,因此后来的读者可以判断这次合并究竟是理由充分,还是仅仅运气好。其次,它把审计变成了一个书面步骤,而不再只是我打算去做的事情。如果缺少 AUDIT 行,就说明合并并未发生,因为事后我无法判断自己是否真的检查过。
账本还兼作恢复产物。如果我彻底丢失了某次会话,通过账本加上 git branch --list,大约一分钟就能重建整个工作状态:哪些任务已经分配,哪些已经合并,哪些仍在某个分支上处于开放状态。这个特性不止一次救过我,而且值得有意识地为它进行设计,而不是事后才偶然发现它的价值。
报告是传闻,产物是证据,而关卡只接受证据。下面的一切,就是在实践中遵守这条原则所需付出的代价。
工作通道报告中的每一项声称,都必须能够转换成某种我可以在一分钟内亲自生成的东西。“我更新了九个文件”会转化为分支 diff 给出的文件列表,然后由我亲自计数。“测试套件通过了”会转化为由我在主检出目录上运行测试套件,并读取包含跳过数量在内的汇总行。“我添加了回归测试”会转化为按名称找到该测试、阅读其测试体,并检查如果没有这个修复,它是否真的会失败。如果某项声称无法转化为枚举,我就会重写任务说明,确保下一次能够做到,因为无法检查的声称,迟早会以某种无人察觉的方式出错。
这并不是对模型疑神疑鬼,而是成本不对称。枚举一项声称需要一两分钟。账本中一项错误的声称,其代价则包括发现错误所需的全部时间、在此期间构建于它之上的一切,以及判断哪些下游结论如今也值得怀疑所需的时间。当权衡的一边只需要几分钟,而另一边可能耗费数小时且影响长期延续时,你无须对失败概率抱有多么强烈的先验判断,也能知道应该偏向哪一边。
错误计数之所以比看上去更糟,是因为账本会赋予它权威性。一旦数字九被写下来,下一个工作通道就会从账本中读到九,并在自己的报告中重复它,于是这个错误便有了引用来源。它不再只是某个 AI 智能体的总结,而是变成了项目中一项拥有书面记录的事实。经由可信记录洗白的错误极难被发现,恰恰是因为所有下游内容都与它保持一致。唯一的防御方式,就是确保记录本身基于产物建立。
有一个细节值得明确,因为它会改变你编写任务说明的方式。枚举工作应该由哨兵执行,而不是由工作通道执行后再上报。要求编码人员“验证并确认数量”得到的是一次确认,而这依然只是来自同一来源的另一项声称。验证必须跨越 AI 智能体边界,否则就不算验证。这与我们不允许人们批改自己的试卷是同一个道理,而且这一理念远比这里提到的任何工具都更加古老。
在关卡处,真正有用的能力是发现一句话声称了什么与它实际证明了什么之间的差距。下面是三类声称,以及每一类所需的审计方式。
**数量。**报告称:“在文档清理中更新了 9 个文件。工作通道已完成。”它没有证明的内容:关于这个数字的一切。报告是在该工作通道经历两次上下文压缩后写成的,因此 9 只是对某次计数的回忆。无论真实答案是 9、7 还是 12,这句话都同样流畅。枚举方式:询问 git,而不是询问 AI 智能体。git diff --name-only main...feat-branch 会每行输出一个路径;你要亲自数这些行。返回的结果是 7 个路径。所谓九个文件中的两个,只是该工作通道打开、考虑过,却没有修改的文件。这两个文件都出现在它的叙述中,却都没有出现在 diff 里。两者之间的差额,就是两个只被读取而未被写入的文件,而总结恰恰会抹平这种区别。
**绿色测试套件。**报告称:“完整测试套件通过。”它没有证明的内容:运行的是哪一套测试、在哪个检出目录运行,以及跳过了多少测试。在点号目录下的 worktree 中运行测试,可能会悄悄跳过路径遍历测试,却仍然打印出绿色的结果行。“通过”隐藏了分母。枚举方式:亲自在主检出目录上运行测试,并逐字读取汇总行:通过、失败、跳过、收集的测试数量。将收集总数与最近一次已知正常的运行结果进行比较。只有当收集数量不低于上一次运行、跳过数量为零或每一项跳过都有单独说明,而且测试运行在代码最终实际所在的位置时,这项声称才算得到确认。测试报告中真正有意思的数字从来不是失败数,而是收集到的测试总数,因为一个比昨天悄悄少收集了测试的测试套件,会出于错误的原因保持绿色,并且会一路保持绿色直到生产环境。
**回归测试。**报告称:“为修复添加了回归测试。”它没有证明的内容:该测试是否真正触发了这个缺陷。一个测试可能确实存在、名称正确并且能够通过,却断言了与真实缺陷相邻的其他内容。随后它会永远通过,包括在它本应捕获的错误代码上也照样通过。枚举方式:按名称找到该测试,阅读其测试体,然后进行反向验证:还原修复,并确认测试会失败。一个不可能失败的回归测试,只不过是带着绿色对勾的文档。这是我审计得最严格的一类声称,因为虚假的回归测试不只是未能捕获一次缺陷。它还证明该缺陷不可能再次出现,而下游的一切都会信任这份证明。
数量案例是这个项目中真实发生的事件。另外两个案例,是将同一套流程应用于我最常审计的另外两类声称;它们展示的是方法和证明标准,而不是已经记录的结果。
现在来说说真正决定你究竟应该运行多少个 AI 智能体的部分——这是一个我从未见过有人用具体数字回答过的问题。
各条工作通道并发运行,审计则不会。只有一个关卡,由我和哨兵共同组成,并且一次只处理一条工作通道的证据。仅凭这一事实,就足以推导出整个方法的上限。
先看数字,再看代数。假设一条工作通道需要 40 分钟,对其进行审计需要 8 分钟。如果串行完成,四项任务需要 4 × 48 = 192 分钟。让四条工作通道同时运行,它们都会在第 40 分钟完成,随后四次审计依次进行,最后一个结论会在第 72 分钟得出。这样只快了 2.67 倍,而不是 4 倍,差距完全来自审计队列。
推广一下:设 L 为工作通道的执行时长,A 为审计时长,N 为并发工作通道的数量。
串行关卡下的加速比
对于一批同时启动的 N 条相同工作通道:
T_serial = N (L + A)
T_parallel = L + N A
S(N) = N (L + A) / (L + N A)
随着 N 增大,分母中的 N A 项占据主导,因此加速比会收敛:
lim S(N) as N → ∞ = (L + A) / A = L/A + 1
你可有效利用的最大 AI 智能体规模,只取决于工作通道执行时长与审计时长之比,除此之外别无其他因素。工作通道耗时 40 分钟、审计耗时 8 分钟时,上限就是 6 倍。不是“工具配置正确后可以达到 6 倍”,而是无论如何都只有 6 倍。第十条工作通道会消耗十条工作通道的 token,却只能换来一个四舍五入级别的微小提升。
这个公式描述的是单个批次。但项目不是一个批次,而是持续不断的任务流,因此值得单独推导流式版本,因为它呈现出的形态有所不同。在稳定状态下,工作通道以每分钟 N/(L+A) 个任务的速率交付工作,而关卡以每分钟 1/A 个任务的速率消费这些工作。吞吐量由二者中较小的那个决定,因此相对于逐个处理任务的方式,加速比为:
流式版本与拐点
S_stream(N) = min( N , (L + A) / A ) = min( N , L/A + 1 )
在关卡饱和之前完全线性增长,此后则永远保持不变。它与批处理公式的上限相同,但这里不是平缓的曲线,而是一个明显的拐角。拐点位于 N* = L/A + 1。对于耗时 40 分钟的工作通道和耗时 8 分钟的审计,拐点就是 6。五条工作通道能带来 5 倍加速,六条能带来 6 倍加速,七条仍然只有 6 倍,七十条也是如此。
这其实就是换了一身外衣的阿姆达尔定律。用这个名字描述它,也就说明了优化杠杆在哪里。这个系统中的串行部分是审计,因此价值最高的做法并不是增加工作通道,而是缩短 A。实现这一点的关键,是把声明设计成可以通过命令进行检查,而不是必须靠人工阅读来验证。
阿姆达尔定律:并行系统的加速比受限于必须保持串行的工作比例。在这里,串行部分就是审计。
这种重新理解改变了我编写任务简报的方式。“报告你修改了什么”会得到一段说明性文字,而我还要花四分钟把这些文字转换成可检查的内容。“报告所有变更路径,并逐字提供测试套件的摘要行”则会得到可以在三十秒内与实际情况进行比对的内容。同一条工作通道、同一个模型、同一项工作,仅仅因为审计成本降低了,我的上限就大致翻了一倍。结构化报告读起来像文书工作,实际发挥的作用却是提升吞吐量。
公式遗漏的两件事
返工。工作通道可能无法通过审计,而失败的工作通道会被重新派出,之后第二次通过同一个串行关卡。如果有比例为 f 的工作通道需要进行第二轮处理,那么每个成功交付的任务都需要通过关卡 1+f 次,因此饱和状态下的吞吐量会从每分钟 1/A 个已交付任务,降至每分钟 1/(A(1+f)) 个已交付任务。不过,要谨慎看待这对比率的影响:逐个处理任务的基准模式通常也具有大致相同的返工率,因此 f 在加速比中基本会相互抵消,上限仍然接近 L/A + 1。无法抵消的是绝对吞吐量、关卡耗时以及账单。当 f = 0.3 时,十条工作通道中有三条没有产出任何可以交付的成果,却依然各自消耗了一次完整的审计。这是我所知道的、支持花二十分钟编写任务简报的最有力论据,因为相比模型质量,f 与任务简报质量的相关性要高得多。
哨兵的上下文窗口。还有第二个与时间完全无关的上限。一条工作通道的证据会消耗一定数量的 token,将其记为 E,而哨兵的窗口大小是有限的,记为 W。一旦累积的证据接近 W,哨兵就会压缩上下文;而一个经过上下文压缩的关卡,已经开始对它本应检查的那些细节进行概括。因此,真正的上限是 min(L/A + 1, W/E)。在审计任务繁重的漫长工作日中,往往是第二项先构成限制。这就是哨兵不编写代码的原因:它在其他任何事情上花费的每一个 token,都会减少一条它原本能够正确审计的工作通道。
还有最后一种被算术掩盖的影响。如果所有工作通道同时启动,它们就会同时完成。随后关卡前会形成队列,而每条工作通道都会在等待结论期间处于空闲状态。关卡利用率达到 100%,工作通道利用率却会急剧下降。错开启动时间,或者有意混合不同规模的工作通道,可以在不改变整个批次总工期的情况下,让任务到达关卡的节奏更加平滑。在实践中,我通常会将一条长工作通道与两三条短工作通道搭配,而不是同时启动四条完全相同的工作通道。
你的 AI 智能体规模从哪里开始不再划算
网站上的交互版本是一个滑块:设置你自己的 L 和 A,然后观察加速比柱状图逐渐趋于平坦。你应该从中看出的是,当工作通道数量超过某个值后,你会花费更多 token,却几乎得不到任何实际耗时上的收益;而这个拐点的位置取决于审计有多昂贵,并不取决于你的机器能够运行多少个 AI 智能体。该模型假设各条工作通道相互独立、同时启动并且永不失败,因此应将其视为一种乐观估计。
让两个 AI 智能体编辑同一个检出目录,就是一台制造更新丢失的机器。一个智能体写入某个文件,另一个智能体根据过时的读取结果写入同一个文件,于是其中一条工作通道的一半成果就这样消失了,而且整个过程中不会出现任何错误。更糟糕的是,它们还会暂存彼此尚未完成的修改,导致工作通道 A 的一次提交中包含来自工作通道 B 的三行错误代码,而测试失败看起来却像是 A 导致的。
解决方案是标准做法,而且确实有效:每条工作通道使用一个 git worktree,并对应一个独立分支。各条工作通道拥有自己的工作目录和索引,同时共享同一个对象存储,因此文件系统不会成为 AI 智能体之间共享的可变变量。与此同时还要设置两条约束。工作通道绝不执行破坏性的 git 操作,因为即使各个检出目录彼此独立,对象存储、引用和钩子仍然全部共享。