← 返回 互连齿轮和管道组成闭合循环的蓝图线条图,带有压力表和出口阀。

Ralph 循环在 Claude Code 中:验证器、预算和停止条件

Ralph 循环有三个要素:任务、验证器和预算。就这么简单。Geoffrey Huntley 以《辛普森一家》中那个不管有没有效果都坚持向前的角色命名了它,这个比喻很恰当。风险不在于 Claude 停止过早。风险在于它在破损状态下宣布成功,消耗掉你的 token 预算,或两者都发生。这篇文章讲述如何精确定义这三个要素、为你的情况选择正确的实现方式,以及在循环真正完成时安全地停止它。智能体开发工作流是一个独立的问题,这篇文章严格限于迭代直到条件成立。

定义目标、验证器和预算

在写任何命令之前,先写三句话。"完成"是什么样子?你将如何以机制方式检查它?你愿意为多少次迭代付费?

目标必须是可机器检查的。"代码是好的"不是目标。"npm test 中的所有测试都通过且 git status 是干净的"才是目标。这个区别很重要,因为验证器只能评估它能观察到的东西。如果你的完成条件依赖于人类查看某个东西,你就没有验证器;你有一个审查步骤,这两样东西不应该在同一个循环内。

验证器不是 Claude 自己的意见。这是大多数人做错的地方。正如一位 Hacker News 评论者所说,"如果 AI 认为事情有效,它会说 COMPLETE,即使你认为它没有完成。"模型生成的 DONE 是一个信号,不是验证器。真正的验证器是一个外部过程:你的测试运行器、你的 linter、一个 curl 请求到实时端点返回预期状态码。它在每次迭代后运行并产生确定的真/假结果。先构建那个。

预算是你的安全阀。选择一个你实际愿意消耗的数字。frankbria/ralph-claude-code 社区实现要求完成指示器(两个或更多)和模型 RALPH_STATUS 块中的显式 EXIT_SIGNAL: true 才能退出。这种双条件设计之所以存在,正是因为单一信号不可靠。你的 --max-iterations 在第一次运行时应该是保守的。一旦你看到循环实际能进行多远,你就可以提高它。

坦白说,运行任何东西前的设置应该回答:

  • 哪条 shell 命令只在任务真正完成时返回退出码 0?
  • 我允许的最大迭代次数是多少?
  • 哪个文件或日志将记录迭代结果,以便我之后可以检查?

选择循环实现

Claude Code 2.1 附带三个内置原语,涵盖大多数 Ralph 用例而无需任何插件。Awesome Claude 指南记录了所有三个:

分叉管道的蓝图线条图,一条路径开放,另一条带有截止阀,代表循环实现选择。
  1. /goal,在多个回合中保持工作,直到验证条件。使用一个单独的较小模型(默认为 Haiku,根据 Ranjan Kumar 的文章)在每个回合后读取会话记录并回答一个问题:目标是否已达到?如果否,Claude 进行另一个回合。如果是,循环清空。
  2. /loop,在固定或自适应间隔重新运行提示词。按 Esc 停止。适用于轮询任务。
  3. /batch,将一个大型变更分散到 5 到 30 个并行工作树代理。完全不同的方式;不适合单个有界任务。

然后是插件路径和原始 bash 循环。它们在实际影响你的工作方式上有所不同。

插件(ralph-wiggum@claude-plugins-official 或社区分支)在单个会话内运行,使用停止钩子。当 Claude 试图退出时,钩子会拦截它并将提示词反馈回来。上下文在迭代之间累积。这很方便,但这意味着到第 15 次迭代时,上下文窗口会包含每次先前尝试的残留,这可能会降低每次新轮次的质量。

原始 bash 方法,在 while 循环内将 PROMPT.md 管道传入 claude -p,每次都会生成一个全新的进程。正如 Steve Kinney 所指出的,"每次 claude -p 调用都获得一个完全干净的上下文窗口。这正是该技术的全部要点,通过故意重新开始来避免上下文衰退。"权衡是:你会失去对已尝试内容的隐性记忆,所以你的 PROMPT.md 和任务状态文件必须在迭代之间显式地承载所有上下文。

你应该选择哪一个?如果你的任务很短(预期少于 10 次迭代)且上下文保持可管理,/goal 加上轮次上限是摩擦力最小的选项。如果任务很长或上下文质量有问题,新鲜上下文 bash 循环更可靠。tests and AI coding tools 文章介绍了如何结构化你的测试套件,使验证器能够干净地运行两种方式。

使用有界迭代运行单个任务

以下是使用 /goal 原语的有界任务的说明性设置:

/goal npm test 中的所有测试都通过且 git status 干净,或在 20 轮后停止

这一行给了 Claude 一个可验证的退出条件和硬上限。Haiku 评估器在每轮后读取记录并检查两个条件。or stop after 20 turns 子句是你的预算护栏。

对于 bash 循环方法,Geocodio 团队的结构是一个很好的模型。他们使用一个 JSON 文件(简单的 prd.json),其中每个任务都有一个 "passes": false 字段。每次迭代找到最高优先级且 passes: false 的故事,实现它,运行验证器,成功时将标志翻转为 true。当每个故事都有 passes: true 时,while 循环退出。他们的文章值得一读,仅从验收标准结构这一点来说。

新鲜上下文 bash 循环的编号流程如下:

  1. 编写 PROMPT.md,包含当前任务、验证器命令和通过/失败日志路径。
  2. 启动 while 循环,将 PROMPT.md 管道传入 claude -p
  3. Claude 读取指令,执行一个工作单元,提交到 git。
  4. 验证器运行。退出代码 0 表示通过;其他任何代码表示失败。
  5. 将结果(迭代号、通过/失败、如果可用的令牌成本)记录到文件。
  6. 如果所有任务都通过,写入停止信号并中断。否则,循环回到第 2 步。

保持每次迭代为一个工作单元。试图在每个循环中做太多事情是导致验证器无法干净地评估的半完成状态的原因。

检测无进展和虚假完成

两个失败模式远比无限循环常见:循环在迭代之间没有取得进展,模型在破损状态下声称成功。

无进展检测需要比较第N次迭代和第N+1次迭代之间的具体信息。git diff是最简单的信号。如果在没有产生通过验证器的迭代之后,git diff HEAD~1为空,说明循环在原地打转。你应该立即标记这一点,而不是再浪费三次迭代希望有所改变。

虚假完成更难应对。模型会在你的实际验证器返回非零退出代码的情况下输出DONE、COMPLETE或EXIT_SIGNAL: true。frankbria实现中的双条件检查(两个或更多启发式指标加显式信号)是合理的缓解方案。但更好的修复是:永远不要让循环仅基于模型的输出而退出。验证脚本无论模型说什么都会运行,如果验证器失败,循环就继续——就这么简单。

小心一个相关的陷阱:验证器本身返回假阳性。如果你的测试套件有不稳定的测试,有时会在底层bug未修复的情况下通过,你就会得到一个模型甚至没有造成的虚假完成。这是下一节的主题。

处理不稳定的测试和失败的验证器

不稳定的测试是任何自动化循环的敌人。一个有80%概率通过的测试最终会在不应该通过的20%的情况下触发循环退出。而且由于每次迭代都要消耗token,虚假退出后的重新运行代价很高。

缓解方案并不复杂,但需要一些前期工作:

  • 在信任一次通过之前,连续运行验证器命令三次。如果在三次中有一次失败,就视为失败。
  • 将"工作是否完成"的测试与"环境是否正常"的测试分开。依赖网络的测试、时间敏感的断言以及任何需要外部状态的东西都不应该在控制循环退出的验证器中。
  • 记录每一次验证器运行及其输出。如果循环停止且情况看起来有问题,你希望拥有最后一次迭代的完整验证器输出,而不仅仅是退出代码。

如果验证器本身失败(崩溃、超时或返回不是测试失败的意外错误代码),将其视为循环停止,而不是循环继续。试图通过一个坏掉的验证器进行迭代只会产生无法评估的迭代。

Claude Code钩子让你可以在会话生命周期中的特定点附加脚本。Claude Code钩子指南详细介绍了机制。对于Ralph循环,相关的钩子是Claude尝试退出时触发的那个:拦截它、运行验证器,只有在验证器通过时才允许退出。如果你使用的是插件路径,这已经接好了。如果你使用的是bash循环,退出决策发生在你的shell脚本中,而不是钩子中。

记录成本并安全停止

在循环完成之前,你应该知道每次迭代的成本。你不需要实时精确数字,但需要一个日志文件来记录迭代号、验证器结果以及足够的token信息来事后估算支出。

Alibaba Cloud社区关于Ralph循环的帖子确定了三个值得内置到任何实现中的停止条件:

  • 成功:验证器返回0且所有任务都有passes: true
  • 无进展停止:两次或更多连续迭代没有git diff且验证器没有改进。
  • 预算耗尽:迭代计数器达到--max-iterations,无论验证器状态如何。

预算耗尽不是一个失败模式,而是设计好的停止。当它触发时,循环应该写出一个总结,说明什么通过了、什么没通过以及最后一次迭代尝试了什么。这为你进行手动审查或用修订的提示进行新循环提供了清晰的交接。

一个要明确构建的事情:区分"循环因为成功而停止"和"循环因为达到上限而停止"。如果你回到终端并看到"循环在第20次迭代停止",你需要知道是哪一种。日志文件中的一个标志STOP_REASON: BUDGET_EXHAUSTED对比STOP_REASON: SUCCESS就足够了。

如果你要大规模进行这类工作或想要托管设置而不是手工脚本,代理工程工作涵盖了具有适当可观测性的生产级循环设置的样子。

FAQ

`/goal`真的是Ralph循环,还是别的什么?

它们共享相同的原理(迭代直到条件成立),但架构不同。/goal在单个持久会话内运行;经典的Ralph bash循环在每次迭代时产生一个新进程。正如Ranjan Kumar所记录的,/goal使用一个单独的Haiku模型来评估每轮之后的文字记录。bash循环不评估任何东西,你的shell脚本进行检查。两者都是有效的;根据上下文累积是否对你的任务是个问题来选择。

我能使用模型自己的`DONE`输出作为验证器吗?

不是。模型的输出是你可以用作一个输入的信号,但不能是唯一的退出条件。模型会在认为任务完成时输出完成标记,这与任务实际完成的时间不同。将任何模型生成的信号与检查系统真实状态的外部命令结合起来。

如果Claude Code在运行中途压缩上下文,循环会发生什么?

在持久会话循环(插件或/goal)中,当上下文变长时压缩会自动发生,但压缩的质量不一致。一位Hacker News评论者观察到"Claude Code的压缩质量低得离谱,基本上就像每隔几轮清空历史记录一样"。新鲜上下文的bash循环完全绕过了这个问题,因为每次迭代都是干净的。如果你选择了插件路径并运行长任务,要注意压缩点之后输出质量下降的情况。

每次迭代的工作单元应该有多小?

尽可能小,但仍然要有意义。一次迭代一个任务是正确的思维模式。Geocodio方法(prd.json中每个循环一个故事)和Hacker News评论者的描述("它选择最重要的任务,完成它,然后结束循环")都指向同一个答案:小的、可验证的块远比宏大的迭代更可靠。

我真的需要一个插件吗?

对大多数任务来说,不需要。内置的/goal原语配合轮数上限可以覆盖常见情况。当你特别想要stop-hook拦截行为或这些实现提供的双条件退出逻辑时,才应该选择插件(ralph-wiggum@claude-plugins-official或frankbria/ralph-claude-code)。不要仅仅因为在教程中看到了就安装插件。

这整个模式中最尖锐的警告:模型生成的完成信号不是验证器。首先建立外部检查,先于其他一切,然后让其他所有决定都建立在这个基础上。

← 返回