AI agent 自评工作质量会导致虚假完成感。 文章提出四个验证层级(自检→工具验证→独立 Agent→外部系统),并给出 checker-agent 模板和三档运行上限。
TL;DR — 循环的可靠性取决于它的停止条件。"完成"必须能由另一个模型来检验——测试通过、lint 干净、schema 验证通过——而不是 worker 自己的声明。下面会讲:为什么自评会失败、验证的四层阶梯、一个可复制的 checker-agent 模板,以及防止循环永不停的三道上限。
Loop Engineering 系列第 3 篇,发表于 ShipWithAI。阅读全文 →
第 2 篇给了你零件清单。第 1 篇承诺本期将完全围绕停止条件设计展开,"因为这是最难做对的部分。"本期来了。
核心论点,一句话:
一个给自己的工作打分的循环,会在真正完成之前就告诉你它完成了。
为什么自评会失败
停止条件是对现实的一个声明。如果写代码的 agent 是唯一一个断言此声明的主体,你拥有的不是验证——而是一份自述。
作者的观点:制作者给自己的输出评分在结构上就是乐观的,所以它会提前宣告胜利。这有一个名字——Anthropic 自己在构建高效 agent 的文章中明确列出了这种失败模式:过早胜利和虚假完成功能。
四层阶梯是一个递进,递进的是"谁来检查":
第 1 层用退出码检查,第 3 层用第二个模型检查,而它们之间的差距恰恰就是制作者的乐观。
值得了解第 2 层和第 3 层的机制差异:Stop hook 以退出码 2 阻塞完成,直到检查通过,Claude Code 在连续 8 次阻塞后会自动覆盖。/goal(v2.1.139+)则有一个独立的模型——Haiku——在每一轮对停止条件进行评分。
把愿望改写为条件
"运行直到功能完成"不是停止条件,是愿望。停止条件是一个带退出码的命令。
检查你写的任何条件的三个属性测试:
可验证——某物能确认它(一个命令、一个计数、一个第二个模型)
可证伪——它可以失败,并带有清晰的失败信号
低成本——你可以在每一轮都评估它
最后一个是人们低估的——这里的"低成本"意思是字面上你可以在每一轮都运行它,而不是说"有它是很好的"。
把这个复制过去。整个设计精髓在于它被禁止做的事:
---
name: stop-condition-checker
description: Grades whether the loop's stop condition holds. Not the maker.
model: haiku
---
You verify, you do not fix. Run the project's check command and report only:
- PASS if `bash tests/run.sh` exits 0 AND `bash scripts/lint.sh` exits 0
- FAIL otherwise, with the first failing line
Never edit code. Never report PASS on the maker's say-so; run the command.
checker 的价值完全来自于它不是 maker。不同的指令、更便宜的模型、以及每一轮干净的环境,这些才是让它的 PASS 有意义的原因。
文章给出的实际处方:一个带有不同指令的 checker,可选地使用不同的模型,最好是每一轮都有干净的环境。不同的模型是选项,不是必选项——必选项是"不是 maker"。把 maker 和 checker 分离开来,这被称为"循环设计中单次最高杠杆的操作"。
一个值得了解的真实权衡:官方的 ralph-wiggum 插件在和 maker 同一个会话中运行 checker,社区已经标记了这与 fresh-context Ralph 的偏离——后者每一圈都从干净状态开始。文章没有选边——同会话更便宜;fresh-context 更难被欺骗。自愿选择。
先读免责声明,因为那是最有用的部分。这次运行没有使用第 3 层。/goal 是最自然的选择,但它是一个交互式的会话内评分器,而作者想要一次被捕获的、可复制的运行——所以他用了第 1 层的裸循环等价物:每一轮用一个 fresh-context 的 claude -p 作为 maker,用 bash 测试退出码作为 checker。一个诚实的 maker/checker 分离,由机器可检验的 artifact 评分而非由 Haiku。他还指出 token 和美元成本没有被计量,所以他不会引用一个他没有的数字。
那个 bug,在 ShipWithAI 的 content-agent 仓库中:scripts/check-draft-seo.sh 将每个关键词单词仅与标题的前三个单词比较——所以任何超过三个单词的关键词都会被标记,即使它逐字出现在标题开头。
⚠ keyword_placement: keyword "how to build an agent loop": not in title first 3 words
FAIL: multi-word keyword that leads the title was wrongly flagged.
exit=1
停止条件——注意它防护的是附带损害,而不仅仅是目标 bug:
bash tests/test-check-draft-seo-multiword-keyword.sh exits 0
AND bash scripts/test-rubric.sh still exits 0
修复,一行真正的逻辑:
all_title_words = re.findall(r"[A-Za-z...]+", title.lower())
kw_words = re.findall(r"[A-Za-z...]+", kw_lower)
title_words = all_title_words[:max(3, len(kw_words))]
结果:一轮 fresh-context maker。在第 1 轮通过,从未达到 8 的上限。他还运行了一个对照草稿来证明修复不仅仅是一个现在总能通过的检查——值得作为一个习惯来复制。
循环停止是因为一个命令退出了 0,而不是因为 agent 觉得完成了。这是唯一一种你可以放心让循环独立运行的"完成"。
停止条件说的是成功后何时停止。上限说的是无论成功与否何时停止。一个可信的循环两者都有。
最大迭代次数——claude -p --max-turns N,加上 Stop hook 的 8 次阻塞自动覆盖
无进展检测——continuous-claude 的 --stall-threshold
预算上限——--max-cost 和 --max-duration
有据可查的先例:ShipWithAI 自己的 content-agent 评审循环上限是 3。上面动手实验的上限是 8。按工作选择,不要按规则选择。
一个可用的测试框架和一个你已经可以运行的最小循环
Claude Code v2.1.139+ 用于 /goal
一个在失败时以非零退出的测试命令的仓库
拿你在第 2 篇之后构建的循环,问自己一个问题:谁来断言它完成了?如果答案是"做这个工作的同一个 agent",那你还没达到第 1 层。
把条件写成一个 shell 命令。加第二个 agent,它唯一的工作就是运行它。然后选择你的三个上限。
这是浓缩摘要。完整文章走过了每一层的工作配置、完整的动手追踪,以及关于 /goal vs Stop hooks 的 FAQ:
👉 Stop Conditions: Making "Done" Mean Something — Part 3, ShipWithAI
前文:第 1 篇 — Why You Should Stop Prompting · 第 2 篇 — Anatomy of a Loop。第 4 篇 — Memory Outside the Context Window — 下期发布。
进一步行动,你可以考虑屏蔽这个人或报告滥用