Loop Engineering - 从提示词到循环执行

@0x_kaize
英语2天前 · 2026年7月04日
318K
190
24
15
457

TL;DR

本指南介绍了 Loop Engineering,这是一个用于构建可迭代运行的自主 AI Agents 的框架。内容涵盖了五个核心课程:发现、交接、验证、持久化和调度,旨在确保自动化流程的可靠性。

我已决定将这篇文章专门用于介绍如何创建你自己的可编程 Agent,让它为你完成工作,这样你就不必每天早上打开电脑,一遍又一遍地给它下达指令。

它会自己醒来,自己寻找任务,自己发现问题,并将结果保存并呈现在你的桌面上,供你审阅。

前言

Loop 的想法并非凭空而来——在 2026 年 6 月的同一个星期里,三位行业专家几乎同时表达了同一个想法:是时候开始开发这个层级了。

一周后,Andrew Ng 将这次讨论置于"从 0 到 1 构建产品"的更广泛背景下,提供了更全面的图景。

这篇文章从那个故事开始,然后带你从头开始构建一个最小但完整的循环:5 个步骤、6 个部分、一扇写着"不"的门,以及一扇人类之门——全部在你自己的机器上运行,并且可以在任何软件项目上重复使用。

kaize - inline image

来源:华数小册、Loop Engineering:别再问我它是什么(v260615,2026 年 6 月)

那一周,3 人点燃了导火索

2026 年 6 月: 三个人,没有任何协调,在几天内说了几乎相同的话。

Peter Steinberger,OpenClaw 的创建者,在 X 上发了两句话,获得了 890 万次浏览:

https://x.com/steipete/status/2063697162748260627

那条帖子才是这场讨论的真正起点。它不是宣言,也不是长文,只是两行字,点明了一个人们早已在悄悄做的事情。

Boris Cherny: 我不再向 Claude 输入提示了,我有正在运行的循环。是它们负责向 Claude 输入提示并弄清楚该做什么。

Addy Osmani: Loop Engineering 就是取代你自己,成为那个向 Agent 输入提示的人。你设计的系统来完成这件事。

实践比命名早了几个月,每个人都在写循环,但还没有人给它起名字。

所以,循环现在是我们如何让 AI Agents 能够长期迭代工作以创建软件的关键部分。

kaize - inline image

Andrew Ng 根据时间尺度将三个循环从内到外排列:最内层的循环速度最快,最外层的循环速度最慢。此外,最外层循环的输入来自于最内层循环的输出:

循环

谁来运行

循环时长

向外层输出的内容

Agent 编码循环

Agent 本身

秒 - 分钟

一个可运行的版本

开发者反馈循环

人类 + Agent

数十分钟 - 数小时

更清晰的产品愿景和规格

外部反馈循环

用户 + 团队

数小时 - 数周

推动愿景演变的真实数据

Andrew Ng 按时间给了我们一张地图:三个以不同速度运行的循环。Addy Osmani 按抽象层次绘制了第二张地图:提示 -> 上下文 -> 工具集 -> 循环。

kaize - inline image

Loop Engineering 处于顶层,并增加了三个工具集没有的动词。它按计时器运行生成助手,并且自我供给——今天的输出成为明天的输入。最后这一点使其成为一个循环,而不是将同一个任务运行 N 次。

最重要的直觉: 同一个错误在每个层级造成的代价不同:

层级

关注点

核心问题

Prompt Engineering

编写提示词

对模型说什么

Context Engineering

窗口中加载了什么

拉取、总结、清理什么

Harness Engineering

部署单次运行

哪些工具、哪些操作、什么算完成

Loop Engineering

在工具集之上进行调度

如何让它自己运行,一轮又一轮

循环的设计本质上是一个让"运行轮次"数量变大的机器。从此刻起的每一个决策——评估器、人工关卡、Token 上限、状态文件——都是为了缩短从"错误发生"到"有人看到它"之间的距离。

一轮内部发生了什么:

"循环"常被误读为"原地打转"。并非如此,每一轮都有 5 个具体的步骤。砍掉任何一个,循环要么无法运行——要么运行了却毫无进展。

kaize - inline image
  1. 验证是那个说"不"的步骤。
  2. 调度是那个将本轮输入到下一轮的步骤。

这正是它成为循环的关键。

映射到我们即将构建的项目上: 一个面向小团队的晨间分类循环:

动作

它做什么

在分类循环中

发现

弄清楚这一轮该做什么

一个技能读取 CI / issues / 最近的提交

交接

分派任务,并隔离它

每个发现都有其自己的工作目录

验证

换另一个 Agent 来说不

第二个子 Agent 作为审阅者,检查测试和技能

持久化

将状态写入对话之外

PR + Linear ticket + ./state/triage.md

调度

让它一轮接一轮地运行

GitHub Actions 定时任务在 06:00 触发

下面的 5 课中,每一课都会成长出恰好一个步骤。

五课 = 一个完整的 Loop。

我们从第一个开始:发现, 因为如果 Loop 无法判断今天什么事值得做,之后的一切都无关紧要。

第 1 课 发现:编写一个晨间分类 SKILL.md

技能,而不是一堵提示墙。

想象一下,周一早上 6 点,你的笔记本电脑是合上的,GitHub Action 醒来并触发 claude --skill morning-triage:它读取什么,它判断为可操作的是什么,它把结果写到哪里——所有这些都存在于你即将编写的 SKILL.md 中。

Osmani 的关键句: 自动化应该触发一个技能——写下来、可更新的项目知识,而不是一堵粘贴到 cron 任务里的提示墙。粘贴的提示只会烂在那里,因为没有人会回去更新藏在调度里的东西。

你这节课的任务: 在 .claude/skills/morning-triage/SKILL.md 编写一个技能,它可以自己读取三个数据源(CI / issues / commits),自己决定什么值得做,将结果写入 ./state/triage.md——并且有一个明确的停止部分,详细说明这个循环不被允许做什么。

这是你的起点——改三个地方就是你的了:

json
1# .claude/skills/morning-triage/SKILL.md
2---
3name: morning-triage
4trigger: invoked by daily automation
5---
6
7## Read (发现输入——换成你仓库实际拥有的数据源)
8- 自上次运行以来失败的 CI 运行 # gh run list --status failure
9- 过去 24 小时内打开的 issues # gh issue list --search "created:>=…"
10- 自昨天以来合并的 commits # git log --since=yesterday
11- 之前的 ./state/triage.md # "昨天 → 今天"的交接
12
13## Judge (上限——让循环自己选择,不要给它一个列表)
14对于每个候选,判断:
15- 它现在就可以操作,还是噪音?
16- 它会阻塞发布吗?→ 优先级 = P0
17- 是否已经在跟踪中?→ 跳过
18只保留今天值得创建一个工作目录的内容。
19
20## Write (持久化——落地为 markdown,而不是聊天)
21追加到 ./state/triage.md:
22| 发现 | 来源 | 优先级 | 状态 |
23提交文件,这样明天就可以读取它。
24
25## Handoff (为第 2 课做准备)
26对于每个保留的发现,输出一个任务行:
27 worktree=fix/<slug>
28 goal=<stop-condition>
29
30## Stop (唯一非模板化的部分——在这里仔细思考)
31永不合并。永不删除。
32任何你不太确定的内容 → 放入 ./inbox/ 供人类处理。
33不是 PR。

这里有三部分做了真正的工作:

Read: 这是 Discovery 的输入——列出具体的数据源,精确到实际命令。记住,"读取上下文"不是一个数据源,但 gh run list --status failure 是一个数据源。

Judge: 这是整个循环的上限。循环自己判断"可操作 vs 噪音"——它不会从你那里接收一个列表,因为如果你每天早上还在给它一个列表,你自动化了的部分,但没有自动化选择的部分。而选择通常是最昂贵的步骤。无论你的数据源多好,如果这个部分选错了垃圾,其他四个步骤就是在无价值的工作上白费力气。

Stop: 这个文件中唯一不是模板的部分。循环会做技能说的一切,并且做技能说的事。如果你不写"永不合并"——总有一天它会为你合并。

明确红线:永不合并,永不删除,永不推送到 main 分支,任何不确定的内容都放入 ./inbox/ 供人类处理,而不是进入 PR。

还有一件值得知道的事: 同样的技能结构也适用于晨间分类之外:将 Read 部分换成"昨天的访问日志 + 新问题 + 当前部署版本",它就变成了晚间站点健康循环。在 Judge 部分加一行——"这是对昨天的回归吗?"——它就能进行跨日比较。

这节课预防的疾病——盲目循环:

最常见的"看起来像循环但实际不是":循环在运行,但你每天早上仍然在分配工作("嘿,看看这三个 bug")。那是执行自动化,而不是发现自动化。

症状: 循环从不给你惊喜,因为它只做你已经知道需要做的事情。

修复: 将 Judge 放在技能内部,让它自己选择今天的工作。

在继续之前,回答这三个问题:

1/ 你的 Stop 部分有多少行?

如果只是"永不合并"——再加一行:这个循环还应该为你做什么? (永不关闭 issues / 永不推送到 main / 一次最多处理 3 个发现...)

2/ 将 Read 的第一行重写为在你的项目中实际运行的命令

3/ 如果这个技能一年不变——会发生什么?

**现在比较:粘贴在 cron 任务里的一堵提示墙一年不变——会发生什么?

第 2 课 交接:分发工作,并隔离它

第 1 课的技能刚刚运行完毕。./state/triage.md 现在包含 5 个可操作的发现。最自然的下一步想法是——同时启动 5 个 Agent,加快速度。但这一步恰恰是你培养出混乱循环的方式:多个 Agent 在同一个目录中编辑同一个文件,合并日变成了考古挖掘。

解药是一行命令: 每个发现都有自己的工作目录。

git worktree 是一个内置的 git 机制:一个仓库,多个独立的工作目录,每个都检出到自己的分支。git worktree add ../fix-auth fix/auth 在 ../fix-auth 打开一个完全隔离的工作区。构建、编辑、测试——任何操作都不会影响其他人。每个发现一个工作目录 = Agent 物理上分离。

你这节课的任务: 编写一个脚本,读取 ./state/triage.md 中的每个发现,为每个发现打开一个隔离的工作目录(命名为 fix/<slug>),派一个 Agent 在该目录内起草修复——并设置一个并行上限,以免无限制地生成。

json
1# 读取 state/triage.md,每个发现一个工作目录
2# 更改:MAX_PARALLEL 为你的机器/账单能处理的数量
3MAX_PARALLEL=3
4
5# 更改:解析方式以匹配你实际的 triage.md 列
6findings=$(awk -F'|' 'NR>2 && $5 ~ /open/ {print $2}' \
7 state/triage.md | head -n $MAX_PARALLEL)
8
9for finding in $findings; do
10 slug=$(echo "$finding" | tr ' ' '-' | tr -cd '[:alnum:]-')
11
12 # 关键行:每个 Agent 的物理隔离
13 git worktree add ../wt-$slug -b fix/$slug
14
15 # 更改:goal 是第 3 课评估器将读取的停止条件
16 claude --worktree ../wt-$slug \
17 --goal "tests in tests/$slug pass and lint is clean" \
18 "draft a fix for: $finding" &
19done
20
21wait # 让所有并行的 Agent 完成
22
23# 关闭规则:Agent 从不自动合并——工作目录等待
24# 第 5 课的人工关卡
25echo "所有工作目录已准备好供审阅:"
26git worktree list

这里重要的规则: 永远不要在 main 分支上工作。

Main 是给人类审阅用的降落跑道——而不是 Agent 的工作台。所有 Agent 都在 fix/* 工作目录上工作。

限制并行度: 无限制地生成是通往 Token 爆炸的大门(第 5 课会回到这一点)。根据一个诚实的问题选择 MAX_PARALLEL:不是"我的机器能跑多少",而是"我实际能审阅多少个 PR"。

合并是人类的决定: Stripe 每周通过这样的循环处理 1000+ 个 PR,而人类仍然审阅每一个。人类并没有离开流程——他们换了岗位:从编写代码变成审阅代码。

还有一个来自 Stripe 案例值得借鉴的细节:他们反直觉地使用了更小的模型,配合严格的确定性门控,而不是使用可用的最大模型。

教训: 对于循环工作,严格的验证门控胜过了原始模型能力。

这节课预防的疾病——混乱循环:

单 Agent 循环看起来完全健康。问题只在你并行化那天才出现——五个 Agent 运行,五个 diff 在同一个文件中冲突,合并变成混乱。

修复: 每个任务都有自己的工作目录,绝不让 Agent 共享工作目录。

在继续之前,回答这三个问题:

1/ 你将 MAX_PARALLEL 设置成了多少——为什么?

**(钱?机器?还是"我能如实审阅多少个 PR"?)

2/ 如果一个工作目录崩溃——Agent 退出而未做任何改动

**明天的状态文件应该如何记录它?

3/ Stripe 使用了更小的模型 + 严格的确定性门控

**而不是最大的模型,这个选择对你自己的循环意味着什么?

第 3 课 验证:安装一扇说"不"的门(最重的一课)

这是循环工程中最难的一步,值得比其他任何课都投入更多精力。

Anthropic 的工程师 Prithvi Rajasekaran 在开发长时间运行的 Agent 应用时注意到:让一个 Agent 评估它刚刚编写的代码,它几乎总是会自我表扬——即使人类一眼就能看出它很平庸。

这不是模型笨:给自己的作业打分在结构上就是有问题的。 到代码写完时,Agent 的上下文中塞满了自我合理化的链条——它为什么这样写。看着自己的输出,它看到的不是结果——它看到的是为结果辩护的理由。

将其放入循环中,它会放大成一场灾难: 每一轮的"这个够好吗?"都由刚刚编写它的 Agent 来回答。每一轮都对自己点头。运行时间越长,它离真正质量的距离就越远。这就是点头循环——而且它是列表中代价最高的疾病。

Rajasekaran 的解决方案,用他自己的话说:

"调整一个独立的评估器,使其持怀疑态度,远比让一个生成器批评自己的工作要容易处理得多。"

这是一个结构性问题,而不是措辞问题。你不能要求作者跳出自己的脑袋,但你可以引入一个完全不同的 Agent,带着完全不同的指令,从零开始审视代码——没有附加任何自我合理化的链条。

这个类比来自 GAN:一个网络构建,另一个攻击:制造者和检查者。同样古老的银行规则:汇款的人和批准汇款的人必须是不同的人。

你这节课的任务——写两样东西:

1) 一个评估器系统提示,位于 .claude/agents/reviewer.md,包含五个部分:ROLE + ASSUME + CHECK + USE + VERDICT。

2) 一个 /goal 命令,声明这个循环的停止条件——由一个独立的、全新的模型来判断,而不是由生成器来判断。

json
1# .claude/agents/reviewer.md (更改 3 处)
2ROLE: 对抗性代码审阅者。
3ASSUME: 这段代码在证明其正确之前是 BROKEN(有缺陷的)。
4不要赞美。找出失败之处。
5
6# 更改:根据你项目真实的失败模式对 CHECK 排序,
7# 首先是可运行的检查
8CHECK, 按顺序:
9 1. 它能运行吗?(执行,而不是阅读)
10 2. 测试:运行它们,粘贴真实输出。
11 3. 作者跳过的边缘情况。
12 4. 行为与工单匹配吗?
13
14# 更改:使用哪个 MCP 取决于你在测试什么——
15# 前端 → Playwright,API → curl/httpie
16USE Playwright MCP: 打开页面,点击,
17截图,检查 DOM。
18判断行为,而不是意图。
19
20VERDICT: 仅当每个检查都成立时才 PASS。
21 否则 REJECT + 列出每个原因。
json
1# 停止条件——由一个全新的小模型判断,而不是生成器
2# 更改:替换为你发现的可验证的实际条件
3/goal all tests in test/auth pass and the lint step is clean
4
5# 注意:/goal 和 /loop 不是一回事
6# /loop = 按间隔重新运行(没有停止判断——危险)
7# /goal = 运行直到一个全新的模型判断条件为真

两个 Agent,理想情况下是两个模型: "同一个模型,不同的提示"通常保留了相同的盲点。如果可能,也换掉模型。

默认怀疑: 评估器的基线是 ASSUME BROKEN——必须通过检查来赢得信任,而不是默认授予。

判断行为,而不是意图: "这个 JSX 看起来没问题"和"我点击了按钮,页面导航了,这是截图"是两个不同的宇宙。评估器必须行动——运行测试,点击页面,而不是阅读代码并凭感觉。

带理由拒绝: 没有列出理由的 REJECT 不是拒绝——生成器不知道要修复什么。

还有一个试金石: 你的评估器在过去 5+ 轮中是否实际拒绝过某些东西?一个在数百轮中从未说过"不"的循环,在任何真实工作负载上在统计上都是不可能的。所以它证明了评估器没有在工作。

这节课预防的疾病——点头循环:

循环运行,Agent 编写代码,同一个 Agent 说"看起来不错"。每一轮都自我批准,以机器速度堆叠看起来合理的错误。修复: 整节课:结构性分离 + 默认怀疑 + 通过行动判断 + 第三方停止条件。

在继续之前,回答这三个问题:

1/ 你的评估器是同一个模型使用不同的提示,还是一个不同的模型?为什么?

2/ 将"判断行为,而不是意图"翻译成你项目的

具体句子:"我的评估器不阅读代码,它运行 _____,并且只有在看到 _____ 时才 PASS"

3/ 一个"足够接近"的新模型有多大可能错误地通过你的 /goal 条件?

如果你最可怕的失败模式是 X——现在就为停止条件添加一个检查 X 的项目

第 4 课 持久化:让今天的工作延续到明天

第 3 课结束了。你的评估器判断了三个发现——两个 PASS,一个 REJECT。问题:这三个判定结果现在在哪里?

如果答案是"在上下文窗口中"——那么明天早上 6 点,循环醒来时上下文已刷新,什么都不记得。它从零开始重新分类,撞上昨天未修复的 REJECT,也许又派另一个 Agent 去修复它,也许完全错过它,也许掉进了同一个坑里。

这就是失忆循环——每一天都从同一个起点开始。

解药在一行中: 记忆是写入磁盘的状态,而非上下文。markdown 文件、Linear 项目、数据库——一旦被写下来,Agent 忘记得多彻底都无关紧要。

这两者经常被混淆,所以明确区别:

上下文 = 这一轮 Agent 看到的窗口 -> 每次刷新时清除。

记忆 = 跨轮次和天数持续存在的状态 -> 写入磁盘(markdown / 数据库 / 项目)。

一个循环通过记忆 连接今天 -> 明天。

你这节课的任务:

  1. 创建 ./state/triage.md,至少包含 4 列(发现 / 来源 / 优先级 / 状态)——第 1 课的技能写入它,第 2 课的工作目录读取它。
  2. 连接一个连接器(MCP):PASS 判定自动打开 PR,REJECT 判定自动更新工单。
  3. 验证明天的循环是否能够真正读取今天的状态。
json
1# ./state/triage.md (循环的记忆——根据需要更改)
2
3| 发现 | 来源 | 优先级 | 状态 |
4|-----------------|--------------|--------|------------|
5| auth test flaky | CI #4821 | P0 | 修复中 |
6| null deref | issue 92 | P1 | PR 已打开 |
7| stale dep | commit a3f9c | P2 | 待处理 |
8
9# 根据需要扩展列:last_seen, retry_count, assigned_to
10# 此文件必须提交到仓库——
11# 否则明天的云运行器无法看到它
json
1// .mcp/connectors.json — 将循环接入外部世界
2{
3 "github_pr": {
4 "type": "mcp",
5 "on_event": "evaluator.verdict.PASS",
6 "action": "open_pr",
7 "branch": "fix/${slug}",
8 "auto_merge": false // ← 必须为 false。人工关卡 = 第 5 课
9 },
10 "linear": {
11 "type": "mcp",
12 "on_event": "evaluator.verdict.REJECT",
13 "action": "update_ticket",
14 "fields": { "status": "Blocked", "reason": "${verdict.reasons}" }
15 }
16}

为什么每列都很重要: 没有来源 -> 你无法审计发现来自何处。没有优先级 -> 你无法排序工作。没有状态 -> 明天无法接棒。

而这节课的验证是唯一一个你不能伪装的:等到明天。 醒来,检查——待处理箱还在吗?未完成的发现仍然保持它们的状态吗?早上 6 点的运行是接着昨天停下的地方开始,还是重新开始?这一点你通过运行来发现,而不是通过推理。

伴随着持久化而来的一个安静的习惯——总是阅读一个样本。

一旦持久化工作,一个陷阱就打开了:循环以高速发布 PR,你感觉效率很高——而你对代码库的心理地图却在不知不觉中过时。规则:不要什么都读(那会违背循环的目的),但每天阅读一个样本 PR,并强迫自己解释循环改变了什么以及为什么。

当你无法解释它的那一刻——你的地图已经落后了。在一个平静的早晨修复它只需要十分钟。在生产事故期间修复它代价要大得多。

这节课预防的疾病——失忆循环:

循环找到了好的工作,完成了它,然后就忘了它做过——因为结果只存在于已刷新的上下文中。下一轮它重新发现同样的工作,或者更糟——重新做一遍并覆盖了第一次的尝试。

症状: 没有累积的进展,每一天都从同一个地方开始。修复: 磁盘上的状态文件。

在继续之前,回答这三个问题:

1/ 将"记忆 ≠ 上下文"翻译成一句来自你自己项目的

具体句子:"我的 _____ 是记忆,我的 _____ 是上下文"

2/ 明天的循环醒来——它会先读取 state.md 中的哪个

**列来决定它的第一步?你命名那个列正确了吗,还是只是为它埋了一个地雷?

3/ 习惯:你会在每天的什么时间阅读你的一个

**样本 PR?现在就设置日历提醒——这是你与自己的约定。

第 5 课 调度:让它真实运行,保障安全,保留一扇门

第 1-4 课已安装,但只要你停止按下按钮,循环就会停止。最后一步是一个动词:将它挂在一个触发器上。

但这节课最危险的部分并不是搞错 cron。而是搞对 cron ——因为那时循环才真正独自运行,一个 bug 就能让它整夜旋转,助手生成助手,直到你醒来看到一个你认不出来的账单。

所以这节课实际上做三件事:挂上触发器,设置上限,保留一个人工入口。

首先——选择你的调度器:

json
1Cloud (GH Actions) Desktop scheduled /loop (in CC)
2运行位置 cloud your machine your machine
3机器开着? not needed needed needed
4会话打开? not needed not needed needed
5最小间隔 1 hour 1 minute 1 minute
6能看到本地文件? no yes yes

决定性问题: 这项工作能否离开你的笔记本?

json
1"检查我的本地开发服务器,每分钟一次"
2 → 必须是本地 + /loop (cloud 的 1 小时间隔无法触及)
3
4"在凌晨 3 点扫描 GitHub issues 并打开 PRs"
5 → 必须是 cloud (你的笔记本是合上的——循环不能
6 依赖它)

而整个实践中最阴险的自我欺骗:将本地定时器视为"在你睡觉时运行"。它不是。它只是"当我在附近时多运行几轮"。

现在关于上限——因为无上限的循环会自我吞噬:

即使一个中等规模的工程循环也容易每天消耗 200 万个 Token。在 Anthropic 的 Max Token 设置为 10,000 的情况下,它用在一个循环中的速度大约是直接使用的 3 倍——因为每一轮都处理相同的系统提示 + 加载的新上下文。这意味着一个每天运行 4 次的 $200/百万 Token 模型的循环每月将产生约 $120 的 API 成本——如果 Agent 开始互相生成而无限制的话,则可能会远高于此。

设置三个上限:

json
1# 限制你的循环——修改这三个值以匹配你实际的预算
2
3MAX_TOKENS_PER_RUN=1000000 # 这是你的美元上限
4MAX_TURNS_PER_DAY=120 # 大约每天 4 小时
5MAX_CONCURRENCY=3 # 第 2 课——在生成时检查
6
7# 一个循环有无限 Token + 无限轮次 + 无人工门控 + 自我生成 =
8# 你醒来发现无法解释的账单
9# 在启用之前设置这些

最后——保留那个人工入口。 循环的悖论:你移除自己作为操作员,但你保留了作为评审员的位置。第 1 课停止部分:永不合并。第 2 课规则:Agent 从不推送。第 4 课连接器:auto_merge 为 false。这三者都指向同一个时刻。

那扇门的样子:

json
1# 保留一扇人类之门——循环从不相交
2
3# 更改:你的分支和提交模式
4git fetch origin
5git log origin/main..origin/ready-for-human/* \
6 --oneline -10
7
8# 你的审阅窗口
9echo "打开以供审阅的 PRs:"
10gh pr list --label loop-agent
11
12# 审阅一个 PR。合并它或拒绝它。
13# 不要批量合并全部。一次一个,带理由。

这节课预防的疾病——失控循环:

循环运行且不可见,直到账单到达。没有上限,没有门控,没有人工审阅——它只是运行,生成,消费。

症状: 你发现它是因为 Token 用完了,而不是因为交付了工作。修复: 这整节课——调度 + 上限 + 人工门控。

在继续之前,回答这三个问题:

1/ 在你的笔记本上本地调度,与在 CI 中由云触发相比,你更倾向于哪一种——以及为什么?

2/ 写下你将放入循环的 MAX_TOKENS_PER_RUN、MAX_TURNS_PER_DAY 和 MAX_CONCURRENCY 的准确数字。不要只是复制——选择一个数字并说出它来自哪里。

3/ 第 3 课的门(评估器),第 4 课的门(连接器),第 5 课的门(合并决策)。在你的循环中,你信任哪一扇门来做最重的验证工作?

结束:现在运行它

五课完成。从一个技能文件开始,到挂在调度器上结束。这就是一个完整的循环——如果你愿意,可以称之为"产品"。

这是起点,不是终点。Loop Engineering 是一种实践——你从早晨的分类循环中学到的东西直接适用于晚间站点健康循环、周末数据收集循环、月底报告循环。结构保持不变:读取、判断、写入、交接给下一个,挂在一个定时器上。

接下来做什么:

现在你有了一个循环——运行它一周,然后回来修改你的 Stop 部分。运行一个月,回来增加第二个数据源。一旦它稳定运行,你就准备好将循环用作 API ——将同样的模式应用于其他项目。

现在不要优化。运行。

附录:完整循环(复制粘贴)

以下是将所有五课组合成一个单一、可运行的循环。将其放入你项目的根目录 —— 更改带 # 标记的值 —— 并设置一个 GitHub Actions 定时任务来触发它。

json
1#!/n/env bash
2# morning-triage-loop — 完整循环,5 课合而为一
3# 放入项目根目录:.github/scripts/morning-triage.sh
4# 更改所有标记了 CHANGE 的值
5
6set -euo pipefail
7
8# ─── 第 5 课:上限 ─────────────────────────────────────
9MAX_PARALLEL=3 # CHANGE: 你的机器/票据可处理的数量
10MAX_TOKENS_PER_RUN=1000000 # CHANGE: 你的 Token 预算
11
12# ─── 第 1 课:发现 ─────────────────────────────────────
13echo "=== 发现:检查 CI、issues、commits ==="
14claude --skill morning-triage # 读取 ./state/triage.md
15
16# ─── 第 2 课:交接——每个发现一个工作目录 ──────
17echo "=== 交接:创建工作目录 ==="
18findings=$(awk -F'|' 'NR>2 && $5 ~ /open/ {print $2}' \
19 state/triage.md | head -n $MAX_PARALLEL)
20
21for finding in $findings; do
22 slug=$(echo "$finding" | tr ' ' '-' | tr -cd '[:alnum:]-')
23 git worktree add ../wt-$slug -b fix/$slug
24 claude --worktree ../wt-$slug \
25 --goal "all relevant tests pass and lint is clean" \
26 "Draft a fix for: $finding" &
27done
28wait
29
30# ─── 第 3 课:验证——使用独立评估器 ──────────
31echo "=== 验证:使用独立评估器 ==="
32for finding in $findings; do
33 slug=$(echo "$finding" | tr ' ' '-' | tr -cd '[:alnum:]-')
34 veridct=$(claude --agent reviewer.md \
35 --worktree ../wt-$slug --eval)
36 # 记录评估器判定
37 echo "$finding: $veridct" >> ./state/veridcts.log
38done
39
40# ─── 第 4 课:持久化——打开 PR 或更新工单 ──────
41echo "=== 持久化:打开 PR / 更新工单 ==="
42while IFS=':' read -r finding veridct; do
43 slug=$(echo "$finding" | tr ' ' '-' | tr -cd '[:alnum:]-')
44 if [ "$veridct" == "PASS" ]; then
45 gh pr create --base main --head fix/$slug \
46 --title "Fix: $finding" \
47 --body "由晨间分类循环自动创建"
48 else
49 # CHANGE: 集成你的项目管理工具
50 echo "REJECT — 需要人工审阅: $finding"
51 fi
52done < ./state/veridcts.log
53
54echo "=== 循环完成。所有 PR 等待第 5 课的人工门控 ==="

以及它的配套 GitHub Actions 工作流:

json
1# .github/workflows/morning-triage.yml
2# CHANGE: 调度时间以匹配你团队的时区
3name: morning-triage
4on:
5 schedule:
6 - cron: '0 6 * * 1-5' # 工作日 06:00 UTC
7 workflow_dispatch: # 允许手动触发
8
9jobs:
10 triage:
11 runs-on: ubuntu-latest
12 steps:
13 - uses: actions/checkout@v4
14 with:
15 fetch-depth: 0
16 - name: Run morning triage loop
17 run: .github/scripts/morning-triage.sh
18 env:
19 GH_TOKEN: ${{ secrets.GH_TOKEN }}
20 ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

最终提醒: 如果出现意外,检查这些顺序:

  1. 第 3 课:评估器是否实际在说"不"?如果没有,你有一个点头循环,你的评估器是假的。从第 3 课重新开始。
  1. 第 1 课:Judge 是否选择了你的技能说它应该选择的内容?检查 ./state/triage.md。如果不对,你的提示需要修复。
  1. 第 2 课:工作目录是否被隔离?检查 git worktree list。如果它们共享目录,你有一个混乱循环。
  1. 第 4 课:状态是否被写下来?等待明天。检查明天的运行是否从同一个 ./state/triage.md 开始,而不是从空白开始。
  1. 第 5 课:你保留了那扇门吗?在合并之前等待人类。

如果所有检查都通过——你已经构建了一个循环。如果不是——逐课回退,使用那里的测试问题。

现在让它运行。

本课任务:

  1. 编写 .github/workflows/triage.yml,配置一个 cron 触发器来执行 morning-triage 技能。
  2. 设置三个上限:单次运行预算、每日预算、最大重试次数。
  3. 在工作流末尾添加规则:PR 永不自动合并,任何不确定的内容都放入 ./inbox/
json
1# .github/workflows/triage.yml (需要修改 3 处)
2name: morning-triage
3
4on:
5 schedule:
6 - cron: '0 6 * * *' # 修改:你所在时区对应 UTC 时间 06:00
7
8jobs:
9 triage:
10 runs-on: ubuntu-latest
11 timeout-minutes: 45 # ← 单次运行上限 · 修改
12 permissions:
13 contents: write
14 pull-requests: write
15 steps:
16 - uses: actions/checkout@v4
17
18 - name: morning triage
19 env:
20 ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
21 DAILY_BUDGET_USD: '20' # ← 每日上限 · 修改
22 MAX_RETRIES: '3' # ← 重试上限 · 修改
23 run: |
24 claude --skill morning-triage \
25 --max-retries "$MAX_RETRIES" \
26 --daily-budget-usd "$DAILY_BUDGET_USD"
27
28 - name: 打开 PR(永不自动合并)
29 run: |
30 # PR 会自动打开。合并操作仍需人工处理。
31 gh pr create --draft --base main \
32 --title "[循环] $(date +%F) triage" \
33 --body-file ./state/triage.md
34 # 这里故意没有 `gh pr merge` 命令
35
36 - name: 将不确定内容路由到收件箱
37 run: |
38 ls ./inbox/ && echo "有项目等待人工审核"

让循环在六个月后依然活跃的三条规则:

先设上限再上线: 三个层级——单次运行超时、每日预算、最大重试次数——必须在首次自主运行 之前 设置好,而不是在看到第一张吓人的账单之后。Token 上限不是为了省钱。它是一个断路器,能把无限风险转化为有限风险。没有上限的循环,等于把支出权限外包给了自己的 bug。

举个例子:Uber 在四个月内烧光了 AI 预算后,将每位工程师每工具每月的花费上限设为 1,500 美元。你的单循环上限就是这个教训的细化版本。

留一扇门: PR 不自动合并。不确定的内容进入收件箱。你每天抽查一个样本。这扇门的存在,不是因为人类总会走进去,而是因为 门的存在,让你始终保有走进去的能力。 把每扇门都焊死,等到你需要进去的那天,你就已经丢了钥匙。

验证它真的能启动。 至少运行 3 次(试运行也算)。最常见的失败模式是:演示日惊艳全场,周四就彻底凉凉。"最后一次运行就是演示那天"——这是手动循环的诊断标志。

在 Claude Code 之外同样适用—— 这些能力并非绑定特定供应商,只是名称不同:

json
1能力 Claude Code Codex
2调度 /loop · worker 自动化标签页
3条件运行 /goal 自动化重跑 + 判断
4并行隔离 --worktree 后台 worktree
5子 Agent .claude/agents/ .codex/agents/
6外部连接 MCP + 插件 MCP 连接器
7显式技能 SKILL.md $skill-name
8离线运行 云例程 云(计划中)

问题从来不是"选哪个供应商",而是"这六项能力是否都已部署到位"。

本课要预防的病症——手动循环:

四个步骤构建得完美无缺,但没有 cron。这不是一个循环——这是一个你运行一次就忘了的脚本。

解决方案: 挂载任何不依赖你记忆的触发器。

上线前,先回答这三个问题:

1/ 你的三个上限具体是多少?

如果一个 bug 整夜运行——哪个上限会首先打破僵局?如果它们都做不到……这说明了什么?

2/ 你的"人类之门"长什么样?

(PR 审核 / 收件箱 / 每日样本 / 每周对账?)把它写下来 = 与自己签订一份契约。

3/ 最后一个问题: 六个月后,这个循环会让你变得更强(理解力、判断力、对系统的感知力)——还是让你变成一个你无法阅读的机器的看护者?你打算如何避免后者?

四种债务与保持工程师本色

你的循环已经构建完成:它能自动唤醒、寻找任务、自我批评、记忆,并安排自己的明天。

当循环运行时,有四样东西会悄悄累积——它们的共同特征是 没有一样会触发警报。

json
1验证债务 未拒绝的输出不断堆积
2理解退化 代码库在增长,你的心智地图却没有
3认知投降 "我不想再仔细审查了"
4Token 爆炸 一个 bug 整夜运行 → 你看到的是一张账单

它们相互滋养: 未验证的输出侵蚀你的理解 -> 被侵蚀的理解招致投降 -> 投降让循环运行更久、花费更多 -> 进而产生更多未验证的输出。

在循环首次独立运行之前,对照以下四点进行审计:

  • 我的评估器确实会拒绝——它不是装饰品。
  • 我每天阅读一个样本 PR,并能解释发生了什么变化。
  • 我保留了说"这是错的"的习惯。
  • 我的上限是在 首次运行之前 设置的,而不是在收到第一张账单之后。

而决定一切的关键在于:

同一个循环,由两个人构建,可能走向截然不同的结局。一个人用它来加速前进,同时仍然阅读代码、把握方向——循环放大了他们已有的判断力。

另一个人则用完全相同的循环,专门为了再也不去理解任何东西——六个月后,第一个人更加敏锐,第二个人则成了一个他无法阅读的机器的看护者。

Addy Osmani: "构建循环,但要像打算一直做工程师的人那样去构建——而不仅仅是那个按下启动键的人。"

循环让生成几乎零成本,真正稀缺的是判断力:哪个计划是对的,在哪一行停下来,哪个东西在运行但根本上是错的。循环不会贬低判断力——它会剥离一切不是判断力的东西,只留下判断力本身。

循环是一个忠实的放大器:

带来理解——它会放大理解。

带来懒惰——它会放大懒惰。

在构建之前,先决定你想放大什么。

敬请期待!

祝你好运 :)

二次创作

使用 YouMind 创作爆款文章

收集素材、拆解爆点、生成视觉资产、撰写内容,并在一个 AI 工作空间里完成分发。

了解 YouMind
写给创作者

把你的 Markdown 变成干净的 𝕏 文章

图片上传、表格、代码块,往 𝕏 上手动重排太痛苦。YouMind 把整篇 Markdown 一键转成干净、可直接发布的 𝕏 文章草稿。

试试 Markdown 转 𝕏

更多可拆解样本

近期爆款文章

探索更多爆款文章