我已决定将这篇文章专门用于介绍如何创建你自己的可编程 Agent,让它为你完成工作,这样你就不必每天早上打开电脑,一遍又一遍地给它下达指令。
它会自己醒来,自己寻找任务,自己发现问题,并将结果保存并呈现在你的桌面上,供你审阅。
前言
Loop 的想法并非凭空而来——在 2026 年 6 月的同一个星期里,三位行业专家几乎同时表达了同一个想法:是时候开始开发这个层级了。
一周后,Andrew Ng 将这次讨论置于"从 0 到 1 构建产品"的更广泛背景下,提供了更全面的图景。
这篇文章从那个故事开始,然后带你从头开始构建一个最小但完整的循环:5 个步骤、6 个部分、一扇写着"不"的门,以及一扇人类之门——全部在你自己的机器上运行,并且可以在任何软件项目上重复使用。

来源:华数小册、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 能够长期迭代工作以创建软件的关键部分。

Andrew Ng 根据时间尺度将三个循环从内到外排列:最内层的循环速度最快,最外层的循环速度最慢。此外,最外层循环的输入来自于最内层循环的输出:
循环 | 谁来运行 | 循环时长 | 向外层输出的内容 |
|---|---|---|---|
Agent 编码循环 | Agent 本身 | 秒 - 分钟 | 一个可运行的版本 |
开发者反馈循环 | 人类 + Agent | 数十分钟 - 数小时 | 更清晰的产品愿景和规格 |
外部反馈循环 | 用户 + 团队 | 数小时 - 数周 | 推动愿景演变的真实数据 |
Andrew Ng 按时间给了我们一张地图:三个以不同速度运行的循环。Addy Osmani 按抽象层次绘制了第二张地图:提示 -> 上下文 -> 工具集 -> 循环。

Loop Engineering 处于顶层,并增加了三个工具集没有的动词。它按计时器运行,生成助手,并且自我供给——今天的输出成为明天的输入。最后这一点使其成为一个循环,而不是将同一个任务运行 N 次。
最重要的直觉: 同一个错误在每个层级造成的代价不同:
层级 | 关注点 | 核心问题 |
|---|---|---|
Prompt Engineering | 编写提示词 | 对模型说什么 |
Context Engineering | 窗口中加载了什么 | 拉取、总结、清理什么 |
Harness Engineering | 部署单次运行 | 哪些工具、哪些操作、什么算完成 |
Loop Engineering | 在工具集之上进行调度 | 如何让它自己运行,一轮又一轮 |
循环的设计本质上是一个让"运行轮次"数量变大的机器。从此刻起的每一个决策——评估器、人工关卡、Token 上限、状态文件——都是为了缩短从"错误发生"到"有人看到它"之间的距离。
一轮内部发生了什么:
"循环"常被误读为"原地打转"。并非如此,每一轮都有 5 个具体的步骤。砍掉任何一个,循环要么无法运行——要么运行了却毫无进展。

- 验证是那个说"不"的步骤。
- 调度是那个将本轮输入到下一轮的步骤。
这正是它成为循环的关键。
映射到我们即将构建的项目上: 一个面向小团队的晨间分类循环:
动作 | 它做什么 | 在分类循环中 |
|---|---|---|
发现 | 弄清楚这一轮该做什么 | 一个技能读取 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——并且有一个明确的停止部分,详细说明这个循环不被允许做什么。
这是你的起点——改三个地方就是你的了:
1# .claude/skills/morning-triage/SKILL.md2---3name: morning-triage4trigger: invoked by daily automation5---67## Read (发现输入——换成你仓库实际拥有的数据源)8- 自上次运行以来失败的 CI 运行 # gh run list --status failure9- 过去 24 小时内打开的 issues # gh issue list --search "created:>=…"10- 自昨天以来合并的 commits # git log --since=yesterday11- 之前的 ./state/triage.md # "昨天 → 今天"的交接1213## Judge (上限——让循环自己选择,不要给它一个列表)14对于每个候选,判断:15- 它现在就可以操作,还是噪音?16- 它会阻塞发布吗?→ 优先级 = P017- 是否已经在跟踪中?→ 跳过18只保留今天值得创建一个工作目录的内容。1920## Write (持久化——落地为 markdown,而不是聊天)21追加到 ./state/triage.md:22| 发现 | 来源 | 优先级 | 状态 |23提交文件,这样明天就可以读取它。2425## Handoff (为第 2 课做准备)26对于每个保留的发现,输出一个任务行:27 worktree=fix/<slug>28 goal=<stop-condition>2930## 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 在该目录内起草修复——并设置一个并行上限,以免无限制地生成。
1# 读取 state/triage.md,每个发现一个工作目录2# 更改:MAX_PARALLEL 为你的机器/账单能处理的数量3MAX_PARALLEL=345# 更改:解析方式以匹配你实际的 triage.md 列6findings=$(awk -F'|' 'NR>2 && $5 ~ /open/ {print $2}' \7 state/triage.md | head -n $MAX_PARALLEL)89for finding in $findings; do10 slug=$(echo "$finding" | tr ' ' '-' | tr -cd '[:alnum:]-')1112 # 关键行:每个 Agent 的物理隔离13 git worktree add ../wt-$slug -b fix/$slug1415 # 更改:goal 是第 3 课评估器将读取的停止条件16 claude --worktree ../wt-$slug \17 --goal "tests in tests/$slug pass and lint is clean" \18 "draft a fix for: $finding" &19done2021wait # 让所有并行的 Agent 完成2223# 关闭规则: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 命令,声明这个循环的停止条件——由一个独立的、全新的模型来判断,而不是由生成器来判断。
1# .claude/agents/reviewer.md (更改 3 处)2ROLE: 对抗性代码审阅者。3ASSUME: 这段代码在证明其正确之前是 BROKEN(有缺陷的)。4不要赞美。找出失败之处。56# 更改:根据你项目真实的失败模式对 CHECK 排序,7# 首先是可运行的检查8CHECK, 按顺序:9 1. 它能运行吗?(执行,而不是阅读)10 2. 测试:运行它们,粘贴真实输出。11 3. 作者跳过的边缘情况。12 4. 行为与工单匹配吗?1314# 更改:使用哪个 MCP 取决于你在测试什么——15# 前端 → Playwright,API → curl/httpie16USE Playwright MCP: 打开页面,点击,17截图,检查 DOM。18判断行为,而不是意图。1920VERDICT: 仅当每个检查都成立时才 PASS。21 否则 REJECT + 列出每个原因。
1# 停止条件——由一个全新的小模型判断,而不是生成器2# 更改:替换为你发现的可验证的实际条件3/goal all tests in test/auth pass and the lint step is clean45# 注意:/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 / 数据库 / 项目)。
一个循环通过记忆 连接今天 -> 明天。
你这节课的任务:
- 创建 ./state/triage.md,至少包含 4 列(发现 / 来源 / 优先级 / 状态)——第 1 课的技能写入它,第 2 课的工作目录读取它。
- 连接一个连接器(MCP):PASS 判定自动打开 PR,REJECT 判定自动更新工单。
- 验证明天的循环是否能够真正读取今天的状态。
1# ./state/triage.md (循环的记忆——根据需要更改)23| 发现 | 来源 | 优先级 | 状态 |4|-----------------|--------------|--------|------------|5| auth test flaky | CI #4821 | P0 | 修复中 |6| null deref | issue 92 | P1 | PR 已打开 |7| stale dep | commit a3f9c | P2 | 待处理 |89# 根据需要扩展列:last_seen, retry_count, assigned_to10# 此文件必须提交到仓库——11# 否则明天的云运行器无法看到它
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 就能让它整夜旋转,助手生成助手,直到你醒来看到一个你认不出来的账单。
所以这节课实际上做三件事:挂上触发器,设置上限,保留一个人工入口。
首先——选择你的调度器:
1Cloud (GH Actions) Desktop scheduled /loop (in CC)2运行位置 cloud your machine your machine3机器开着? not needed needed needed4会话打开? not needed not needed needed5最小间隔 1 hour 1 minute 1 minute6能看到本地文件? no yes yes
决定性问题: 这项工作能否离开你的笔记本?
1"检查我的本地开发服务器,每分钟一次"2 → 必须是本地 + /loop (cloud 的 1 小时间隔无法触及)34"在凌晨 3 点扫描 GitHub issues 并打开 PRs"5 → 必须是 cloud (你的笔记本是合上的——循环不能6 依赖它)
而整个实践中最阴险的自我欺骗:将本地定时器视为"在你睡觉时运行"。它不是。它只是"当我在附近时多运行几轮"。
现在关于上限——因为无上限的循环会自我吞噬:
即使一个中等规模的工程循环也容易每天消耗 200 万个 Token。在 Anthropic 的 Max Token 设置为 10,000 的情况下,它用在一个循环中的速度大约是直接使用的 3 倍——因为每一轮都处理相同的系统提示 + 加载的新上下文。这意味着一个每天运行 4 次的 $200/百万 Token 模型的循环每月将产生约 $120 的 API 成本——如果 Agent 开始互相生成而无限制的话,则可能会远高于此。
设置三个上限:
1# 限制你的循环——修改这三个值以匹配你实际的预算23MAX_TOKENS_PER_RUN=1000000 # 这是你的美元上限4MAX_TURNS_PER_DAY=120 # 大约每天 4 小时5MAX_CONCURRENCY=3 # 第 2 课——在生成时检查67# 一个循环有无限 Token + 无限轮次 + 无人工门控 + 自我生成 =8# 你醒来发现无法解释的账单9# 在启用之前设置这些
最后——保留那个人工入口。 循环的悖论:你移除自己作为操作员,但你保留了作为评审员的位置。第 1 课停止部分:永不合并。第 2 课规则:Agent 从不推送。第 4 课连接器:auto_merge 为 false。这三者都指向同一个时刻。
那扇门的样子:
1# 保留一扇人类之门——循环从不相交23# 更改:你的分支和提交模式4git fetch origin5git log origin/main..origin/ready-for-human/* \6 --oneline -1078# 你的审阅窗口9echo "打开以供审阅的 PRs:"10gh pr list --label loop-agent1112# 审阅一个 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 定时任务来触发它。
1#!/n/env bash2# morning-triage-loop — 完整循环,5 课合而为一3# 放入项目根目录:.github/scripts/morning-triage.sh4# 更改所有标记了 CHANGE 的值56set -euo pipefail78# ─── 第 5 课:上限 ─────────────────────────────────────9MAX_PARALLEL=3 # CHANGE: 你的机器/票据可处理的数量10MAX_TOKENS_PER_RUN=1000000 # CHANGE: 你的 Token 预算1112# ─── 第 1 课:发现 ─────────────────────────────────────13echo "=== 发现:检查 CI、issues、commits ==="14claude --skill morning-triage # 读取 ./state/triage.md1516# ─── 第 2 课:交接——每个发现一个工作目录 ──────17echo "=== 交接:创建工作目录 ==="18findings=$(awk -F'|' 'NR>2 && $5 ~ /open/ {print $2}' \19 state/triage.md | head -n $MAX_PARALLEL)2021for finding in $findings; do22 slug=$(echo "$finding" | tr ' ' '-' | tr -cd '[:alnum:]-')23 git worktree add ../wt-$slug -b fix/$slug24 claude --worktree ../wt-$slug \25 --goal "all relevant tests pass and lint is clean" \26 "Draft a fix for: $finding" &27done28wait2930# ─── 第 3 课:验证——使用独立评估器 ──────────31echo "=== 验证:使用独立评估器 ==="32for finding in $findings; do33 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.log38done3940# ─── 第 4 课:持久化——打开 PR 或更新工单 ──────41echo "=== 持久化:打开 PR / 更新工单 ==="42while IFS=':' read -r finding veridct; do43 slug=$(echo "$finding" | tr ' ' '-' | tr -cd '[:alnum:]-')44 if [ "$veridct" == "PASS" ]; then45 gh pr create --base main --head fix/$slug \46 --title "Fix: $finding" \47 --body "由晨间分类循环自动创建"48 else49 # CHANGE: 集成你的项目管理工具50 echo "REJECT — 需要人工审阅: $finding"51 fi52done < ./state/veridcts.log5354echo "=== 循环完成。所有 PR 等待第 5 课的人工门控 ==="
以及它的配套 GitHub Actions 工作流:
1# .github/workflows/morning-triage.yml2# CHANGE: 调度时间以匹配你团队的时区3name: morning-triage4on:5 schedule:6 - cron: '0 6 * * 1-5' # 工作日 06:00 UTC7 workflow_dispatch: # 允许手动触发89jobs:10 triage:11 runs-on: ubuntu-latest12 steps:13 - uses: actions/checkout@v414 with:15 fetch-depth: 016 - name: Run morning triage loop17 run: .github/scripts/morning-triage.sh18 env:19 GH_TOKEN: ${{ secrets.GH_TOKEN }}20 ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
最终提醒: 如果出现意外,检查这些顺序:
- 第 3 课:评估器是否实际在说"不"?如果没有,你有一个点头循环,你的评估器是假的。从第 3 课重新开始。
- 第 1 课:Judge 是否选择了你的技能说它应该选择的内容?检查 ./state/triage.md。如果不对,你的提示需要修复。
- 第 2 课:工作目录是否被隔离?检查 git worktree list。如果它们共享目录,你有一个混乱循环。
- 第 4 课:状态是否被写下来?等待明天。检查明天的运行是否从同一个 ./state/triage.md 开始,而不是从空白开始。
- 第 5 课:你保留了那扇门吗?在合并之前等待人类。
如果所有检查都通过——你已经构建了一个循环。如果不是——逐课回退,使用那里的测试问题。
现在让它运行。
本课任务:
- 编写
.github/workflows/triage.yml,配置一个 cron 触发器来执行 morning-triage 技能。 - 设置三个上限:单次运行预算、每日预算、最大重试次数。
- 在工作流末尾添加规则:PR 永不自动合并,任何不确定的内容都放入
./inbox/。
1# .github/workflows/triage.yml (需要修改 3 处)2name: morning-triage34on:5 schedule:6 - cron: '0 6 * * *' # 修改:你所在时区对应 UTC 时间 06:0078jobs:9 triage:10 runs-on: ubuntu-latest11 timeout-minutes: 45 # ← 单次运行上限 · 修改12 permissions:13 contents: write14 pull-requests: write15 steps:16 - uses: actions/checkout@v41718 - name: morning triage19 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"2728 - name: 打开 PR(永不自动合并)29 run: |30 # PR 会自动打开。合并操作仍需人工处理。31 gh pr create --draft --base main \32 --title "[循环] $(date +%F) triage" \33 --body-file ./state/triage.md34 # 这里故意没有 `gh pr merge` 命令3536 - name: 将不确定内容路由到收件箱37 run: |38 ls ./inbox/ && echo "有项目等待人工审核"
让循环在六个月后依然活跃的三条规则:
先设上限再上线: 三个层级——单次运行超时、每日预算、最大重试次数——必须在首次自主运行 之前 设置好,而不是在看到第一张吓人的账单之后。Token 上限不是为了省钱。它是一个断路器,能把无限风险转化为有限风险。没有上限的循环,等于把支出权限外包给了自己的 bug。
举个例子:Uber 在四个月内烧光了 AI 预算后,将每位工程师每工具每月的花费上限设为 1,500 美元。你的单循环上限就是这个教训的细化版本。
留一扇门: PR 不自动合并。不确定的内容进入收件箱。你每天抽查一个样本。这扇门的存在,不是因为人类总会走进去,而是因为 门的存在,让你始终保有走进去的能力。 把每扇门都焊死,等到你需要进去的那天,你就已经丢了钥匙。
验证它真的能启动。 至少运行 3 次(试运行也算)。最常见的失败模式是:演示日惊艳全场,周四就彻底凉凉。"最后一次运行就是演示那天"——这是手动循环的诊断标志。
在 Claude Code 之外同样适用—— 这些能力并非绑定特定供应商,只是名称不同:
1能力 Claude Code Codex2调度 /loop · worker 自动化标签页3条件运行 /goal 自动化重跑 + 判断4并行隔离 --worktree 后台 worktree5子 Agent .claude/agents/ .codex/agents/6外部连接 MCP + 插件 MCP 连接器7显式技能 SKILL.md $skill-name8离线运行 云例程 云(计划中)
问题从来不是"选哪个供应商",而是"这六项能力是否都已部署到位"。
本课要预防的病症——手动循环:
四个步骤构建得完美无缺,但没有 cron。这不是一个循环——这是一个你运行一次就忘了的脚本。
解决方案: 挂载任何不依赖你记忆的触发器。
上线前,先回答这三个问题:
1/ 你的三个上限具体是多少?
如果一个 bug 整夜运行——哪个上限会首先打破僵局?如果它们都做不到……这说明了什么?
2/ 你的"人类之门"长什么样?
(PR 审核 / 收件箱 / 每日样本 / 每周对账?)把它写下来 = 与自己签订一份契约。
3/ 最后一个问题: 六个月后,这个循环会让你变得更强(理解力、判断力、对系统的感知力)——还是让你变成一个你无法阅读的机器的看护者?你打算如何避免后者?
四种债务与保持工程师本色
你的循环已经构建完成:它能自动唤醒、寻找任务、自我批评、记忆,并安排自己的明天。
当循环运行时,有四样东西会悄悄累积——它们的共同特征是 没有一样会触发警报。
1验证债务 未拒绝的输出不断堆积2理解退化 代码库在增长,你的心智地图却没有3认知投降 "我不想再仔细审查了"4Token 爆炸 一个 bug 整夜运行 → 你看到的是一张账单
它们相互滋养: 未验证的输出侵蚀你的理解 -> 被侵蚀的理解招致投降 -> 投降让循环运行更久、花费更多 -> 进而产生更多未验证的输出。
在循环首次独立运行之前,对照以下四点进行审计:
- 我的评估器确实会拒绝——它不是装饰品。
- 我每天阅读一个样本 PR,并能解释发生了什么变化。
- 我保留了说"这是错的"的习惯。
- 我的上限是在 首次运行之前 设置的,而不是在收到第一张账单之后。
而决定一切的关键在于:
同一个循环,由两个人构建,可能走向截然不同的结局。一个人用它来加速前进,同时仍然阅读代码、把握方向——循环放大了他们已有的判断力。
另一个人则用完全相同的循环,专门为了再也不去理解任何东西——六个月后,第一个人更加敏锐,第二个人则成了一个他无法阅读的机器的看护者。
Addy Osmani: "构建循环,但要像打算一直做工程师的人那样去构建——而不仅仅是那个按下启动键的人。"
循环让生成几乎零成本,真正稀缺的是判断力:哪个计划是对的,在哪一行停下来,哪个东西在运行但根本上是错的。循环不会贬低判断力——它会剥离一切不是判断力的东西,只留下判断力本身。
循环是一个忠实的放大器:
带来理解——它会放大理解。
带来懒惰——它会放大懒惰。
在构建之前,先决定你想放大什么。
敬请期待!
祝你好运 :)





