Loop Engineering:别再提示 Agent,去设计提示 Agent 的系统
我通读了这份 11 页的 IEEE 风格工作笔记:《Loop Engineering: The Anthropic Playbook for Designing Systems That Prompt Your Agents》。它的标题看起来像又一个 AI 黑话,但读完我反而觉得,这个词值得被认真对待。
因为它说的不是“怎么把 prompt 写得更漂亮”。它说的是:别再把自己放在每一次 Agent 调用的键盘前,去设计那个会自动提示 Agent 的系统。
这一步变化很大。Prompt engineering 关心一句话怎么写;context engineering 关心这轮窗口里该塞什么;harness engineering 关心一次 Agent 运行该配哪些工具、权限和完成条件;Loop engineering 再往上一层,关心的是:怎样让一次运行变成可重复、可校验、可记忆、会自己转起来的工作系统。
换句话说,人从“内循环里的提示者”,变成了“外循环上的系统设计者”。这篇文章我不打算翻译论文,而是把它拆成一个工程师真正能带走的模型:四层栈、五步循环、六个部件、一个最关键的生成器/评估器分离,以及四笔迟早会来收账的隐性债务。
先给结论:Loop Engineering 是第四层,不是 Prompt Engineering 的升级包
这份 PDF 最有价值的地方,是把几个容易混在一起的概念摆成了一层一层的栈。

每上一层,关注的单位都变大一圈:
| 层级 | 关心什么 | 核心问题 |
|---|---|---|
| Prompt Engineering | 一句好提示词 | 我要怎么告诉模型? |
| Context Engineering | 当前窗口 | 这一轮该放进哪些上下文? |
| Harness Engineering | 一次运行 | 工具、动作、权限、done 条件是什么? |
| Loop Engineering | 反复运行的系统 | 怎么让它自己一轮又一轮地跑? |
这个栈解释了为什么很多人刚听到 Loop Engineering 会误解。它不是“我写一个 while true 让 Agent 重跑”。那只是重复执行。真正的 loop 不是空转,而是每一轮都在做一组完整动作:发现这轮该做什么,隔离地交出去,独立验证,把状态写到对话之外,再决定下一次什么时候继续。
这也是它和自动化脚本的分水岭。脚本通常执行固定指令;loop 会把上一轮的输出变成下一轮的输入。脚本跑完就结束;loop 会携带状态,明天接着今天的未完成项继续转。
一轮 Loop 有五个动作:少一个,就会变形
PDF 把一次 loop 拆成五个动作:Discovery、Handoff、Verification、Persistence、Scheduling。我更愿意把它翻成五句话:
- 发现:系统自己找出这轮值得处理的工作。
- 交接:把工作交给合适的 Agent,并隔离执行现场。
- 校验:换一个独立视角判断结果能不能过。
- 沉淀:把状态写到对话窗口之外。
- 调度:让下一轮在正确时间自己发生。

这里面最容易被低估的是 Discovery。很多团队说自己做了 loop,其实只是把“执行”自动化了,早上还是人来决定“今天修哪三个问题”。这当然有价值,但它还没有把人从内循环里拿出去。真正的 loop 至少要能自己读失败的 CI、打开的新 issue、昨天合并的 commit、上一次留下的 state file,然后给出今天值得处理的候选项。
第二个关键是 Handoff。只要你允许多个 Agent 并行,隔离就不再是洁癖,而是生存条件。两个 Agent 在同一个工作目录里改同一个文件,和两个工程师同时在同一行上手搓冲突没有本质区别。PDF 里推荐的做法很朴素:一项工作一个 git worktree。不要让并行变成互相踩脚。
第三个也是整篇里最重要的:Verification。一个 loop 有没有工程价值,不看它能跑多久,而看它有没有一个东西能对它说“不”。如果每一轮都是写代码的 Agent 自己宣布“我做得不错”,这不是闭环,这是自我肯定机。
Persistence 则解决另一个常见幻觉:不要把记忆放在聊天窗口里。上下文窗口会清空,会被截断,会被下一轮污染。真正能让 loop 明天接着今天干的,是 state.md、Linear board、PR、inbox、commit 这些在对话之外存在的东西。论文里有一句判断很准:Agent 会忘,repo 不会。
最后才是 Scheduling。没有调度,前面四步做得再漂亮,也只是“人手动运行的一次流程”。调度可以是本地循环命令,可以是桌面自动化,可以是 cloud routine,也可以是 GitHub Actions cron。选哪种不重要,重要的是你要知道代价:本地调度能看到本机状态,但机器关了就停;云端调度能在人睡觉时继续跑,但通常拿不到本地开发服务器和本机文件。
六个部件:把五个动作落到工程系统里
五个动作回答的是“这一轮发生什么”。六个部件回答的是“系统要长什么器官,才能让这一轮发生”。
| 部件 | 它是什么 | 对应动作 |
|---|---|---|
| Automations | schedule 或 event trigger | Scheduling |
| Worktrees | 给并行 Agent 的隔离目录 | Handoff |
| Skills | 项目知识的持久化文件 | Discovery |
| Connectors | MCP / 外部系统接口 | Discovery / Persistence |
| Sub-agents | 写的人和审的人分开 | Verification |
| Memory | 磁盘上的持久状态 | Persistence |
我最喜欢 PDF 里对 Skill 的定义:它是在偿还 intent debt。所谓 intent debt,就是你每次开一个新对话,都要重新解释“这个项目是什么、规矩是什么、坑在哪里、哪些地方不能碰”的成本。Prompt 是一次性叮嘱,Skill 是可维护的项目知识。Loop 如果靠一大坨 cron prompt 活着,迟早会腐烂;靠 Skill 活着,才有机会持续演化。
Connectors 决定 loop 的视野半径。一个只能读本地文件系统的 loop 很小;能读 issue tracker、CI、Slack、数据库、staging API 的 loop 才开始像一个团队成员。MCP 的意义也在这里:它不是炫技接口,而是把 loop 从“我能在 repo 里跑”扩展到“我能看见外部世界发生了什么”。
Memory 则决定 loop 是否能积累。没有 memory 的 loop,每天早上都像刚入职一样重新理解世界;有 memory 的 loop,才会知道昨天发现了什么、哪些已经处理、哪些进了 inbox、哪些 PR 等人看。
全文最硬的一点:生成器和评估器必须分开
PDF 里最值得抄进工程规范的观点,是 generator/evaluator separation。
写代码的 Agent,不应该负责判断自己写得好不好。
这不是因为 Agent 不够聪明,而是因为结构上有偏差。写代码的上下文里,已经塞满了它为什么这样写的理由。它回头看自己的 diff,看见的不是冷冰冰的结果,而是一路自我说服的过程。让它给自己打分,就像让作者给自己的稿子做终审。

一个好的 evaluator 有四个特征。
第一,它默认怀疑。不是“帮我看看有没有问题”,而是“假设这段代码是坏的,直到证明它不是”。语气差异背后是验收姿态的差异。
第二,它用动作验证,而不只是读代码。前端任务不能只看 JSX 看起来对不对,要打开页面、点按钮、截屏、检查 DOM、跑测试。后端任务也一样,能执行就执行,能打真实接口就打真实接口。判断行为,不判断意图。
第三,它最好和 generator 不是同一个模型或同一组指令。换一个模型不总是必要,但换一个视角非常必要。否则你只是让同一个盲点换了件衣服回来。
第四,停止条件要清楚。比如“test/auth 全部通过,lint 干净,登录按钮在浏览器里点击后能进入 dashboard”。不要把 stop condition 写成“修好这个问题”。太抽象的完成条件,会让 loop 把“看起来差不多”当成“已经完成”。
这里其实是一个老原则的新包装:maker-checker。银行里录入大额转账的人和审批的人不能是同一个;loop 里写代码的人和放行的人也不该是同一个。区别只是,现在 maker 和 checker 都可以是 Agent,但它们必须结构性分离。
五种坏 Loop:每一种都是少了一个动作
PDF 很好的一点,是没有把 loop 写成万能机器。它列了一组反模式,刚好对应前面的五个动作。
| 少掉的动作 | 坏 loop | 症状 | 修法 |
|---|---|---|---|
| Verification | 点头 loop | 跑了几百轮,从没拒绝过自己 | 独立 evaluator |
| Persistence | 失忆 loop | 每天重复发现同一件事 | 写 state file / board |
| Scheduling | 手动 loop | demo 那天很惊艳,后来没人记得跑 | 真实 timer / trigger |
| Discovery | 盲 loop | 人每天早上仍在喂任务清单 | 把发现逻辑写成 Skill |
| Handoff | 缠线 loop | 并行后冲突一团乱 | 一事一 worktree |
这张表的残酷之处在于:最容易产出可见 demo 的,恰恰是 Discovery 和 Handoff;最容易被省掉的,恰恰是 Verification、Persistence 和 Scheduling。也就是说,一个粗糙 loop 最容易保留“看起来会干活”的部分,省掉“让它长期安全”的部分。
如果你看到一个 loop 跑了很久,但从来没有主动说过“不行,这个结果不能过”,那它大概率没有真正的校验。真实工作里不可能几百次都全对。一个从不拒绝自己的系统,不是在稳定,是在失真。
Stripe 的 Minions:可靠性来自约束,不是模型更大
PDF 提到的现实案例里,最有冲击力的是 Stripe 的 Minions:每周合并超过 1300 个机器写的 PR。这个数字很容易让人兴奋,但真正值得学的不是规模,而是它怎么把不确定性关进笼子。
它不是把所有事情都丢给 LLM。相反,LLM 醒来之前,有一段确定性 orchestrator 先做事:扫链接、拉 Jira、找文档、通过 Sourcegraph 和 MCP 定位代码。能用规则解决的,就不要交给概率模型。LLM 负责创造性产出,硬规则负责门禁。
它的管线大概是这样:

这给我的启发很直接:一个强 loop 的核心,不是“模型多强”,而是“哪些事情绝不交给模型自由发挥”。
代码搜索可以确定化,lint 可以确定化,测试可以确定化,commit 步骤可以确定化,预算上限可以确定化,PR 必须人审也可以确定化。LLM 应该被放在最需要弹性的地方,而不是被要求同时当产品经理、开发、测试、预算管理员和最终审批人。
这也解释了为什么很多小团队做 loop 会失败:他们把所有判断都塞进一个 Agent 里,然后惊讶于它越跑越飘。不是 Agent 背叛了你,是你没有给它边界。
四笔隐性债务:Loop 越顺,越容易欠
Loop engineering 最吸引人的地方,是一个人可以做出一个小团队的产出。最危险的地方,也是同一句话。
PDF 把成本拆成四类:verification debt、comprehension rot、cognitive surrender、token blowout。我翻成四笔债:

第一笔是校验债。每一个“测试绿了但其实没覆盖到”的 PR,都会把未验证产出暂存在未来。它不会马上报错,通常会在某个发布日一起爆出来。
第二笔是理解腐烂。Loop 写得越快,你脑子里的代码地图越容易落后。你没有亲手写这些代码,也没认真读它们,代码库却每天在长。几周之后,你还是 owner,但已经不是理解者。
第三笔是判断投降。系统越顺,人越容易停止拥有观点。刚开始你会认真看每个 PR;后来你只看标题;再后来你相信“反正它一直都行”。这不是节省时间,这是把判断权拱手交给一个会自信犯错的系统。
第四笔是 token 失控。Loop 会派生 helper,会重试,会在一个坏假设上转一整夜。没有 per-run budget、daily budget、max retries,它的 bug 不只是 bug,还是一张账单。
四笔债不是独立的。未校验产出越多,人越不理解;越不理解,越不想看;越不想看,loop 越长时间无人监管;无人监管越久,成本和错误越滚越大。最可怕的是,四笔债在积累时都很安静。
怎么做第一个 Loop:小,但五脏俱全
PDF 最后给了一个很务实的建议:第一个 loop 不要学 Stripe。Stripe 是终点,不是起点。第一版应该小到能被你完整理解,但必须把该有的安全部件都装上。
真正的第一版 checklist 可以很简单:
| 元素 | 你要问自己的问题 |
|---|---|
| Discovery | 它定时读什么?CI、issue、commit、inbox? |
| State file | 跨轮记忆写在哪里? |
| Evaluator | 有没有独立检查能说 no? |
| Isolation | 并行时是不是每个 Agent 一个 worktree? |
| Token cap | 每轮、每天、最大重试有没有上限? |
| Human review | 哪一步会停下来给人看? |
注意最后一项。Human review 不是临时脚手架,不是“等 loop 成熟后就拆掉”。恰恰相反,它是让 loop 长期可信的永久入口。你不一定每次都介入,但你必须随时能介入。
下面这个 morning triage 示例,就是按这张 checklist 搭出来的。
完整示例:用 Codex 做一个每天早上自己巡检 CI 的 Loop
只讲原则还是太飘。下面我把第一版 loop 写成一个基于 Codex CLI 的完整示例:每天早上 6 点,它读取失败的 CI、新增 issue、昨天合并的 commit,挑出值得处理的 finding;每个 finding 开一个隔离 worktree;Codex 起草修复;再用一次独立的 Codex review 做验收;最后只开 PR,不自动合并。
这不是 Stripe Minions 那种企业级管线,而是一个个人或小团队今天就能照着改造的最小闭环。下面的代码是 Codex-first 骨架,不是即插即用脚本;真实落地时,你还要补上 Codex CLI 的安装和认证、仓库权限、测试命令和通知方式。

1. 调度:先让它在固定时间醒来
第一步不是写一个巨大 prompt,而是给 loop 一个真实触发器。比如 GitHub Actions:
# .github/workflows/morning-triage.yml
name: morning-triage
on:
schedule:
- cron: "0 22 * * *" # UTC 22:00 = 北京时间 06:00
workflow_dispatch:
jobs:
triage:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
issues: read
pull-requests: write
actions: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run Codex morning triage loop
run: ./scripts/loop/morning-triage.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAX_READY_FINDINGS: "3"
这段 YAML 只负责两件事:到点启动,并给这次运行套上外层上限。timeout-minutes 防止整夜空转,MAX_READY_FINDINGS 防止第一版 loop 一口气开太多工作面。
这里我故意没有展开 Codex 的安装和登录。GitHub-hosted runner 默认不会凭空拥有你的 Codex 登录态;生产里更合理的做法,是在受控 runner 上预装 Codex CLI,或者在 CI bootstrap 阶段用团队批准的 secret 注入认证。
它不应该承载大量业务判断。调度器里塞满 prompt,是 loop 腐烂的开始,因为没人会认真维护 cron 里的那坨自然语言。
2. Discovery:把“怎么找事”写成 Skill
真正的发现逻辑,应该放进 Skill。Skill 是可读、可改、可 review 的项目知识。
<!-- .codex/skills/morning-triage/SKILL.md -->
---
name: morning-triage
description: 每天早上读取 CI、issue、commit,找出值得处理的工程 finding。
---
## Read
- 过去 24 小时失败的 CI run
- 过去 24 小时新开的 issue
- 昨天合并到 main 的 commit
- 上一次留下的 `state/triage.md`
## Judge
保留满足任一条件的 finding:
- 阻塞 main 分支
- 影响发布
- issue 有明确复现步骤
- commit 引入了可疑失败
跳过这些噪音:
- 没有复现信息的抱怨
- 已经有 PR 处理的重复问题
- flaky 且三次重跑后恢复的 CI
## Write
把结果写入 `state/triage.md`,每条 finding 必须包含:
- id
- source
- priority
- hypothesis
- next_action
- status
## Stop
不要自动合并。不要删除代码。信心不足的 finding 写入 `state/inbox.md`。
这里的 Stop 很重要。很多自动化失败,不是因为它不会做事,而是因为它不知道哪里不该做。这个边界不能指望 Agent 自己猜,必须由工程师写进系统。
如果你的 Codex 环境不会自动发现 repo-local skills,就把这个 Skill 安装到 $CODEX_HOME/skills,或者在脚本里显式读取这份文件并塞进 prompt。示例里用 repo 路径,是为了让团队能把 Skill 和代码一起 review。
3. Persistence:让状态活在对话之外
Skill 的输出不是聊天回复,而是磁盘上的状态文件:
<!-- state/triage.md -->
| id | source | priority | hypothesis | next_action | status |
|---|---|---:|---|---|---|
| auth-ci-4821 | CI #4821 | P0 | 登录测试在 token refresh 后失败 | 修复 refresh mock | ready |
| issue-913 | issue #913 | P1 | 空项目打开 dashboard 报错 | 加空态保护 | ready |
| dep-audit | commit a3f91c | P2 | 依赖升级引入 warning | 先观察 | inbox |
这就是 loop 的记忆。明天早上它会先读这个文件,知道哪些还没处理,哪些已经开了 PR,哪些被人类放进了 inbox。没有这个文件,loop 每天都在重新入职。
4. Handoff:每个 finding 一个 worktree
现在才轮到执行。一个简单的脚本可以把 ready 的 finding 拆出去:
#!/usr/bin/env bash
# scripts/loop/morning-triage.sh
set -euo pipefail
codex --ask-for-approval never exec \
-C "$PWD" \
-s workspace-write \
-o codex-triage-summary.md \
- <<'PROMPT'
Use the morning-triage skill at .codex/skills/morning-triage/SKILL.md.
Read CI, issues, commits, and state/triage.md.
Update state/triage.md and state/inbox.md.
Do not modify application code in this discovery step.
PROMPT
这一步只做 Discovery 和 Persistence:让 Codex 读外部信号,更新 state/triage.md,但不碰业务代码。把“找事”和“做事”拆开,后面才容易验证。
接下来,每条 ready finding 都交给一个独立 worktree:
python scripts/loop/list-ready-findings.py state/triage.md |
head -n "${MAX_READY_FINDINGS:-3}" |
while read -r finding; do
branch="loop/${finding}"
worktree="../worktrees/${finding}"
if [ -d "$worktree" ]; then
echo "skip existing worktree: $worktree"
continue
fi
git worktree add "$worktree" -b "$branch"
(
cd "$worktree"
codex --ask-for-approval never exec \
-C "$PWD" \
-s workspace-write \
-o codex-generator-summary.md \
- <<PROMPT
Fix finding ${finding}.
Keep the change minimal.
Run the relevant tests.
Do not commit.
PROMPT
)
done
这里最朴素、也最关键的设计是一行:git worktree add。它让每个 Codex run 有自己的工作台。没有隔离,并行就是灾难预演。
5. Verification:让 Codex review 专门挑错
执行用的是 Codex,验收也可以用 Codex,但必须是另一次独立调用。不要让刚写完代码的那次上下文顺手宣布胜利。
给 review 单独一份指令:
<!-- .codex/evaluator-review.md -->
# Role
你是 adversarial evaluator。默认假设代码是坏的,直到证据证明它是好的。
# Check
1. 运行相关测试,贴出真实输出。
2. 运行 lint 或 typecheck,贴出真实输出。
3. 检查 finding 是否真的被解决,而不是只改了测试。
4. 检查边界条件:空数据、重复请求、权限不足、网络失败。
5. 如果是前端问题,用浏览器打开页面,点击复现路径,截图或描述 DOM 状态。
# Verdict
只能输出 `PASS` 或 `REJECT`。
`REJECT` 必须列出原因和下一步建议。
然后对每个 worktree 调 codex exec review:
(
cd "$worktree"
codex --ask-for-approval never exec review \
--uncommitted \
-o evaluator-report.md \
- < ../../.codex/evaluator-review.md
)
codex exec review --uncommitted 会针对当前 worktree 里的未提交改动做 review。它和 generator 是两次独立 Codex 调用:前一次负责生成,后一次负责反对。
这里的关键不是“多跑一次 Codex”,而是“换一种姿态”。Generator 的姿态是完成任务;Evaluator 的姿态是证明它还没完成。一个负责造,一个负责拆,这个结构比任何单句 prompt 都可靠。
6. Human review:只开 PR,不自动合并
最后一步故意留给人:
if head -n 1 evaluator-report.md | rg -q '^PASS$'; then
git add .
git commit -m "fix: ${finding}"
git push -u origin HEAD
gh pr create \
--head "$branch" \
--title "fix: ${finding}" \
--body-file evaluator-report.md
else
cp evaluator-report.md "../../state/inbox-${finding}.md"
fi
注意,这里没有 gh pr merge。这不是保守,而是设计。第一版 loop 的目标不是“完全没人管”,而是“把机械劳动推到 PR 前,把最终判断留给人”。当你连续几周看到 evaluator 真能抓住错误,再讨论哪些低风险 PR 可以自动合并。
把这个例子映射回前面的五个动作,会很清楚:
| 动作 | 示例里的 Codex 落点 |
|---|---|
| Discovery | codex exec 调用 morning-triage Skill 读取 CI / issue / commit |
| Handoff | 每个 finding 一个 git worktree |
| Verification | codex exec review --uncommitted 做独立验收 |
| Persistence | state/triage.md、state/inbox.md、PR |
| Scheduling | GitHub Actions cron 每天早上触发 |
这就是一个基于 Codex 的完整 loop。它不大,也不神秘,但已经具备最重要的工程性质:会自己醒来,会自己找事,会隔离执行,会被独立校验,会留下记忆,也会在该停的地方停下来等人。
这篇 PDF 真正想提醒工程师的事
Loop engineering 表面上是在讨论 Agent 自动化,底层其实是在讨论工程师的价值迁移。
当生成变得便宜,判断就变得更贵。Loop 可以生成代码、计划、PR、修复方案,甚至一晚上生成几十个候选实现。但它不能真正替你判断哪个方向值得做,哪个看起来合理但根上是错的,哪个改动虽然测试绿了却会破坏产品语义。
这会把工程师分成两类。
一类人用 loop 放大自己已经拥有的判断。他们读代码,理解系统,知道哪里需要人工 checkpoint,知道哪些事情必须确定化,知道 evaluator 该怎么怀疑。Loop 让他们更强。
另一类人用 loop 逃离理解。他们把“不想看”包装成“自动化”,把“不知道”交给 Agent 去猜,把“过了测试”当成“我可以不关心”。Loop 也会放大他们,只是放大的是懒惰和盲区。
所以我读完这篇 PDF,最想带走的不是某个具体命令、worktree 或 MCP 连接器。真正要带走的是一句工程纪律:
设计 loop,但要像一个打算继续当工程师的人那样设计它。
把生成交给机器,把校验拆出来,把状态写到磁盘,把预算上限写死,把人工 review 的门留着。不要做那个每分钟提示 Agent 的人,也不要做那个只会按下 Go 的人。Loop engineering 最好的形态,是让人从机械劳动里退出来,但仍然站在判断的位置上。
这才是“别再提示 Agent,去设计提示 Agent 的系统”的完整含义。