1. 从“写提示词”到“搭循环”:Loop Engineering 到底在解决什么问题
如果你最近半年一直在用 Claude Code、Codex、Cursor 这类 AI 编程工具,大概率经历过这样一个阶段:一开始觉得“哇,一句话就能生成一个函数”,用着用着发现不对劲——同一个需求反复描述,模型每次给的实现都不一样;改完 A 文件忘了 B 文件,跑起来一堆报错;上下文一长,模型开始“失忆”,前面定的规范全忘了。
这不是你提示词写得不好,而是单次对话式的交互模式本身有天花板。Loop Engineering(循环工程)要解决的,就是把这个天花板往上抬一层:不再指望“一次问对”,而是设计一套可重复、可验证、可收敛的循环流程,让 AI 在循环里自己迭代,你只负责定义目标、约束和验收标准。
我自己的理解是,Loop Engineering 本质上是把软件工程里那套“构建-测试-修复”的反馈闭环,搬到了 AI 辅助开发场景里。区别在于,传统 CI 循环里“修复”是人来做的,而 Loop Engineering 里,修复动作由 AI 在受控范围内自动完成,人退到“定义规则”和“把关结果”的位置上。
这套方法适合谁?三类人最该看:一是已经在用 Claude Code 或 Codex 做实际项目、但效率卡在“反复返工”上的开发者;二是想把 AI 编程工具引入团队流程、但担心不可控的技术负责人;三是刚上手 Cursor、还在纠结“为什么别人用起来那么顺”的新手。下面我会从设计思路、核心机制、实操落地到踩坑排查,把整套循环工程拆开讲透。
2. Loop Engineering 的整体设计与核心思路拆解
2.1 为什么“单次提示”注定不够用
先讲清楚一个底层事实:大语言模型在单次生成里,没有“执行结果”这个输入。它只能基于你给的文字描述去猜代码对不对,猜完就结束了。你让它写一个排序函数,它写得出来;你让它改一个涉及五个文件、三个模块耦合的 bug,它只能靠“读代码”来推断,推断错了你也不知道,直到跑起来才炸。
Loop Engineering 的第一个设计原则就是:把“执行结果”变成循环的输入。具体做法是让 AI 每轮产出代码后,自动跑测试、跑 lint、跑类型检查,把报错信息回灌给模型,让它基于真实反馈修正。这一步听起来简单,但它是整个循环能收敛的前提——没有真实反馈,循环就是空转。
第二个原则是状态外置。模型上下文有限,对话一长就丢信息。解决办法是把“当前任务目标、已完成项、待办项、已知约束”写到一个外部文件里(比如TASK.md或PROGRESS.md),每轮循环开始时先读这个文件,结束时更新它。这样即使换模型、换会话,状态也不丢。
第三个原则是收敛判据明确。循环不能无限跑,必须定义“什么时候算完成”。常见判据有三类:测试全绿、lint 零报错、人工验收通过。三者可以组合,但至少要有一个是机器可判定的,否则循环停不下来。
2.2 三种主流工具的循环能力对比
Claude Code、Codex、Cursor 这三者都能做循环工程,但侧重点不一样。我实测下来的感受是:
| 工具 | 循环触发方式 | 状态管理 | 适合场景 |
|---|---|---|---|
| Claude Code | 命令行驱动,可脚本化 | 依赖文件系统,需手动维护 | 复杂重构、多文件改动 |
| Codex | API 调用,可编程控制 | 需自行设计状态存储 | 批量任务、自动化流水线 |
| Cursor | IDE 内交互,Agent 模式 | 内置对话历史,易丢失 | 日常开发、快速迭代 |
Claude Code 的优势在于它本身就是命令行工具,你可以用 shell 脚本把它包起来,写一个while循环,每轮把测试结果喂回去。Codex 更适合做“批处理”,比如一次性改 50 个文件的命名规范,用 API 编排循环比手动点更靠谱。Cursor 的 Agent 模式对新手最友好,但它的循环是“隐式”的,你不太容易控制它什么时候停、按什么顺序改,做精细循环工程反而要绕开它的自动化,手动分步。
我的建议是:新手从 Cursor 的 Agent 模式入手找感觉,真要落地循环工程,用 Claude Code 或 Codex 搭脚本。原因很简单,循环工程的核心是“可控”,而可控的前提是你能看到每一轮的输入输出,能干预、能回滚。IDE 里的黑盒 Agent 做不到这一点。
2.3 循环工程的四层结构
把上面这些原则落成具体结构,我习惯分成四层:
- 目标层:用自然语言写清楚“要做什么、不做什么、验收标准是什么”。这一层是给人看的,也是给 AI 读的。
- 执行层:AI 根据目标产出代码改动。这一层要限制改动范围,比如“只改 src/ 下的文件”。
- 验证层:跑测试、lint、类型检查,产出机器可读的结果。这一层是循环的“传感器”。
- 决策层:根据验证结果决定“继续循环、调整目标、还是终止”。这一层可以人来做,也可以写规则让脚本自动判断。
四层里最容易忽略的是决策层。很多人搭了循环,但没定义“什么情况下该停”,结果要么循环跑飞,要么卡在一个错误上反复重试。决策层的规则要写死,比如“连续三轮测试无改善就终止并报警”,这种硬规则比让模型自己判断靠谱得多。
3. 核心机制解析与实操要点
3.1 上下文窗口管理:循环工程的第一道坎
循环工程跑不起来,十有八九是上下文爆了。Claude Code 和 Codex 都有 token 上限,循环跑几轮之后,历史对话越堆越长,模型开始丢关键信息。解决办法不是“换更大的模型”,而是主动裁剪上下文。
我的做法是每轮循环只保留三样东西:当前任务描述、上一轮的验证结果、相关文件的当前内容。历史对话全部丢弃。具体操作上,Claude Code 可以用--no-history之类的参数控制,Codex 则在 API 调用时只传必要的 message。Cursor 的话,手动开新会话比在长对话里继续更稳。
这里有个细节:文件内容不要全量塞进去。一个项目几千个文件,全塞进去光读就超限了。正确做法是只塞“本轮可能改动的文件”,以及它们的依赖接口定义。比如你要改一个函数,就塞这个函数所在文件、调用它的文件、以及它依赖的模块的签名。这个范围可以手动指定,也可以写脚本根据 import 关系自动推导。
提示:上下文裁剪的粒度直接决定循环的稳定性。裁得太狠,模型缺信息改错;裁得太松,token 爆掉。建议先用小范围试跑,观察模型在哪一步开始“胡言乱语”,那个点就是裁剪边界。
3.2 状态文件设计:让循环“记住”自己走到哪了
状态文件是循环工程的“硬盘”。我一般用两个文件:GOAL.md写目标和约束,STATE.json写机器可读的进度。GOAL.md给人看也给模型看,内容大概是:
## 目标 把 user 模块的密码存储从明文改成 bcrypt 哈希。 ## 约束 - 只改 src/user/ 下的文件 - 不改数据库 schema - 保持现有 API 签名不变 ## 验收标准 - 所有测试通过 - 新增至少 3 个针对哈希逻辑的单元测试 - lint 零报错STATE.json则是脚本读写的,记录“当前轮次、已完成项、待办项、上轮错误摘要”。每轮循环开始时,脚本读STATE.json决定这轮干什么;结束时更新它。这样即使循环中断,下次从断点继续就行,不用从头再来。
这里有个坑:状态文件本身也会被模型改坏。我遇到过模型在“修复”过程中顺手把STATE.json的格式改乱了,导致脚本解析失败。解决办法是把状态文件放在模型改不到的地方,或者用只读方式挂载给模型看,写操作由脚本单独做。
3.3 验证层的搭建:没有测试就没有循环
循环工程能自动收敛,靠的是验证层给的“对错信号”。如果项目没有测试,循环工程基本无从谈起——模型改完代码,你没法自动判断对不对,只能人工看,那循环就退化成“手动迭代”了。
所以落地循环工程的第一步,往往是补测试。不用补全,补关键路径就行。我的经验是,先给“本轮要改的模块”补测试,覆盖率不用追求 100%,但核心逻辑必须覆盖。测试写好后,循环里每轮跑一次,报错信息直接喂给模型。
验证层除了测试,还应该包括:
- lint:统一代码风格,避免模型产出风格不一致的代码
- 类型检查:TypeScript 的
tsc --noEmit、Python 的 mypy,能抓出一大类低级错误 - 构建:确保改动不会导致编译失败
这三样加上测试,构成一个“四重验证”。每轮循环跑完,把四样的结果合并成一个摘要,喂回模型。摘要要精简,只保留错误信息和文件行号,不要把完整日志塞进去。
3.4 循环终止条件:什么时候该停
终止条件设计不好,循环要么跑飞要么卡死。我总结了几条硬规则:
- 验证全绿:四重验证全部通过,循环正常结束。
- 连续 N 轮无改善:比如连续 3 轮错误数量没减少,判定为“卡住”,终止并报警。
- 轮次上限:设一个硬上限,比如 20 轮,到了就停,防止无限循环。
- 改动范围越界:如果模型改了约束之外的文件,立即终止并回滚。
这四条里,第 2 条最重要。模型有时候会陷入“改 A 坏 B、改 B 坏 A”的死循环,没有这条规则就会一直跑下去。实现上,脚本每轮记录错误数量,连续三轮不降就触发终止。
注意:终止不等于失败。循环终止后,把当前状态、最后一轮的错误、以及模型尝试过的改动记录下来,人工介入往往能快速找到症结。我遇到过好几次,模型卡住的地方其实是我
GOAL.md写得不清楚,改一下目标描述,循环立刻就通了。
4. 完整实操流程:从零搭一个可跑的循环
4.1 环境准备与工具安装
先讲环境。Claude Code 的安装,官方文档写得很清楚,但国内用户常卡在登录环节。我的建议是先把基础环境跑通,再考虑循环工程。安装步骤大致是:
# 以 npm 全局安装为例 npm install -g @anthropic-ai/claude-code # 验证安装 claude --versionCodex 的安装类似,走 npm 或官方安装包。Cursor 则是下载 IDE,装完在设置里配好模型和 API key。这三者可以共存,我自己的机器上三个都装了,按任务类型切换用。
环境准备好后,建一个测试项目。不要拿生产项目练手,循环工程初期一定会出乱子,拿个玩具项目试最安全。我用的是一个简单的 Node.js 项目,带 Jest 测试和 ESLint,结构清晰,方便观察循环行为。
4.2 编写循环脚本:一个可复用的模板
循环脚本的核心逻辑是“读状态 → 调 AI → 跑验证 → 更新状态 → 判断是否继续”。用 bash 写一个简化版大概是这样:
#!/bin/bash MAX_ROUNDS=20 ROUND=0 while [ $ROUND -lt $MAX_ROUNDS ]; do ROUND=$((ROUND+1)) echo "=== Round $ROUND ===" # 1. 读状态和目标 GOAL=$(cat GOAL.md) STATE=$(cat STATE.json) # 2. 调 AI 产出改动 claude --prompt "目标:$GOAL\n当前状态:$STATE\n请产出代码改动" \ --output-format json > round_$ROUND.json # 3. 跑验证 npm test > test_$ROUND.log 2>&1 TEST_RESULT=$? npx eslint src/ > lint_$ROUND.log 2>&1 LINT_RESULT=$? # 4. 判断是否全绿 if [ $TEST_RESULT -eq 0 ] && [ $LINT_RESULT -eq 0 ]; then echo "验证通过,循环结束" break fi # 5. 更新状态,把错误喂回下一轮 # (这里省略具体解析逻辑,实际要提取错误摘要写入 STATE.json) done这个模板是骨架,实际用的时候要补三块:错误摘要提取、状态更新、越界检查。错误摘要提取可以用grep抓关键行,状态更新用jq改 JSON,越界检查用git diff --name-only对比约束范围。
脚本写好后,先手动跑一轮,看 AI 产出的改动是否符合预期,验证层是否能正确报错。确认无误再放开循环。
4.3 参数选择与调优:几个关键数字
循环工程里有几个参数需要调,调不好直接影响效果:
- 每轮改动的文件数:建议控制在 3-5 个。改太多,模型顾此失彼;改太少,循环轮次暴涨。这个数字可以根据项目复杂度微调。
- 上下文里保留的历史轮次:建议只保留上一轮。保留太多,token 浪费且干扰判断。
- 连续无改善的阈值:建议 3 轮。2 轮太敏感,容易误判;4 轮太迟钝,浪费算力。
- 单轮超时:建议 5 分钟。AI 调用加验证跑完,正常不会超过这个时间,超了大概率是卡住了。
这些数字不是拍脑袋定的,是我在几个项目上试出来的经验值。你的项目如果特别大或特别小,可以按比例调整。比如小项目改动文件数可以降到 2,大项目可以升到 8,但超过 10 基本就失控了。
4.4 一次完整的循环实录
拿一个真实场景举例:把项目里的var全部改成let/const。这个任务适合练手,因为规则明确、验证简单。
第一轮,我把GOAL.md写好,跑脚本。AI 改了 4 个文件,测试通过,但 lint 报了 2 个错——有一处它把var改成了const,但那个变量后面被重新赋值了。错误信息喂回去。
第二轮,AI 修正了那处,lint 通过,测试也通过。循环结束,总共两轮。
这个例子太顺利了,实际项目里经常要跑七八轮。我印象最深的一次是改一个状态管理模块,模型前五轮一直在“改 A 坏 B”,第六轮我手动改了GOAL.md,把“不要动 reducer 的签名”这条约束加进去,第七轮就通了。这说明循环卡住时,先检查目标描述,而不是怀疑模型能力。
5. 常见问题与排查技巧实录
5.1 循环跑飞:模型改了不该改的文件
这是最常见的问题。模型在“修复”过程中,会顺手改一些它认为相关但你没授权的文件。排查方法是每轮循环后跑一次git diff --name-only,对比约束范围。如果越界,立即git checkout回滚,并把越界信息写进下一轮的提示里,明确告诉模型“不要动这些文件”。
预防措施是在GOAL.md里写死“允许改动的文件列表”,而不是写“不要改某某文件”。正面清单比负面清单有效,因为模型对“允许”的遵循度明显高于“禁止”。
5.2 循环卡死:连续多轮错误数量不变
错误数量不变,说明模型在“原地打转”。原因通常有三个:一是目标描述有歧义,模型理解偏了;二是验证层的错误信息不够具体,模型不知道错在哪;三是任务本身超出了模型能力。
排查顺序是:先看错误信息是否具体,如果只是“测试失败”没有堆栈,那模型确实没法修;再看GOAL.md是否有歧义,把模糊表述改成具体例子;最后才考虑换模型或拆任务。我遇到的大部分卡死,都是前两个原因。
5.3 上下文丢失:模型“忘记”了前面的约束
循环跑到后面,模型开始违反前面定好的约束,这是上下文被挤掉的典型症状。解决办法是每轮循环都把GOAL.md的完整内容重新塞进提示里,而不是只塞“本轮任务”。多花点 token,但能保证约束不丢。
另一个技巧是把关键约束放在提示的开头和结尾。模型对首尾信息的注意力高于中间,这是实测出来的规律。把“不要改数据库 schema”这种硬约束放开头,把“验收标准”放结尾,遵循度会明显提升。
5.4 验证层误报:测试通过但实际有问题
有时候测试全绿,但代码实际跑起来有问题。这通常是测试覆盖不足导致的。循环工程依赖测试,测试不靠谱,循环就会“假收敛”。
解决办法是给验证层加一道“冒烟测试”——用真实数据跑一遍核心流程,不追求覆盖率,只求能发现明显问题。冒烟测试通过,才算真通过。这一步会增加循环时间,但能避免“测试全绿、上线就炸”的尴尬。
5.5 常见问题速查表
| 现象 | 可能原因 | 排查动作 | 解决方式 |
|---|---|---|---|
| 改了不该改的文件 | 约束不明确 | 查 git diff | 写正面清单,越界即回滚 |
| 连续多轮无改善 | 目标歧义或错误信息不具体 | 查 GOAL.md 和错误日志 | 改目标描述,补错误堆栈 |
| 模型忘记约束 | 上下文被挤掉 | 查提示内容 | 每轮重塞完整 GOAL.md |
| 测试通过但实际有问题 | 测试覆盖不足 | 跑冒烟测试 | 补关键路径测试 |
| 循环不终止 | 终止条件缺失 | 查脚本逻辑 | 加轮次上限和无改善阈值 |
| token 超限 | 上下文裁剪不当 | 查每轮提示长度 | 只保留上轮结果和相关文件 |
5.6 几个我踩过的坑
第一个坑是状态文件被模型改坏。前面提过,模型会“顺手”改它看到的文件,包括STATE.json。后来我把状态文件放到项目外,用绝对路径引用,模型看不到也改不了,问题解决。
第二个坑是验证脚本本身有 bug。有一次 lint 配置写错了,把所有文件都报错,循环跑了十轮都在“修”一个不存在的问题。教训是验证层要先单独跑通,确认它能正确区分对错,再接入循环。
第三个坑是模型版本切换导致行为突变。同一个提示,换个模型版本,产出风格完全不一样,循环直接崩。解决办法是锁定模型版本,循环期间不升级。要升级就重新调一遍参数。
6. 循环工程的扩展玩法与个人体会
6.1 多循环嵌套:大任务拆小循环
单个循环适合中等规模的任务。如果任务特别大,比如“重构整个认证模块”,一个循环跑不下来。这时候可以拆成多个小循环,每个循环负责一个子任务,循环之间用状态文件传递进度。
比如认证模块重构可以拆成:循环一改密码存储,循环二改 token 生成,循环三改权限校验。每个循环独立跑,跑完更新总状态,再启动下一个。这样每个循环的上下文都短,收敛快,出问题也好定位。
6.2 循环工程与代码审查的结合
循环跑完不等于任务完成,还要过人工审查。我的做法是循环结束后,让 AI 生成一份“改动摘要”,列出改了哪些文件、每个文件改了什么、为什么这么改。这份摘要作为审查的起点,比直接看 diff 高效得多。
审查时重点关注三类改动:一是涉及安全逻辑的,二是涉及数据结构的,三是模型“自作主张”加的额外改动。前两类风险高,第三类往往是模型理解偏差的产物,都要仔细看。
6.3 我对循环工程的几点体会
用了大半年循环工程,最大的体会是:它把 AI 编程从“抽奖”变成了“工程”。以前用 AI 写代码,质量全看运气,同一个提示十次十个样。现在有了循环和验证,质量下限被抬高了,虽然上限还是看模型能力,但至少不会产出明显有问题的代码。
第二个体会是测试的价值被重新发现了。以前写测试是为了“防回归”,现在写测试是为了“让 AI 能自己迭代”。没有测试的项目,循环工程根本跑不起来。这反过来倒逼我把测试补上,算是意外收获。
第三个体会是人的角色变了。以前是“写代码的人”,现在是“定义规则和验收标准的人”。这个转变一开始不适应,总觉得“不亲手写不放心”,但用久了发现,定义好规则之后,AI 产出的代码质量比我手动写还稳定,因为规则不会累、不会忘。
最后分享一个小技巧:循环工程初期,把MAX_ROUNDS设小一点,比如 5 轮,跑几次观察行为,摸清模型在你项目上的“脾气”之后再放开。不同项目、不同模型,循环行为差异很大,没有一套参数通吃。多试几次,找到适合自己项目的节奏,比照搬别人的配置有用得多。