循环工程:自主循环的结构解析
本文深入探讨了AI编程从提示工程到循环设计的转变,指出真正的杠杆在于构建能让AI自主工作直至目标达成的循环。文章详细分解了循环的五个核心组件:发现工作、规划、行动、验证和记忆,强调验证是循环的灵魂,必须依赖外部硬性检查(如测试通过/失败)而非模型自我评价。给出了一个20行bash的极简循环示例,并重点介绍了制动机制(步数上限、预算上限、爆破半径、断路器、活跃度检测)以防止失控。最后强调工程师应保持对代码的理解,从小范围、有边界的循环开始,逐步建立信任。

2026 年 3 月,Claude Code 的创造者承认,他已经整整一个月没有打开过代码编辑器。Boris Cherny 提交了 259 个拉取请求,每一个都不是他写的,而是由他的循环(loop)完成的。他的工作不再是编写代码,而是编写那个能编写代码的东西。
几天前,工程师 Peter Steinberger 用一句话概括了同样的理念,这句话被数百万人看到:你不再应该手动提示(prompt)Agent,而应该设计循环(loop),让循环来提示你的 Agent。两位经验丰富的工程师,独立地得出了相同的结论。如今,决定上限的技能不再是写出好提示的能力,而是构建好循环的能力。
两周之内,这个话题出现在几十篇文章中,几乎所有的文章都归结为工具清单:这是命令,这些是文件,复制它们。而本文要讨论的是别的东西:关于机制——循环实际上是什么?为什么它要么收敛于真相,要么变成昂贵的随机漫步?以及为什么这是两个不同的工程问题,而第二个问题几乎没有人教授。因为就在同一个月,另一个人的循环无人看管地运行了十一天,在有人注意到之前烧掉了数万美元。
循环与提示的区别
提示(prompt)是一条指令。你提出请求,得到回答,然后决定下一步问什么。每一步都经过你。你是引擎,Agent 是你手中的工具,你一旦停下,一切就都停下。
循环(loop)是一个目标,Agent 自行朝着它前进,直到达成。你只需设定一次目标,系统就会自行寻找工作、执行工作、检查结果、修复薄弱环节,并重复直到目标达成。你退出了回路,工作在你不在场的情况下继续进行。
区别不在于规模,而在于谁在转动步骤。在提示中,由你转动步骤;在循环中,由系统转动。这使你从执行者的角色转变为设计者:你不再亲自做工作,而是构建能做工作的机器。和任何机器一样,它有其内部设计,你必须理解它,否则它会驶向你未曾预料的方向。
一个工作循环的五个部分
除去表面噪音,一个工作循环由五个反复出现的部分组装而成。这份列表看起来简单得令人难以置信,但一个有用循环与一个烧钱循环之间的全部差异,就隐藏在这些部分之中。
发现工作:循环自行找出需要做什么:读取失败的测试、未解决的问题、最近的提交。没有这一点,你又得手动喂它任务。 计划:决定如何做,并将其分解为若干步骤。 行动:执行工作:编写代码、编辑文件、调用 API。 验证:对照目标检查结果。这是循环的核心,我们将单独讨论它。 记忆:记住哪些已完成、哪些失败,以便明天的运行能继续,而不是从头开始。
五个部分中有三个承担了所有实际工作,而它们恰好是人们搞砸循环的地方:验证、记忆,以及隐藏在验证中的停止条件。我们来逐一审视,因为如果没有它们,其余部分不过是一个美观的外壳,包裹着一台只会自说自话的机器。

但说实话,五个部分中只有两个需要认真思考。发现、计划和行动几乎总是微不足道的,由 Agent 用标准手段处理:读取任务列表、草拟步骤、编写代码——这些都是现代模型开箱即用的能力。只有在大型循环中,发现工作才需要关注,因为你必须决定下一步该抓取什么。但验证和记忆才是循环赖以生存或消亡的关键,这也正是我们将在前面用单独小节讨论它们,而对中间三个部分保持简短的原因。
验证:为什么没有它就没有循环
这是最重要的部分,也是最被低估的部分。如果没有对结果的真实检查,你拥有的不是循环,而是一个反复自说自话的 Agent。
编写代码的模型和给代码评分的模型是糟糕的裁判:它对自己的工作过于宽容。它总会让自己通过。因此,检查必须能够独立于 Agent 说“不”。一个能通过或失败的硬测试。一个可测量的条件,一个超过阈值的数字。类型检查、代码检查工具、构建过程。任何能基于事实(而非 Agent 的意见)给出裁决的东西。
这就是为什么循环首先在编程领域兴起的缘故。代码是世界上最可验证的东西:测试要么变绿要么变红,没有什么可以争辩的,Agent 总是确切地知道自己是否完成。在不存在这种检查的领域,循环是无用的,因为它无法区分“完成”和“未完成”。
由此推导出一个实用的过滤器:只有当存在某种东西可以自动拒绝错误结果时,才去构建循环。如果没有任何东西可以替你否决工作,那么循环只会空转,在制造的进展假象中消耗 Token。
最简单的工作循环:二十行 Bash
抽象得够多了,这里有一个你可以今天就复制并运行的循环。在最纯粹的形式中,循环就是一个 while 循环,它向 Agent 反复发送相同的提示,直到检查变绿。这种技术被称为“Ralph”(取自顽固的角色):笨拙,但有效。
##!/usr/bin/env bash
set -euo pipefail
MAX_ITER=20
i=0
while [ $i -lt $MAX_ITER ]; do
i=$((i + 1))
echo "=== 迭代 $i / $MAX_ITER ==="
# 先验证:如果已经变绿,直接退出,不浪费模型调用
if npm test --silent; then
echo "测试通过。迭代 $i 次后完成。"
exit 0
fi
# 否则进行一次 Agent 处理:修复第一个失败的检查,不动其他东西
claude -p "测试失败了。运行 `npm test`,读取第一个失败信息,
做出能修复它的最小改动。不要重构无关代码。
不要为了通过而降低测试标准。" \
--permission-mode acceptEdits
done
echo "达到 $MAX_ITER 次迭代上限。测试仍然红色。"
exit 1
阅读这里发生了什么,因为这二十行包含了整套骨架。npm test 检查放在每一轮的最前面:循环先问“已经完成了吗?”,只有未完成时才花费一次模型调用。-p 标志是无头模式:Agent 读取提示,执行工作,然后退出。轮次之间的记忆并不存在于模型中,而是存在于磁盘和 Git 中:下一次遍历会看到已更改的文件和红色测试,它不记得之前的对话。而 MAX_ITER 是你的主保险丝:没有它,同一个循环会一直运行到钱花光为止。
请注意使这一切运作起来的关键特性:每次迭代都以全新的上下文开始。随着窗口填满,模型会退化,因此不累积历史,而是每次重新开始并从磁盘拉取状态,这不是一个缺陷,而是主要特性。进度由文件系统保存,而非 Agent 的记忆。
/goal 与 /loop:谁来做裁决
上面那个 Bash 循环是你自己写的。现代工具已经内置了相同的逻辑,这里理解两条命令之间的区别很重要,因为人们经常混淆它们。
/loop 根据计时器或间隔重复运行一个提示。这是轮询:检查部署、重新运行测试、看看是否有变化。Agent 在每一轮中检查自己的工作。
/goal 持续运行,直到你写的一个条件被证明为真。这里的关键细节是:每一轮之后,有一个独立的、更小的模型读取对话记录,并独立判断条件是否满足。编写代码的模型不会给自己评分。这实现为一个会话范围的 Stop Hook:每轮之后,条件和对话被送入一个快速便宜的模型(如 Haiku),它返回“是”或“否”并附上简短理由,而“否”会反馈给 Agent 作为下一轮的指导。
## 持续工作直到条件满足;由独立的模型进行判断
claude -p "/goal test/auth 中的所有测试通过,且 linter 无报错"
## 按间隔重复运行一个提示(轮询,不是“直到为真”)
## /loop 监控部署并重复检查
区别不在于表面。/loop 是“重复这个动作”,而 /goal 是“持续工作直到这个条件为真,并由执行者之外的人来判断”。对于一个能收敛到结果的真正循环,你需要第二个,因为其中的检查无法被内部绕过。
记忆:为什么没有它循环就会遗忘
当一次运行结束时,模型会忘记一切。对话消失,上下文清空,下一次运行醒来时一无所知。聊天中的记忆随运行而消亡,因此它必须活在磁盘上。
实际上就是使用一个文件。比如仓库根目录下的 STATUS.md,循环首先读取它,最后重写它。它记录着什么已完成、什么正在进行、下一步要做什么,以及循环绝不能触碰的少数几件事。
## STATUS.md (循环首先读取此文件,最后写入它)
### 已完成
- [x] auth:迁移到 tokens v2,测试通过
### 进行中
- [ ] billing:webhook 重构(PR #214,CI 红色)
### 下一步
- [ ] dashboard:test/charts 中的不稳定测试
### 绝对不碰
- 未经人工确认,不要修改 infra/
这也解决了长上下文的微妙问题。随着窗口填满,模型的质量会退化:超过某个阈值,质量会明显下降。如果每次运行都以全新的上下文开始,并且状态从文件中拉取,循环就不会被自己的历史淹没。进度存在于文件和 Git 历史中,而非模型的记忆里。当上下文填满时,你会得到一个全新的 Agent,它从文件中读取状态,然后从上一个停止的地方继续。
把循环看作你从不监视的夜班。别人评价的不是你循环在凌晨三点做了什么,而是九点准时放在你桌上的便条。先设计那个便条,循环的大部分设计会自行完成。
将制造者与检查者分离
在任何循环中,最有价值的结构性举措是将执行工作的 Agent 与检查工作的 Agent 分开。
原因与通用验证相同:模型对自己过于宽容。第二个 Agent,带着不同的指令,理想情况下使用更高推理能力的模型,能够捕捉到第一个 Agent 自圆其说的问题。你的制造者可以快速且便宜,你的检查者可以缓慢且严格。这种分离构成了大部分的质量。
## .claude/agents/reviewer.md
---
name: reviewer
description: 对抗性评审者。在任何代码更改后使用。
model: opus
---
像项目所有者一样评审。假设作者是错的,直到代码变更证明并非如此。
寻找:逻辑错误、安全漏洞、缺失的测试、不符合惯例的地方。
你不编辑代码,你只找出问题。
成本是真实的:第二个 Agent 会运行自己的模型和工具,使账单翻倍。因此,只在错误代价高昂的地方投入第二次意见,而不是每件小事都做。
几乎没人教的另一半
以上所有内容都是关于构建一个能够完成工作的循环。但还有另一半,它决定了你的循环是资产还是负债。那就是刹车。
那个运行了十一天并烧掉数万美元的循环,并非聪明的 AI 失控。而是两个 Agent——一个分析者、一个验证者——礼貌地互相请求更多工作,没有步骤限制、没有预算上限、没有停止条件。任何一个上限都能在第一天就阻止它。
在马力之前先装上刹车。在装上刹车踏板之前不要交付引擎。在你离开循环之前,至少要装上以下最小集合:
步骤上限:迭代次数的硬上限,没有例外。一个大型项目上五十步已经是几十美元了。 预算上限:每个阶段的花费限制,达到后循环自动停止。 爆炸半径:一个工作树、一个分支,工作文件夹之外只读。循环不应该有物理访问权限来触碰你害怕破坏的东西。 断路器:连续三次使用相同参数运行相同工具,意味着 Agent 卡住了,而不是在工作。停止。 存活检查:每次运行时,循环向状态文件写入一个心跳信号。静默意味着它已死亡,应该唤醒你。
规则很简单:根据循环能破坏的范围来设定其范围,而不是根据你希望它做什么。先确定爆炸半径,再确定任务。
并且要诚实地定价。同一个能提交 259 个 PR 的方法,在别人手中没有刹车,却产生了数万美元的账单。这里有另一个使用相同技术的数字:一位工程师通过运行一个自治循环完成了一个五万美元的合同,而支付的 API 费用不到三百美元。相同的机器。区别在于刹车,以及循环是否有真正的检查。
以下是相同的二十行循环,但加上了刹车。它和朴素版本之间的区别,就是资产与数万美元账单之间的区别。
##!/usr/bin/env bash
set -euo pipefail
MAX_ITER=20
MAX_BUDGET_USD=10
i=0
last_failure=""
repeat_count=0
while [ $i -lt $MAX_ITER ]; do
i=$((i + 1))
# 存活检查:写入心跳,静默 = 循环已死
echo "iter=$i ts=$(date +%s)" > .loop_heartbeat
if npm test --silent; then
echo "在 $i 次迭代后通过。"; exit 0
fi
# 断路器:连续两次相同失败 = Agent 卡住,未修复
current_failure=$(npm test 2>&1 | grep -m1 "FAIL" || true)
if [ "$current_failure" = "$last_failure" ]; then
repeat_count=$((repeat_count + 1))
if [ $repeat_count -ge 2 ]; then
echo "相同失败出现 3 次。卡住。暂停等待人工处理。"
exit 2
fi
else
repeat_count=0
fi
last_failure="$current_failure"
# 预算上限:每次通过的费用限制
claude -p "用最小改动修复第一个失败的测试。" \
--permission-mode acceptEdits \
--max-budget-usd "$MAX_BUDGET_USD"
done
echo "达到迭代上限,测试仍为红色。"; exit 1
新增的三样东西就是刹车的工程设计。写入文件的心跳:如果它停止更新,你就知道循环已死,而不是静静地假装活着。重复检测器:连续两次相同失败意味着 Agent 在猜测,而不是在修复,循环会自行暂停并呼叫人工,而不是再浪费十五轮。每次通过的预算上限:即使出了问题,账单也有上限。爆炸半径还由你运行循环的文件夹以及你授予 Agent 的权限决定,这就是为什么你应在隔离的 Git 工作树或容器中运行它,而不是在主力机器上。
循环是如何死去的
每一个失败的循环都会死于四种死法之一。现在记住它们的名字,这样你在凌晨三点看到症状就能立刻认出它们并知道如何治疗。
失控 (Runaway)。症状:账单和迭代次数攀升,但进展没有。原因:两个 Agent 互相喂食,永不停止。治疗:步骤上限和预算上限,不需要更聪明的东西。 静默死亡 (Silent death)。症状:循环报告正在工作,但实际上已经停滞数小时。原因:一次运行遇到了满的上下文窗口,停下来了,但假装还活着,反复撞同一堵墙。治疗:心跳信号和每阶段刷新上下文,而不是一次无尽运行。 随机漫步 (Random walk)。症状:循环旋转但远离目标,而不是接近它。原因:没有可验证的停止条件,Agent 永远模拟“看起来完成了”。治疗:一个硬的固定点检查,比如绿色测试,不能靠言语满足。 理解债务 (Comprehension debt)。症状:仓库做了越来越多的事情,而你理解的越来越少。原因:循环代码提交的速度快于你阅读的速度,你变成了盲目批准变更的印章。治疗:一个强制的、循环绝不能跳过的“人类阅读”门禁。
前两种杀死钱和时间,后两种杀死质量和你作为工程师的能力。上一节的刹车直接治愈前一对。后一对不能靠标志治愈,只能靠纪律:一个不能靠撒谎满足的检查,以及一个不能跳过的阅读。
行之有效的顺序
如果你要构建一个循环,顺序比工具更重要。那些在现实中存活的循环,其构建方式都相同。
首先让一次手动运行能够可靠通过。然后将其指令折叠到一个技能文件(skill file)中,这样你每次就不必粘贴一大段文本了。然后将技能包裹在循环中,加入检查和停止条件。最后才把它放到一个日程上。跳过步骤,在还没有手工使其可靠之前就自动化,正是导致循环在你睡觉时爆炸的原因。先证明它一次,使其坚固,然后再自动化。
关于钱有一个重要的点,不要产生错觉。那些关于一百个 Agent 舰队的故事是真实的,但这样的舰队每月花费超过一百万,并且它能工作是因为它由一个大实验室规模的雇主买单。前沿案例是有赞助的。而你的是没有的。在普通的计划下,能够产生回报的循环是小的、有上限的、针对一项枯燥工作的,而不是一群 Agent 的蜂群。
保持工程师的身份
两个人可以构建完全相同的循环,却得到相反的结果。一个人用它来加速自己完全理解的工作。另一个人用它来完全停止理解工作。循环无法区分他们。但你可以。
Cherny 的观点从来不是工作变得更简单了。而是杠杆转移了:从提示转移到了循环,从打字转移到了判断。这是一份比提示更艰难的工作,而不是更轻松。以前,你为自己写的东西负责。现在,你为自己构建的东西负责,并且要为自己是否仍理解它在做什么负责。
所以行动要小,并且刻意保持谦逊,因为这一切都还早,成本波动剧烈。明天早上,拿你仍然手动完成的最无聊的工作:定位失败的测试、关闭陈旧的 issue、追踪不稳定的测试。围绕它包裹一个带上限的循环。先装刹车。要小到让你仍然能读到它提交的每个变更。
没有人是从一百个 Agent 开始就每月提交两百个拉取请求的。他们都是从他们信任的一个循环开始的,并且在整个过程中始终保持着工程师的身份。构建那个循环吧。
- 原文链接: x.com/zostaff/status/206...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~