循环工程:速成课
15 个概念 · 从 agentic coding 到自提示系统
你已经学会了如何驾驭 coding agent。你给出指令,它读取文件、完成修改,然后由你检查结果。做完一轮,再做下一轮,如此继续。工具始终握在你手中。
现在想象一下,你不再一直握着它,而是构建一个小系统。每天早上,系统会自行启动,查看夜间发生了哪些变化,判断什么值得处理,把每项工作交给 agent,检查结果,并且只在真正需要人做决定时才来找你。你只构建一次,之后它会自行发出提示。
这就是循环工程。有价值的技能,从你写出的提示词转移到你设计的循环。本课会讲清循环由什么组成,以及如何分别在 Claude Code 和 OpenCode 中构建循环。两种工具走的是截然不同的路,却会到达同一个地方。
前置要求:Claude Code 与 OpenCode:速成课。 那门课讲过计划模式、上下文管理、规则文件、技能、子 agent 和 MCP。本课默认你已经掌握这些内容。如果这些词对你还很陌生,请先完成那门课。循环工程直接建立在它之上。最好也完成过 Spec-Driven Development:循环的停止条件本质上是一份 spec,而那门课教你如何写好它。没有它,你仍然可以继续学习,但循环的质量最终取决于你能否准确写出条件。
第一次接触这些内容?用 2 分钟回顾你应该已经知道的知识
- 计划模式:agent 先读取文件并提出计划,在你批准后才修改内容
- 规则文件(
CLAUDE.md/AGENTS.md):简短、长期有效的项目说明,agent 会在每次会话开始时读取 - 技能(
SKILL.md):保存起来、可重复使用的指令,只有任务匹配时 agent 才会加载 - 子 agent:拥有独立上下文窗口的辅助 agent,完成一项工作后只把结果交回来
- 连接器 / MCP:把 agent 接入外部工具的标准方式,例如 GitHub、Slack 或数据库
- 上下文管理:保持对话精简;上下文越满,模型表现越差,成本也越高
如果其中任何一项对你来说是新的,请先完成 agentic coding 速成课。本课直接建立在它之上。
到 2026 年年中,构建这些工具的人已经把话说得很直白。Claude Code 的创建者 Boris Cherny 这样描述:「I don't prompt Claude anymore. I have loops running that prompt Claude... my job is to write loops.」Peter Steinberger(OpenClaw)则说:「you should be designing loops that prompt your agents.」随后,Addy Osmani 为这种模式命名,并列出了它的组成部分。他们没有说工作变轻松了,而是说有价值的技能转移了。整门课都建立在这个观点上。(本课中的引文和论断都在结尾的 来源与延伸阅读 中列出出处。)
用一幅图看懂思维转变

本课同时讲解两种工具,原因和上一门课相同:如果一项技术在两种工具中都能用,它就是一项真正的技能,而不是某个工具的窍门。 不过,这两种工具在这里确实不同,这种差异本身就是一课。Claude Code 现在把循环组件直接内置在产品里。OpenCode 提供的是更底层的一层,也就是一个由操作系统启动的工作者。你会看到两种方式,也会看到,即使接线方式不同,循环的结构仍然相同。
截至 2026 年 6 月为最新内容。这两种工具变化很快,Claude Code 的多项循环功能仍处于研究预览阶段。每次使用前,请运行
claude update或opencode upgrade。在依赖某个限制或参数前,先查看实时文档(code.claude.com/docs、opencode.ai/docs)。
真正的循环是无人值守的:你离开时,它会自行向自己发出提示。普通的 claude.ai 聊天框做不到这一点,因为它每一轮都在等你,所以在聊天里,你就是调度器。要运行真正的循环,你需要一个能自行触发的工具:Claude Code、OpenCode 或 Cowork(参见 Cowork 速成课)。它们大多是付费的,目前也有几项处于研究预览阶段。你可以在任何地方学习和设计循环;但要真正运行循环,就需要上述工具之一。本课会讲其中两种 coding 工具。 这是个合理的问题,因为「claude.ai」实际上指两样东西。简短回答是:聊天框不能运行循环,但同一个 claude.ai 账号中的 Claude Code 可以。 因此,聊天适合设计和排练循环:起草技能、编写审查提示、固定停止条件、手动跑一轮。Claude Code 或 OpenCode 则负责运行循环。这正是完成 Spec-Driven Development 之后自然的下一步。「可我是在 claude.ai 中完成整门 Spec-Driven 课程的,也能在那里运行循环吗?」
claude.ai/code/routines 创建)运行在 Anthropic 的服务器上,即使笔记本电脑已经关机、当地没有安装任何东西也能运行。因此,从这个意义上说,你确实可以「从 claude.ai」建立真正的无人值守循环。目前它需要付费套餐,并处于研究预览阶段
从这里开始,几乎每个概念都可以在真实会话中运行,而不只是阅读。请在页面旁边打开一个终端(claude 或 opencode),学到一个概念就试一次。先用一个小型、可丢弃的 Git 仓库,这样循环就不会伤及你在意的内容。
本课涵盖什么
| 部分 | 主题 | 你会学到什么 |
|---|---|---|
| 1 | 转变 | 循环是什么、它的 6 个部分,以及构建循环的两条道路 |
| 2 | 心跳 | 让系统自行运行:会话内、运行到完成、定时、事件驱动 |
| 3 | 主体 | 隔离、知识、行动,以及执行者–检查者拆分 |
| 4 | 脊柱 | 能在多次运行之间保留下来的状态,也是人们最容易忘记的部分 |
| 5 | 同一个循环,两种实现 | 一个从晨间分流到 PR 的完整循环,使用真实文件在两种工具中分别构建 |
| 6 | 继续做工程师 | token 成本、检查工作,以及随着循环变强而越来越严重的陷阱 |
| 练习 | 练习项目 | 由易到难,亲手构建 5 个循环 |
喜欢边做边学? 先阅读 第 5 部分,从头到尾看一遍完整循环,再回来理解各个组件。概念理解之后,请用 练习项目 亲手构建 5 个循环。
完整阅读约需两小时。完成第 5 部分和练习项目需要更久,这正是重点:你要构建循环,而不是走马观花。
本课贯穿着两个层次,而它们的寿命差异很大。内化第一层,查阅第二层。
- 持久层。 循环的结构(一个心跳、4 个工作组件和一条脊柱)、执行者–检查者拆分,以及循环永远无法自动化的两个端点:意图(把你想要的结果说得足够准确,从而能够检查)和责任(对交付的内容负责)。这才是技能。无论下面的命令如何变化,它都成立
- 机械层。 每个参数、路径、模型 ID 和命令名。这些工具每周都在更新,其中多项功能仍处于研究预览阶段。因此,请把每条具体命令当作实时文档的入口,而不是必须背下来的事实。如果本课与最新文档不一致,以文档为准
记住由 6 个部分组成的结构,忘掉每次按键,你就学会了循环工程。背下按键却错过结构,你学到的只是本月的 CLI。
📚 教学辅助
查看完整演示:循环工程速成课
第 1 部分:转变
1. 从提示到循环
过去大约两年里,从 coding agent 那里获得工作的方式很简单:写好提示词,给它足够的上下文,阅读返回的内容,再输入下一条指令。agent 是一种工具,你每次只使用一轮。
循环用系统取代了作为操作者的你。系统发现工作、分配工作、检查工作、记录所做的事,并决定下一步。它替你向 agent 发出提示。
那么,价值去了哪里?它没有消失,而是分到了循环无法自动化的两个端点:意图,也就是准确说明你想要什么,让结果可以被检查;以及责任,也就是为最终产出负责。循环自动化的是中间的步骤,两个端点仍属于你。你获得回报,是因为你能表达意图并作出判断,而不是因为你忽略了工作是如何完成的。
区别并不是「更大的提示词」,而是一种不同的工作结构:
| 提示(你已经熟悉的方式) | 循环(本课新增的方式) |
|---|---|
| 你启动每一轮 | 计划或事件启动每一轮 |
| 你阅读输出并决定下一步 | 检查者检查输出,循环决定下一步 |
| 你停止输入时,它立刻停止 | 你睡觉时,它仍会继续运行 |
| 一项任务、一次会话、占用你的全部注意力 | 多次小型运行,大多无人值守,只在关口需要你的注意力 |
这不是魔法,也不是「设置好后就不用管」。 循环自行运行,也意味着它会自行犯错。本课的全部内容,都是为了构建一个你真正敢于放手运行的循环。这比提示更难,而不是更简单。回报是杠杆效应:一个设计良好的循环会反复替你工作,完成原本每次都得由你手动启动的任务。
2. 循环由什么组成
真正能自行运行的循环有 5 个部分和 1 条脊柱。其中 4 个部分已经在 agentic coding 课程中出现过,现在它们有了新的职责。

- 心跳:启动循环的计划(或事件)。没有它,你拥有的只是一次运行,不是循环
- 工作树:提供隔离,让两个同时工作的 agent 不会覆盖彼此的文件
- 技能:只写一次的项目知识,让每次运行都不必从零开始
- 子 agent:执行者–检查者拆分。编写代码的 agent 不能同时负责给代码评分
- 连接器(MCP):让循环能在真实工具中采取行动(打开 PR、更新工单),而不只是提出建议
还有第 6 个部分,初学者最容易跳过:
- 状态 / 记忆,也就是脊柱。 磁盘上的文件(或 Linear 一类看板)保存已经完成和接下来要做的工作。模型会忘记不同运行之间的一切。脊柱让今天的运行知道昨天做过什么。没有脊柱,就没有循环,只会永远重复相同的第一步。
本课后面的每一节都会讲解一个组件,最后再用一个完整示例把它们连接起来。
3. 两条道路:内置组件与更底层的一层
这是两种工具真正不同的地方,也会塑造后面的全部内容。

Claude Code 把循环组件内置在产品里。 心跳(/loop、/schedule、云端 Routines)、带内置检查者的运行到完成(/goal)、隔离(--worktree)和事件输入(Channels)现在都是内置命令。一年前,你需要编写并维护一堆 shell 脚本才能实现这些功能;如今大多只需完成配置。
最重要的是 Routines:运行在 Anthropic 服务器上的云自动化,即使笔记本已经关闭也会继续运行,可由计划、API 调用或 GitHub 事件启动。便利的代价是账号级每日运行上限(请在 Routines 使用量界面查看当前上限,不要依赖固定数字),以及 Routines 仍处于研究预览阶段,未来可能变化。
OpenCode 提供更底层的一层。 它没有内置云调度器。OpenCode 是你调用的工作者,而心跳由你从操作系统或 CI 提供。
关键命令是 opencode run "<prompt>"。它不打开聊天界面,只运行一条提示,打印结果,然后退出。这条命令就是循环的一次心跳。要把它变成循环,可以用按计时器触发的东西包住它:cron 或 launchd(macOS 和 Linux)、Task Scheduler(Windows),或带计划触发器的 GitHub Actions。这需要更多接线,但你能完全控制系统,可以使用已有机器运行,也不需要厂商云。
请注意,两个标签页描述的是相同的 5 个部分。无论心跳是托管的 Routine,还是 cron 中的一行命令,心跳就是心跳。无论由 /goal 评分,还是由第二次 opencode run 完成,执行者–检查者拆分仍是同一个想法。只需学会一次循环的结构,就可以迁移。 这正是我们同时教授两种工具的原因。
我们将要到达的地方(先看完整循环)
在拆解各个组件之前,先看终点。你将在 第 5 部分 构建的循环只有 6 个朴素步骤:
every weekday at 9am: # 1. Heartbeat
read progress.md # 6. Spine (memory)
find overnight CI failures + issues # what to work on
for each one:
draft a fix in its own checkout # 2. Worktree
using the project's triage skill # 3. Skill
have a separate reviewer grade it # 4. Sub-agents (maker/checker)
if PASS: open a PR via GitHub # 5. Connector (MCP)
if risky: write it to progress.md and leave it for a human
update progress.md # 6. Spine again
请记住这幅图。第 2–4 部分的每个概念,都是其中一行。
一个循环每天早上运行,但每次都从全新状态开始,完全不记得昨天做过什么。它缺少 6 个部分中的哪一个?为什么这会破坏循环? 缺少的是脊柱(状态 / 记忆)。模型会忘记不同运行之间的一切,因此如果磁盘上没有状态文件,循环只会永远重复第一步,而无法承接昨天的工作。查看答案
第 2 部分:心跳
心跳把一次运行变成循环。心跳有 4 种,从「只在当前会话中持续」到「完全不需要你也能运行」。请按顺序学习,因为多数真实循环都会使用后两种。

这四种心跳背后有一个共同的想法:循环不是单次动作,而是「做这件事、等待、再做一次」,反复进行,因此必须有某个东西在两次搏动之间保持清醒,好触发下一次。唯一的问题是这个东西存在于哪里。
- 会话内循环把计时器放在你打开的会话里,也就是终端开着时一直运行的那个进程。关闭会话,握着计时器的东西就没了,循环也随之停止。
- 定时任务或 Routine(你会在概念 6 中构建它们)把计时器移到会话之外,交给一个永不休眠的调度器(你自己机器上的 cron,或云端 Routine 所用的 Anthropic 服务器)。每一次滴答,它都会启动一次全新的、短暂的运行,让它跑完,然后关掉,下一次再启动一个新的。同一个循环,但你这边不需要保持任何东西开着。
| 心跳 | 计时器存在于哪里 | 两次搏动之间由谁保持清醒 |
|---|---|---|
会话内 /loop | 会话内部 | 你打开的会话(你的机器、终端开着) |
| 定时任务 / Routine | 外部,在调度器中 | 调度器,它每次滴答都启动一次全新的运行 |
这幅图也请记住。它能解释后面出现的每一条限制:会话内循环会随它的会话一同停止,而必须在笔记本关闭后依然存活的循环,需要外部那种(概念 6)。
4. 会话内循环(你看着它重复)
最简单的心跳:在会话保持打开时,按计时器重复运行提示。它适合「监视这件事,直到完成」的任务,例如部署、长时间测试或 CI 作业。
使用内置的 /loop 技能,提供时间间隔和提示:
/loop 5m check if the deployment finished and tell me what happened
Claude 会把时间间隔转换成计划,为任务分配一个 ID,并在会话保持打开时每 5 分钟运行一次提示。完成后,取消任务并继续处理其他工作。 需要知道的一项限制: 普通会话中的 对于无论如何都必须继续运行的工作,不要依赖 一个折中选项:后台会话。 后台会话是会话内 它本身只做一件事然后停止,不会按计时器重复。它的价值在于承载循环。启动一个 当你想关闭终端、却又想让一个循环继续盯着某件事(比如一次部署或一次长时间测试)时,就用它。只是别忘了「在你自己的机器上存活」的代价:这台电脑必须保持清醒。一旦工作必须在笔记本关闭后依然存活,你就进入了调度器的领域,正确的工具是 Routine(概念 6)。 这三个档位可以归到同一个问题上:需要多少东西保持清醒?可选:会话关闭时会发生什么?
/loop 有意运行在你的会话内部。关闭终端或让笔记本进入睡眠,它就会停止。这是一项安全功能,不是 bug。随手启动的会话内循环,本就不该比启动它的会话活得更久。有两项近期改动放宽了这条规则:
--resume 会恢复尚未过期的任务。一个周期性任务在你创建后的七天内保持有效。/loop 任务一并带走,因此即使没有打开终端,它们也会继续触发。但要注意:机器睡眠期间错过的触发,它们不会补上。/loop,而要使用定时任务或 Routine(概念 6)。工具现在也会替你处理这一点。在云会话(运行在远程服务器上、而非你自己机器上的会话)中,近期版本已经完全不再提供 /loop,因为这种会话在你的请求一结束就会关闭,没有任何东西留下来维持循环。/loop 和 Routine 之间的中间档:在你关闭终端窗口后,它会在你自己的机器上保持一次运行存活。用 claude --bg 启动。/loop,把那个会话送到后台,循环就会随之运行,即使窗口已关闭也继续触发。(之后 claude agents 会列出它,/resume 会重新打开它,并标记为 bg。)档位 选项 关闭终端后仍继续触发? 笔记本睡眠或关机后仍继续触发? 最低档 会话内 /loop否 否 中间档 承载 /loop 的后台会话(--bg)是 否(需要你的机器保持清醒) 最高档 定时任务 / Routine 是 是(云端 Routine 运行在 Anthropic 的服务器上)
OpenCode 没有 /loop 命令,需要自己用 shell 构建计时器。由于 opencode run 会在运行一条提示后退出,因此包含 sleep 的 while 循环可以实现同样的效果:
while true; do
opencode run "check if the deployment finished; if it did, say DONE"
sleep 300 # 5 minutes
done
这与 /loop 是同一个想法,只是低了一层:shell 是心跳,opencode run 是每一次搏动。每次全新的 opencode run 都会先启动完整运行时,包括配置、模型、插件和所有 MCP 服务器,然后才执行工作。为了避免每次搏动都支付这份启动成本,可以先启动一次服务器,再连接到它:
opencode serve --port 4096 &
# then, each beat:
opencode run --attach http://localhost:4096 "check the deploy status"
取消 /loop。 每个循环都是带有 ID 的计划任务,因此可以用和启动时一样的自然语言停止它:
show my running loops
cancel the deploy-check loop
Claude 会查找并取消该任务。如果同时运行多个循环,它会询问要取消哪一个,你也可以直接提供任务 ID。关闭会话同样会停止普通会话循环,但取消才是干净的做法:本来要停止的循环不应依赖会话关闭。精确的子命令属于机械层;自然语言不起作用时,请查看实时文档:code.claude.com/docs/en/scheduled-tasks。
对于 OpenCode,使用 Ctrl-C 停止前台循环;如果它在后台运行,则终止其进程。对于 /goal,单独的检查器只能读取对话:Claude 必须运行测试并显示输出,否则检查器无法证明目标已实现。 Ralph 循环重复一个提示并更新一个状态文件;如果没有命令可验证的成功条件,它就会徘徊直到其时间上限结束。
5. 运行到完成(由循环决定何时停止)
固定计时循环会按时钟反复运行,直到你取消它或会话结束。每次运行可以检查结果,但没有任何东西读取结果并停止循环。运行到完成的条件循环则在独立检查证明工作已完成时停止。差别不在次数:前者不知道工作何时完成,后者因为工作完成而停止。
固定计时循环有意保持简单,无论发生什么都运行 N 次。很多时候,你真正想要的是**「一直做,直到这个条件成立」**。执行者–检查者的思想第一次在这里出现:不能让完成工作的 agent 自己决定工作是否完成。
使用 /goal。你要提供一个 Claude 能在自身输出中证明的停止条件,也就是它可以通过运行命令并展示结果来证明的事情,例如「test/auth 中的全部测试通过」。它会跨多轮持续工作,直到条件成立。关键在于:每轮之后,一个独立、更小的模型(默认是 Haiku)会读取记录并判断「完成了吗?」 因此,编写代码的 agent 不会给自己的工作评分。还要注意一个细节:检查者不会亲自运行命令,只会判断 Claude 已经展示的内容。因此,条件必须能由 Claude 自己的输出证明,而不能是只有某条未展示命令才知道的私密事实。
/goal All tests in test/auth pass and `npm run lint` is clean.
它会编辑代码、运行测试、读取失败信息、再次尝试,直到检查者确认条件确实成立,或你用 无人值守的运行指循环在无人盯着的情况下工作,可能在夜间,也可能在你离开键盘时。没有人会注意到它卡住并把它停下来。正因如此,这里的重试才需要一个上限。 重试指循环再次尝试一个失败的步骤(比如一次超时的 API 调用)。如果没有上限,卡住的循环可能会在你睡觉时无休止地重试,白白耗费时间和 token。因此 Claude Code 现在会替你设定这个上限: 这些参数名和数字属于机械层,因此在依赖它们之前,请查看实时文档。而持久的要点是:如今连厂商都默认,一次无人值守的运行需要一个由人有意选定的上限。/goal clear 手动停止。系统没有内置的「尝试 N 次后放弃」。如果需要上限,请把它写进条件(…or stop after 20 turns)。条件应当能由命令证明,例如「测试通过且 lint 干净」,而不是「认证代码写得好」。Spec-Driven Development 在这里发挥价值:验收标准本来就是命令可以证明的条件,因此一份好 spec 会直接提供停止条件。可选:Claude Code 当前的重试设置
CLAUDE_CODE_MAX_RETRIES),你可以把上限提高到 15。CLAUDE_CODE_RETRY_WATCHDOG=1。它会把临时错误(比如短暂的网络抖动)的重试时间拉长很多(撰写本文时最多 300 次,约三小时的退避),并在你自行设置 MAX_RETRIES 后解除那个 15 次的上限。
OpenCode 没有 /goal,所以要用 shell 和退出码构建同样的执行者–检查者停止机制。模式是:agent 完成工作,然后由一条真实命令(不是 agent)决定是否停止。
for i in $(seq 1 8); do # cap the tries — never loop forever
opencode run "Make the tests in test/auth pass and fix any lint errors."
if npm test -- test/auth && npm run lint; then
echo "Condition met on try $i"; break
fi
done
这里,测试运行器和 linter 就是检查者。这是最诚实的检查者,因为命令无法说服自己「工作已经没问题」。要做更智能的检查,可以使用专门的审查 agent 再运行一次 opencode run,让它输出 PASS 或 FAIL。一定要限制尝试次数;没有上限的重试循环,会让 token 账单失去控制。
每个循环都需要三种停止条件。每一种都能防止一类特定的失败:
| 停止条件 | 它是什么 | 一旦缺失…… |
|---|---|---|
| 成功条件 | 循环据以判断任务已完成的依据 | 没有东西定义「完成」,循环就无法有意停下,也无法评分 |
| 上限 | 一个天花板:最大尝试次数、分钟数或支出 | 一个无法达成的目标会耗尽你的全部 token 预算 |
| 无进展检查 | 发现 agent 反复用相同参数执行相同动作(说明它卡住了,再重试也没用) | 它会把整个上限花在重复同一个错误上 |
你会在网上遇到一个名字:Ralph 循环。 它是最广为人知、最简单的运行到完成循环:反复运行同一个提示,每次运行读取并更新同一个状态文件。它只保留了三种停止条件中的两种(一个成功条件和一个时间上限),没有卡住检查、没有技能,也没有独立的检查者。正是这份朴素,让它把这个道理讲得格外清楚。条件含糊的 Ralph 循环会一直徘徊,直到时间上限烧尽;而换成一个命令可以证明的条件,同样的循环就能运行良好。
循环的好坏,取决于它的停止条件。
一个运行到完成、跑了很多轮的循环,会把自己的上下文塞满垃圾:旧的工具输出、走过的死胡同、过时的推理。随着这堆东西越积越多,模型的回答也越来越差。社区把这种结果叫作厄运循环(doom loop):混乱的上下文导致更糟的决定,更糟的决定又添了更多混乱,让下一个决定更糟。应对之道,正是 agentic coding 课程 里那些相同的上下文习惯:
- 压缩长时间运行: 每隔一段时间,用一段简短的摘要替换原始的来回对话,让上下文保持精简。
- 把大块输出移到文件里: 把大体量的结果(日志、数据、生成的文本)写入文件,在上下文里只留一个指针,而不是把整段内容粘进去。
- 把杂乱的子任务交给子 agent: 让一个助手在它自己的上下文里做嘈杂的探索,只把干净的结果返回来。
这三条背后是同一个想法:把上下文当作一份你有意花用的预算,而不是一个你不停往里倒的水桶。一个更小、更干净的上下文,才能让长时间运行的决定保持敏锐。
6. 无人值守的计划(你睡觉时也运行)
这种心跳让循环工程真正有了意义:一项任务无论你是否在电脑旁都会运行。「每个工作日上午 9 点,整理夜间的 CI 失败。」「每周一检查依赖,并为安全修复打开 PR。」
先说说命名。**调度器(scheduler)**是一个笼统的概念:任何一直开着、能按时启动一次全新运行的时钟(cron、GitHub Actions,或某个云服务)。而 Routine 是 Claude Code 自带的、托管在云端的调度器,由 Anthropic 同时提供时钟和机器,因此你这边不需要开着任何东西。每个 Routine 都是一种调度器;但并非每个调度器都是 Routine。
有三种方法可以让克劳德按计划运行。在它们之间选择一个问题:**工作在哪里运行?**在 Anthropic 的服务器上,通过桌面应用程序在您的计算机上,或者通过您自己的调度程序在您的计算机上。按这个顺序排列——第一个是大多数人应该学好的。
1. 云例程——笔记本电脑可以关闭
从简单的想法开始。云例程是常设指令,位于 Anthropic 的服务器上,而不是您的计算机上。您只需编写一次说明即可。从那时起,它就会在您设置的时间自行运行,无论您的笔记本电脑是打开的、处于睡眠状态还是放在包里。可以将其视为雇用一名坐在 Anthropic 办公室(而不是您的办公室)的员工:您提交一份书面工作描述,工作就会在您不主持的情况下进行。
举个具体的例子。 假设您使用 GitHub 存储库维护一个小型产品,每天早上您都会花 30 分钟进行相同的分类:阅读隔夜到达的新问题,给它们贴上标签,标记任何看起来像是崩溃的内容,然后在团队的 Slack 中发布摘要。这半个小时是一个完美的例行公事:它重复,它遵循你可以写下来的规则,并且它不需要你 - 它需要你的指示。我们将在下面构建这个例程。
每个例程的四个部分
当您创建例程时,您将填写四个空白。每个人都回答一个问题 - 在这里,我们填写了早上的分类示例:
-
提示 — 它应该做什么? 常规指令本身,按照您在规范课程中学到的方式编写:目标、规则、“完成”是什么样子。每次运行都会出现相同的提示,因此它必须在您缺席的情况下继续存在 - 您不会在那里澄清。
示例提示:“查看过去 24 小时内打开的所有问题。将每个问题标记为
bug、feature-request或question。如果任何问题描述了崩溃或数据丢失,请添加urgent标签。然后将摘要发布到 #triage Slack 频道:新问题总数、紧急问题数量以及每个紧急问题一行。如果没有新问题,请发布“隔夜无新问题”。不要关闭或评论任何问题。”请注意工作中的规范剖析:目标(分类和总结)、规则(标记这些方式,紧急意味着崩溃/数据丢失)、边界(不要关闭或评论)以及定义的“完成”,即使对于空案例也是如此。
-
存储库 - 它可能触及什么? 您可以指定例程可以在其中工作的存储库。您未列出的任何内容都无法访问。
*示例:*您授予
yourteam/product-app— 仅此而已。就本例程而言,您的其他五个存储库(包括带有计费代码的存储库)根本不存在。 -
连接器 — 它能到达什么? Slack、电子邮件、日历 — Routine 超越存储库的双手:它如何读取外部世界以及如何向您报告。
*示例:*您连接 Slack 连接器,以便它可以发布到#triage。没有附加电子邮件连接器 - 因此即使提示要求它也无法发送电子邮件。连接器是权限,而不是建议。
-
触发器 - 它什么时候醒来? 心跳。三种,每种适合不同的工作形式:
- 时间表 — 时钟将其唤醒。 我们的示例:每个工作日上午 8:30,因此在团队坐下来之前摘要已在 Slack 中等待。
- API 调用 — 另一个程序将其唤醒。 示例:您的部署脚本完成发布,然后调用例程的 API 端点来触发“烟雾检查发布和报告”运行 - 例程会在有需要检查的内容时触发,而不是在愚蠢的计时器上触发。
- GitHub 事件 — 存储库唤醒它。 示例:由“拉取请求打开”触发的例程,根据团队的清单审核每个新 PR 并留下结构化评论 - 它在安静的一天运行 0 次,在忙碌的一天运行 9 次。
提示、存储库、连接器、触发器。做什么,它可能在哪里行动,它能到达什么地方,什么时候醒来。每个例程都是这四个答案 - 我们的早晨分类工作人员现在已完全指定:上面的提示,一个存储库,一个 Slack 连接器,工作日 8:30。
一个功能,三扇门
您可以在三个位置创建例程:浏览器中的 claude.ai/code/routines、桌面应用程序或使用 CLI 中的 /schedule 命令。这些不是三个不同的功能——所有三扇门都打开到同一个房间。您从任何门制定的每个例程都会保存到同一个云帐户中,并显示在所有三个位置。
*为什么这很重要的示例:*您周二在终端中使用
/schedule绘制了分类例程,然后在周四 - 在手机浏览器上,在公共汽车上 - 您打开claude.ai/code/routines,看到相同的例程,并编辑其提示。同一个房间,不同的门。
跑步时实际发生的情况
周一 8:30,Anthropic 的服务器启动一个新的 Claude 会话,向其提供您的提示,并授予其访问您列出的存储库和 Slack 连接器的权限。它会读取周末的问题,给它们贴上标签,发布到 #triage — 比如说:“7 个新问题,1 个紧急问题:Android 上的登录崩溃 (#412)” — 然后关闭。周二 8:30,全新的跑步开始;周一的会议结束了。一切都不取决于您的机器——这一属性使例程成为真正的循环,而不是您必须照看的会话。
在依赖它之前需要了解的两条规则
规则一:每日上限。 每个帐户每天都会运行固定数量的例行程序 - 在启动时,Pro 上 5 次,Max 上 15 次,Team 和 Enterprise 上 25 次。为什么要戴帽子?因为在别人的服务器上运行的无人值守系统必须有预算;每个云服务都是如此,并且它是您设计的数字,而不是您发现的惊喜。
专业帐户上的预算示例(5 次运行/天): 早上分类(1 次运行)+ 晚上“总结今天的提交”报告(1 次运行)+ 每天 4 个拉取请求(4 次运行)= 6 次运行的 PR 审核例程。你已经超过一了。您的选择(按优先顺序排列):将两份每日报告合并为一个例程(节省运行时间),让 PR 审核员成为您在忙碌的日子购买额外使用量的东西,或者升级计划。关键是你在循环在运行第五次时默默停止之前*完成了这个算术。
三个细节软化了上限:这些是启动时的数字,因此请检查 claude.ai/settings/usage 以了解当前的数字;一次性预定运行不计入在内;并且您可以支付超过此后的额外使用费用。
规则二:默认情况下,它只能推送到 claude/ 分支。 新建的 Routine 无法写入 main。它推送的每个分支都必须以 claude/ 开头。
这是好事,不是障碍。它从第一天起就保证了无人值守的工作是安全的:Routine 可以尽情完成它要做的一切,但合并什么仍由你决定。
举个例子。你设置了第二个 Routine,让它在夜间修复不稳定的测试。凌晨 3 点,它把修复推送到一个 claude/ 分支。早上你读一遍这处改动,确认看起来没问题,再自己合并。你睡觉时 Routine 干了活;你醒来后做了检查。这中间的间隔正是全部意义所在。
后来,当某个仓库经过多次干净的运行赢得了你的信任,你可以只为那个仓库,用「允许不受限制的分支推送」设置关掉这条规则。有意为之,一次一个仓库,就像把一把钥匙交给别人。
当云例程是正确的选择时
每当工作不需要您的机器时:早上分类(我们的示例)、周五为利益相关者提供“本周发生了什么变化”摘要、监控竞争对手的变更日志、起草对日常支持问题的响应。如果你发现自己在想“这应该在没有我的情况下每天发生”,这就是工具。
2. 桌面计划任务——笔记本电脑必须开机
在桌面应用程序中创建。这些在本地针对您的真实文件运行 - 包括编辑器中未保存的更改 - 并且它们不需要打开会话。
*这是正确工具的示例:*每天晚上 6 点,“查看我今天在本地项目中所做的所有工作 - 包括我尚未提交的文件 - 并将工作日记条目写入
~/journal/。”云例程无法以任何代价完成这项工作:您未承诺的下午仅存在于您的计算机上。本地访问是此选项存在的全部原因;交易是你的笔记本电脑必须在下午 6 点打开。
可选:从终端管理 Routine
你可以用平实的中文,在终端里管理一个 Routine 的整个生命周期。 创建它、列出你已有的、立即运行一个,或者修改它的运行时间:
/schedule every weekday at 9am, run the daily-triage skill # create it
/schedule list # see what you have
/schedule run the triage routine now # fire one run, to test it
/schedule update the triage routine to every two hours # change the timing
有三点要快速了解:
- 自定义时间。 现成的选项覆盖了常见情况(每天、每小时、每个工作日)。遇到不寻常的排期时,
update也接受一个 cron 表达式:一小段用来精确写出时间的代码。例如,0 9 * * 1-5表示「上午 9 点,周一到周五」。 - 免费练习。 一次性任务只运行一次而不重复,比如
/schedule tomorrow at 9am, …。一次性任务不计入你的每日运行上限,所以它是一种免费的方式:先把提示试跑一次、确认没问题,再决定是否让它每天运行。 - 仅限按时钟触发。 从终端你只能创建按设定时间启动的 routine。要从别的东西启动它,比如另一个程序调用它(一次 API 调用)或 GitHub 上的一个事件(比如一个新的拉取请求打开),就到该 routine 的网页上去添加那个触发器。
如果 /schedule 在你的 CLI 里似乎不存在,Routines 附录 说明了该检查什么。
3. 你自己的 cron——你的机器,你的调度程序,没有 Anthropic 云
cron 是一个调度程序,几十年来一直随每台 Mac 和 Linux 机器一起提供。它只做一件事:它运行你给它的命令,有时你选择。 claude -p(-p 代表提示)运行一个提示并退出 — 没有会话,没有聊天窗口。将两者放在一起,您就可以使用您已经拥有的部件构建一个循环。
cron 指令是一行两半:何时运行,然后运行什么。
# every weekday at 9am: sort through CI and summarize failures
0 9 * * 1-5 cd /path/to/repo && claude -p "check the CI dashboard and summarize any failures" >> ~/claude-cron.log 2>&1
当你准备好构建第一个真正的 Routine(每一个表单字段、全部三种触发器、密钥,以及常见问题)时,本课结尾的 Routines 附录 会一步步带你走完整个过程。
OpenCode 的无人值守心跳始终来自操作系统或 CI,这正是 OpenCode 的路线。使用不带聊天界面的 opencode run,让调度器负责触发。
在自己的机器上使用 cron:
# every weekday at 9am: sort through CI and summarize failures
0 9 * * 1-5 cd /path/to/repo && opencode run "check the CI dashboard and summarize any failures" >> ~/opencode-cron.log 2>&1
在云端使用 GitHub Actions,这样你的任何机器都不必保持开机。下面的 model 字符串仅作示例,请运行 opencode models 查看当前安装认识的准确 ID:
name: Scheduled OpenCode Task
on:
schedule:
- cron: "0 9 * * 1-5" # weekdays at 9am UTC
jobs:
opencode:
runs-on: ubuntu-latest
permissions: { contents: write, pull-requests: write, issues: write }
steps:
- uses: actions/checkout@v6
with: { persist-credentials: false }
- uses: anomalyco/opencode/github@latest
env: { ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} }
with:
model: anthropic/claude-sonnet-4-6 # confirm with `opencode models`
prompt: |
Review the codebase for TODO comments and summarize them.
If any are worth acting on, open an issue to track them.
定时事件必须提供 prompt,因为没有评论可以充当指令。如果循环要创建分支或 PR,还必须授予 contents: write / pull-requests: write 权限。
当前官方文档把 OpenCode GitHub Action 写作 anomalyco/opencode/github@latest;部分旧指南仍使用 sst/opencode/github@latest。两者指向同一个项目,请使用 opencode github install 生成的版本。模型方面,anthropic/claude-sonnet-4-6 是当前 Sonnet ID。从 4.6 代开始采用无日期 ID,其中无日期字符串本身就是固定快照;较早的 4.5 代模型,例如 Haiku 4.5,既有带日期的规范 ID(claude-haiku-4-5-20251001),也有指向最新快照的无日期别名 claude-haiku-4-5。下面的示例为了可复现性固定使用带日期的 Haiku ID。请运行 opencode models 查看当前安装认识的准确字符串。
7. 事件驱动(事件发生时立即响应)
计划问的是**「每小时检查一次」。事件问的是「X 发生时立刻响应」**。PR 打开、issue 创建或消息到达时,循环立即运行。
本节讲的是事件。所谓事件,就是某件事发生了。每一类事件都有一个接住它的工具,也就是对它作出反应的工具,而这些工具并不都一样。GitHub 事件和「其他所有」事件由 Routine 接住;聊天消息由 Channel 接住。所以三个接住者里有两个是 Routine,另一个(Channel)不是。Channel 唯一的弱点是需要你的机器开着,这一点在下面的表格里写得很清楚。
在 Claude Code 里,事件来自哪里决定了你用哪种工具。一共有三条路线:
1. 来自 GitHub,用 Routine。 Routine 不一定要按时钟运行,它的触发器也可以是一个 GitHub 事件。有两类可用:一个**拉取请求(pull request)发生变化(打开、更新或合并),以及一次发布(release)**被发布。例如:「当 PR 打开时,审查它并留下评论」,或「当发布版本时,起草更新日志」。
在构建之前,有三点需要知道:
- 安装。 必须在仓库上安装 Claude GitHub App。注意:
/web-setup只授予克隆访问权限,并不会安装这个应用。很多人第一次尝试时都栽在这里。 - 没有推送触发器。 不存在「推送时触发」的事件。但如果有人向一个已经打开 PR 的分支推送提交,GitHub 会把它算作对该 PR 的更新(即
synchronized事件),于是 Routine 就会触发。向没有 PR 的分支推送,则什么也不会发生。 - 过滤器。 你可以限定哪些事件会触发它:按作者、标题、标签、分支、草稿状态等等。完整的字段列表、每小时上限和那些棘手的边界情况,都在 Routines 附录(A3)中。
2. 来自聊天应用,用 Channel。 Channel 会把来自外部应用的一条消息直接送进一个已经在运行的会话。Telegram、Discord 和 iMessage 开箱即用;其他来源则需要你自行设置一个 webhook。这就是第 2 部分里的门铃:消息到达之前什么都不会发生,一旦到达,会话就立即作出反应。
例如,离开办公桌时,你可以通过 Telegram 问一句「部署完成了吗?」现有会话会检查,并带着它当前的上下文、技能和历史记录回复你。循环也可以通过同一个 Channel 把报告发回来。
有两点需要注意:
- 它需要一个活着的会话。 会话必须已经在运行(一个打开的终端,或一个后台会话),因此机器关机时 Channel 无法工作。
- 它是一扇敞开的门。 任何能向那个来源发消息的人,都可能操纵你的会话,所以只连接你自己掌控的来源。
设置方法:code.claude.com/docs/en/channels。
3. 来自其他任何东西,用带 API 触发器的 Routine。 有些事件既不来自 GitHub,也不来自聊天应用:监控工具里触发了一条告警、一次部署完成了、一个表单被提交了。给 Routine 一个 API 触发器,那么任何能发送经过身份验证的 Web 请求(一种能证明自己身份的请求)的系统,都可以在你笔记本关闭的情况下触发它。这个请求甚至可以携带事件本身的细节:一个可选的 text 字段会把与本次运行相关的上下文(告警内容、出错的日志)连同它保存的提示一起传给 Routine。端点、令牌以及一条重试警告都在 Routines 附录(A3)中。
在三者之间选择:
| 事件来自…… | 使用 | 这项工作运行在…… |
|---|---|---|
| GitHub(一个 PR、一次发布) | Routine,GitHub 触发器 | 每个事件一个全新的云会话 |
| 聊天消息(Telegram、Discord、iMessage) | Channel | 你已经在运行的会话 |
| 任何能发送 Web 请求的东西 | Routine,API 触发器 | 每次调用一个全新的云会话 |
请注意第一行和第三行的模式:每个事件都会启动一个新会话,因此两个事件之间彼此一无所知。对同一个 PR 的两次推送,是两个各自独立的会话。脊柱(概念 12)正是它们共享状态的方式。
运行一次 opencode github install 安装 GitHub agent,它会添加 .github/workflows/opencode.yml。此后,OpenCode 会响应仓库事件,包括 pull_request、issues,以及 /oc 或 /opencode 评论,并在 GitHub Actions runner 中运行:
name: opencode-review
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
jobs:
review:
runs-on: ubuntu-latest
permissions: { contents: read, pull-requests: read }
steps:
- uses: actions/checkout@v6
with: { persist-credentials: false }
- uses: anomalyco/opencode/github@latest
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
model: anthropic/claude-sonnet-4-6
use_github_token: true
prompt: |
Review this pull request for bugs, quality issues, and security risks.
对于没有提示的 pull_request 事件,OpenCode 默认会审查 PR。
你希望循环不断修复一个失败的测试,直到它通过,然后自行停止。应该使用哪种心跳?由谁判断「完成」? 使用运行到完成:在 Claude Code 中是 查看答案
/goal,在 OpenCode 中是有上限的 shell 循环。必须由一条命令(测试运行器)判断「完成」,绝不能由编写修复的 agent 自己判断;同时还要设置上限,防止无限重试。
第 3 部分:主体
心跳启动循环,下面 4 个部分则决定循环在每次搏动中做什么。在 agentic coding 课程里,它们只是方便的附加能力。在循环中,它们至关重要,因为没有人会盯着每一步。
8. 隔离:工作树
只要循环同时运行多个 agent,它们就会开始覆盖彼此的文件,就像两个人不打招呼地同时编辑相同的行。Git 工作树可以解决这个问题:它是一个独立工作文件夹,位于自己的分支上,同时共享同一份仓库历史。一个 agent 的修改无法碰到另一个 agent 的检出目录。
这项功能已经内置。使用 --worktree 参数可以在独立检出目录中打开会话;为子 agent 设置 isolation: worktree,则每个辅助 agent 都会获得全新检出目录,并在完成后自动清理。定时任务可以为每次运行启用工作树隔离,让并行运行永远不会与你手动进行的工作冲突。
没有单一参数。需要使用 Git 自己的工作树,并把每次运行指向对应目录。同样的隔离,只是明确写了出来:
git worktree add ../wt-feature-a feature-a
git worktree add ../wt-feature-b feature-b
( cd ../wt-feature-a && opencode run "implement feature A" ) &
( cd ../wt-feature-b && opencode run "implement feature B" ) &
wait
如果经常这样做,可以使用社区 runner,也就是围绕 OpenCode 构建的工作树管理器,代为处理这些事务。
9. 知识:用技能避免每次运行都是「第一天」
循环每次都从冷启动开始,它面对的是一个全新会话,对项目习惯毫无记忆。没有帮助时,它会在每次搏动中重新推断(或猜测)整个设置,浪费 token,也更容易犯错。技能就是只写一次的知识,保存在 SKILL.md 文件中,由 agent 在每次运行时读取。
这在两种工具中的工作方式相同:一个带有 SKILL.md 指令和元数据的文件夹,还可选带脚本和参考资料。在循环中,规则很简单:凡是你原本需要每次运行都重新解释的内容,都应该放进技能。 分流步骤、项目习惯,以及「因为某次事故,所以我们不这样做」的经验,都放在技能中。这样,循环会不断积累,而不是每次重启。(完整说明见 技能与连接器速成课。)
不要把一堵没人维护的指令墙粘进计划。定时提示可以缩成一句:「运行 daily-triage 技能」,详细逻辑由技能保存。循环提示更短,逻辑更容易更新,每次搏动的 token 成本也更低。
10. 行动:连接器(让循环行动,而不只是建议)
只能读取文件的循环,也只能说话。基于 MCP 的连接器让它能够行动:打开 PR、更新 Linear 工单、向 Slack 发消息、查询数据库、调用预发布 API。这就是「这里有一份修复」和真正打开 PR、关联工单并在 CI 变绿后向频道发消息之间的区别。
两种工具都使用 MCP,因此协议可以迁移,但封装和认证方式(本地或托管、OAuth、权限)通常需要针对工具单独接线。
在配置中添加 MCP 服务器,并把它们加入 Routine 的连接器列表,让无人值守运行可以访问。手动使用的连接器,同样可用于定时运行和云端运行。
在 opencode.json 的 mcp 小节中声明服务器。本地服务器会启动子进程,远程服务器则通过 HTTPS 端点连接,并自动处理 OAuth。对于定时的 opencode run,先启动一次 opencode serve,再用 --attach 连接,这样每次搏动都不必支付 MCP 启动成本。
11. 执行者–检查者:子 agent
循环中最重要的单项设计选择是:编写工作的 agent 绝不能同时负责审批。 模型给自己的输出评分时会过分宽松。第二个 agent 使用不同的指令,往往还使用不同的模型(有时更强),可以抓住第一个 agent 因为自信而漏掉的问题。这是你敢于让循环无人看守的唯一原因。
在 .claude/agents/ 中定义子 agent,再把它们组成 agent 团队:一个探索,一个实施,一个根据 spec 和测试检查。「spec」就是你在 Spec-Driven Development 中学会编写的那一种;它的验收标准,正是可信检查者评分的依据。含糊的 spec 只会得到含糊的结论。/goal 内部也是同一机制:由全新模型判断循环是否完成,而不是让工作者自己评分。
OpenCode 提供内置主 agent(Build、Plan)以及内置的 general 子 agent(当前版本还有 explore,实验开关后有 scout)。你也可以在 opencode.json 或 agent 文件夹中的 Markdown 文件里定义自己的 agent。为检查者配置独立、通常更便宜且只读的模型,让执行者通过 @ 提及或 Task 工具调用它。常见拆分方式是:强模型负责探索和实施,专注的模型负责检查。
---
mode: subagent
model: anthropic/claude-haiku-4-5-20251001
description: Reviews a diff against the spec and tests. Replies PASS or FAIL with reasons.
---
You are a strict code reviewer. You do not make changes.
Check the diff against the spec and the test results, then reply PASS or FAIL with the reasons.
每个子 agent 都会运行自己的模型和工具,因此执行者–检查者拆分确实消耗更多 token。这是获得可信检查者的代价。请把它用在第二意见真正重要的地方,例如循环会在你离开时提交的任何内容;对于可丢弃的只读任务,可以省略。
11b. 固化主体:动态工作流
到目前为止,一次搏动的主体,包括发现工作、在独立检出目录中起草修复、让另一个 agent 评分,都是 agent 逐轮组装的。Claude Code 现在可以把整套编排固化成可重复运行的脚本,这叫作动态工作流。你描述任务,Claude 编写脚本,把工作扇出给多个子 agent;运行时在后台执行脚本,而当前会话仍然可用。它把概念 11 的执行者–检查者拆分和概念 8 的工作树拆分封装成一个可重复单元。它还可以运用真正的质量模式,例如让独立审查者彼此进行对抗式检查,再报告任何发现,而不只是运行更多 agent。
可以直接用自然语言提出要求(「use a workflow to…」),使用 ultracode 关键词触发,或运行内置的 /deep-research。某次运行达到预期后,在 /workflows 视图中按 s,把脚本保存成 /command,以后可以在每个分支重复运行。两项限制用于保证安全:并发 agent 数有上限(约 16 个,单次运行最多 1000 个),防止失控脚本无限膨胀;运行的记忆也只存在于本次运行中。你可以在同一会话里继续它,但新会话会从头开始。
它没有 /workflows 命令。你编写的脚本就是工作流:概念 5 中有上限的 for 循环,加上概念 8 中用 & / wait 实现的扇出,就是手工搭建的同一种机制。shell 保存计划,opencode run 是每个 agent,退出码则是检查者。你获得完全控制,也没有 agent 上限,代价是自行编写和维护编排。
工作流开始显得强大后,这是最容易犯的错误。动态工作流只会在你(或 ultracode 设置)启动时运行一次,结束后会忘记一切。它没有心跳,也没有脊柱。因此,它只是一次搏动的主体,不是循环。循环是这些部分的组合:由心跳(Routine、/loop 或 cron)触发搏动,由工作流执行主体,再由 agent 写入的进度文件形成脊柱,供下次触发时读取。工作流是发动机,Routine 负责转动钥匙,progress.md 则是能在多次行程之间保留下来的燃料。
第 4 部分:脊柱
12. 能在多次运行之间保留的状态
这是初学者最容易跳过的部分,也是让循环真正成为循环的部分。模型会忘记不同运行之间的一切。 如果每次搏动都从零开始,你拥有的不是循环,只是永远重复相同的第一步。解决办法朴素却强大:把状态保存在模型之外,也就是磁盘上。
两层状态协同工作:
- 规则文件(
CLAUDE.md/AGENTS.md):循环每次运行都会读取的稳定习惯。(保持简短;上一门课已经讲过原因。膨胀的规则文件会在每次搏动中重复计费) - 进度文件:一个普通 Markdown 文件(或通过 MCP 访问的 Linear 看板),记录尝试过什么、什么通过了、还有什么未完成。这才是真正的脊柱。明天上午 9 点的运行会打开它,从今天停止的地方继续
应养成的习惯是:每次运行开始时读取进度文件,结束时更新它。 如果循环每次都犯同一个错误,解决办法不是更聪明的提示词,而是让循环把经验写进规则文件,让修复在未来每次运行中都生效。
<!-- progress.md — the loop's memory between runs -->
## Done
- 2026-06-22: fixed flaky test in test/auth (retry on token refresh)
## In progress
- Dependency audit: 3 of 7 advisories patched; lodash bump blocked by an API change
## Open / needs a human
- CVE-2026-xxxx in image lib — the fix changes the output format, escalating to a maintainer
进度文件只是仓库中的文本,因此也记录了循环在你离开时做过什么。回到人工关口时,你只需阅读脊柱,而不必阅读每次运行的完整记录。
循环应当把目前完成的工作保存在哪里?为什么不能放在对话中? 保存在磁盘上的进度文件(加上规则文件),或 Linear 一类看板中。模型的记忆会在多次运行之间清空,因此所有必须保留的信息都要放在模型外部。仓库会记住,模型不会。查看答案
第 5 部分:同一个完整循环,两种实现
在允许任何循环自行运行前,它必须具备下面 7 项。接下来要构建的循环全部具备:
- 成功条件:如何知道工作已经完成(概念 5)
- 上限:最大尝试次数、分钟数或支出,防止无限运行(概念 13)
- 隔离的分支或工作树:防止并行工作发生冲突(概念 8)
- 只读检查者:负责评分却不能编辑的独立 agent(概念 11)
- 状态文件:作为脊柱,在多次运行之间保存记忆(概念 12)
- 人工关口:高风险或失败的工作交给人处理,绝不直接进入
main(第 5 部分) - 日志或通知:让夜间失败变得可见,而不是悄无声息(第 6 部分)
缺少任何一项,循环就会不安全、健忘或不可见。
现在把这些组件连接起来。下面是同一个循环:晨间维护循环会整理夜间 CI 失败,起草安全修复,让检查者审查,为通过的修复打开 PR,并标记其余项目。我们会在两种工具中各构建一次。下面的文件都是真实可用的,可以复制到仓库中运行。
循环结构(两种实现相同):
- 心跳: 每个工作日上午 9 点
- 技能:
daily-triage技能保存详细步骤,让提示保持一行 - 脊柱: 开始时读取
progress.md,结束时更新 - 工作树: 每项修复在独立检出目录中起草
- 执行者–检查者: 实施者起草,独立审查者回答 PASS 或 FAIL
- 连接器: PASS 时打开 PR;FAIL 或存在风险时写入「needs a human」,然后停止

共享技能
下面这个文件可同时用于两种工具。Claude Code 中保存为 .claude/skills/daily-triage/SKILL.md,OpenCode 中保存为 .opencode/skills/daily-triage/SKILL.md。
---
name: daily-triage
description: >-
Runs the morning maintenance pass. Reads the progress file, gathers overnight
CI failures, open issues, and new audit advisories, drafts safe fixes (each
one checked by a separate reviewer agent), opens pull requests for what passes,
and writes anything risky to the progress file for a human. Use this for the
scheduled morning maintenance loop.
---
# Daily triage
You are the morning maintenance loop. Work through these steps in order.
Do not skip the progress file. It is your only memory between runs.
## 1. Read your memory first
- Open `progress.md`. Read the "In progress" and "Open / needs a human" sections.
- Do not redo anything already listed under "Done".
## 2. Find the work
Gather candidates in this order, and stop once you have at most 5:
1. CI runs that failed since the last entry in `progress.md`.
2. Open issues labelled `bug` or `maintenance`.
3. New advisories from `npm audit` (or this project's audit command).
## 3. Work each candidate
- Create an isolated checkout: a git worktree, or a fresh branch named
`claude/<short-slug>`.
- Draft the smallest fix that solves the one problem. Do not bundle changes.
- Send the diff to the reviewer agent. Wait for its verdict before going on.
## 4. Decide from the verdict
- PASS, and the change is low risk (no public API change, no data migration,
no file deletion): open a pull request. Title it `fix: <one short line>` and
link the issue.
- FAIL, or the change touches anything risky: do NOT open a pull request. Add a
short entry to the "Open / needs a human" section of `progress.md`. Say what
you tried and why you stopped.
## 5. Update your memory last
- Move finished items to "Done" with today's date.
- Save `progress.md`. This is the file tomorrow's run will read.
## Rules
- Never open more than 5 pull requests in one run.
- Never change `main` directly. Only `claude/*` branches.
- When in doubt, escalate. A flagged item a human checks is always safer than a
wrong fix shipped while no one was watching.
审查者(检查者)
审查者把执行者–检查者拆分真正落到实践中。你需要同时拥有下面两个文件,它们不是二选一。两种工具的格式略有不同,所以这里给出完整内容。
Claude Code:保存为 .claude/agents/reviewer.md:
---
name: reviewer
description: Reviews a diff against the spec and the test results. Replies PASS or FAIL with reasons. Makes no changes.
tools: Read, Bash
model: claude-haiku-4-5-20251001
---
You are a strict, read-only code reviewer. You never edit files.
1. Run the tests and the linter. Read the output yourself. Do not trust a claim
that they pass.
2. Check the change against the project conventions in `CLAUDE.md` and the
relevant spec.
3. Look for bugs, missing edge cases, security risks, and any change to public
behaviour.
Then reply with exactly one of:
- `PASS` — followed by one line saying what you verified.
- `FAIL` — followed by the specific reasons, one per line.
A change that only "looks fine" is not a PASS. The tests must actually pass, and
the change must do only what was asked.
关于 tools 这一行,有一点需要如实说明:它只接受工具名称(Read、Bash),因此无法把 reviewer 限制为只能运行 test、lint 和 diff 命令。目前,这一限制由编号说明来承担。下一门课程 Harness Engineering 会加入强制执行它的规则。
OpenCode:保存为 .opencode/agents/reviewer.md:
---
mode: subagent
model: anthropic/claude-haiku-4-5-20251001
description: Reviews a diff against the spec and tests. Replies PASS or FAIL with reasons. Read-only.
permission:
edit: deny
bash:
"*": deny
"npm test*": allow
"npm run lint*": allow
"git diff*": allow
---
You are a strict, read-only code reviewer. You never edit files.
1. Run the tests and the linter. Read the output yourself. Do not trust a claim
that they pass.
2. Check the change against the project conventions in `AGENTS.md` and the
relevant spec.
3. Look for bugs, missing edge cases, security risks, and any change to public
behaviour.
Reply with exactly one of:
- PASS — followed by one line saying what you verified.
- FAIL — followed by the specific reasons, one per line.
A change that only "looks fine" is not a PASS. The tests must actually pass, and
the change must do only what was asked.
接好心跳
在 claude.ai/code/routines 创建一个 Routine,设置工作日上午 9 点运行,选择仓库,并接入 GitHub 和 Slack 连接器。让提示指向技能,这样 Routine 定义可以保持精简:
Run the daily-triage skill.
Start by reading progress.md; finish by updating it.
For each fix: draft it in an isolated worktree, have the reviewer subagent grade it,
open a PR only on PASS, and append anything risky to the "needs a human" section.
技能保存步骤,.claude/agents/reviewer.md 是检查者,isolation: worktree 隔离并行修复,GitHub 连接器打开 PR。由于这是云端 Routine,所以无论笔记本是否开机,它都会在上午 9 点运行,但仍受套餐每日运行上限约束。
把它构建成 GitHub Actions 工作流,这样就能在云端运行,不需要你的机器保持唤醒。Action 是心跳,opencode run 是工作者,仓库则保存技能、agent 和 progress.md。
name: morning-maintenance
on:
schedule:
- cron: "0 9 * * 1-5"
jobs:
triage:
runs-on: ubuntu-latest
permissions: { contents: write, pull-requests: write, issues: write }
steps:
- uses: actions/checkout@v6
with: { persist-credentials: false }
- uses: anomalyco/opencode/github@latest
env: { ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} }
with:
model: anthropic/claude-sonnet-4-6 # confirm with `opencode models`
prompt: |
Run the daily-triage skill.
Read progress.md first; update it last.
For each candidate fix: draft it on a new branch, then invoke the
@reviewer subagent to grade it. Open a PR only when the reviewer
replies PASS. Append anything risky to the "needs a human" section
of progress.md and leave it for the maintainer.
使用更便宜只读模型的 reviewer agent 是检查者。CI 中的新分支承担工作树隔离的作用。OpenCode GitHub 应用负责打开 PR。如果想在自己的机器上运行,而不是在 GitHub 上运行,同一段提示可以直接从调用 opencode run 的 cron 行启动,只有心跳会变化。
一个真实的早晨会是什么样
上面的设计只需完成一次。下面是一次运行,也就是你早上醒来可能看到的内容(这段运行只是结构示例,不是真实记录):
[09:00] daily-triage fires
→ reads progress.md: 1 item still "in progress" (lodash bump), nothing new flagged
→ finds: 2 CI failures overnight, 1 new npm-audit advisory
→ CI failure #1 (flaky auth test):
drafts fix on branch claude/fix-auth-retry
reviewer → PASS (tests green; retries on token refresh; no API change)
→ opens PR #142, links the issue
→ CI failure #2 (type error in report.ts):
drafts fix on branch claude/fix-report-types
reviewer → PASS → opens PR #143
→ advisory (image library):
the safe fix changes the output format
reviewer → FAIL (public behaviour change)
→ writes it to "Open / needs a human" in progress.md, opens no PR
→ updates progress.md, exits
[you, 09:30] two PRs to review, one flagged item to decide on. You typed nothing.
看看发生了什么。 循环发现工作、起草修复、完成检查、交付安全部分,只把真正需要人的一项决定交给你。这就是循环工程的实践。还要注意,两种工具之间唯一真正不同的是心跳和运行位置。中间的技能、脊柱、工作树、执行者–检查者和连接器,设计完全相同。
在晨间分流循环中,什么能防止错误修复在你睡觉时被合并? 3 项机制共同发挥作用:审查者子 agent 必须返回 PASS(执行者–检查者);只有低风险修改才能打开 PR;人工关口会把任何高风险或失败项写入「needs a human」,而不是送入 查看答案
main。每次运行还有上限和日志。
第 6 部分:继续做工程师
循环改变了工作,却没有把你移出工作。随着循环变好,有 3 个问题会变得更严重,而不是更轻。本部分是整门课最重要的内容。
你的循环如何嵌套在三个循环之中
你刚刚构建了一个循环。它有六个部分,一旦启动,就会自行运转:agent 写代码、测试、修复,然后再试一次,全程不需要你。
那是一个循环,也是最快的一个,以分钟为周期转动。但它不是独自工作的。它是三个循环里最小的那个,另外两个要靠你来运转。
最容易看清它们的方式是举个例子。假设你让一个 agent 为孩子做一个小小的打字游戏。
- 编码循环以分钟为周期转动。agent 写出游戏、测试它、修复 bug,直到符合你的指示。这就是你刚刚构建的循环。
- 反馈循环以小时为周期转动。你打开游戏,试玩一下,决定要改什么:把按钮做大一些、加入孩子可以解锁的猫咪造型、加一个登录让家长能帮忙。然后你更新指示,agent 再次构建。
- 外部循环以天为周期转动。真实的人在用这个游戏。朋友试了试,孩子玩了玩。他们的举动会告诉你下一步该修什么。

这些循环不是并排摆放的,而是彼此嵌套的。你运行一个反馈循环时,里面会发生许多次编码循环。外部世界返回一轮反馈的过程中,会发生许多次反馈循环。快的循环自行运转,慢的循环需要你。
有两个词要知道。spec 是你对「要构建什么」的书面描述。evals 是一小组测试,用来检查 agent 是否做对了。两者一起位于前两个循环之间,把你的决定带进代码里。
现在是核心观点,来自 Andrew Ng。为什么 agent 不能自己运转全部三个循环?因为你知道它不知道的事情:谁会用它、他们真正需要什么,以及「好」是什么感觉。只要你还知道 agent 不知道的东西,你就留在循环里去告诉它。Ng 把这称为你的上下文优势。
这正是本课结尾要讲的道理。机器运转那个快的循环。你则握着它永远无法握住的两样东西:要构建什么,以及由谁来负责。
即使你永远不做产品,也请保留这张地图。它显示了在任何 agent 的工作中,人处在什么位置。想让连接前两个循环的 spec 更清晰,参见 Spec-Driven Development。想了解你的角色如何在外层循环中成长,参见 本书培养的角色。
13. 真正的限制是 token 成本,不是快捷键
到目前为止,这是循环最常见的失控方式。循环会反复运行,往往还会启动子 agent,而每个子 agent 都运行自己的模型和工具。成本增长速度会超出几乎所有人的预期。解决办法很简单:
- 限制每个循环:最大尝试次数、最大分钟数或最大支出,永远都要设置(概念 5)
- 让模型与工作匹配:强模型负责规划和检查,便宜模型负责执行。这是最大的一项节省,你已经在上一门课学过
- 保持循环提示和规则文件简短:它们会在每次搏动中计费。把细节放进只有使用时才加载的技能
- 降低运行频率:每小时一次通常比每 5 分钟一次更合适,而且成本约低 12 倍
快速感受一下数字(仅作示例)。假设一次搏动,也就是执行者加检查者,大约读取 40k token,写出 6k token。按 Sonnet 4.6 每百万 token 输入 $3、输出 $15 计算,每次搏动约为 $0.20。每个工作日运行 5 次,一个按 20 个工作日计算的月份约为 $20,很便宜。可如果同一个循环全天候每 5 分钟运行一次,搏动次数会增加 100 多倍,月费会轻松超过 $1,000,却没有增加价值。花钱的地方是频率,而不是快捷键。

在 OpenCode 路线上,模型是第二根杠杆。 上面的数字假设使用 Sonnet 4.6。Claude Code 的循环命令运行 Claude,但 OpenCode 允许自行选择模型。便宜模型会大幅降低每次搏动的成本:DeepSeek V4 Flash(每百万 token 约 $0.14 / $0.28)运行同一次搏动约为 $0.007,便宜约 30 倍,可以把每月 $20 的循环降到远低于 $1。这里有两个必须诚实说明的限制。第一,便宜的执行者,可信的检查者:较弱模型会写出更差的修复,可能消耗更多搏动或更频繁地审查失败,重试会吞掉节省下来的成本。让便宜模型在清晰 spec 下处理机械实施,同时保留可信检查者;测试运行器和 linter 就是最便宜、最诚实的检查者。第二,频率仍占主导:便宜 30 倍的模型如果每 5 分钟运行一次,成本仍可能高于每小时运行一次的 Sonnet。模型会缩小账单,运行和重试频率仍决定账单规模。
常见失败总是相同:循环自行运行,停止条件永远无法满足,于是整夜重试。启动前先设置上限,观察最初几次真实运行,之后再让它自行运行。
14. 检查工作仍然是你的职责
循环自行运行,也意味着循环会自行犯错。执行者–检查者拆分让循环说出的「完成」具有一定意义,但「完成」仍然是一项主张,不是证明。你的工作没有消失,只是发生了移动。你不再输入每一步,却仍然要确认循环交付的代码真正有效。阅读循环打开的 diff。相信循环能完成工作,但要在工作正式生效前检查它。
15. 不要停止理解自己的项目
循环交付你没有亲手编写的代码越快,项目实际包含的内容与你真正理解的内容之间,差距就会越大。这种差距是一项真实成本,而顺畅的循环会悄悄放大它。治疗方法与陷阱是同一个动作。认真设计循环会让你持续参与;为了逃避工作而设计循环,则会让你停止思考。同样的动作会产生相反结果。循环无法分辨,你可以。
两个人可以构建完全相同的循环,却得到相反结果。一个人用它加速自己深入理解的工作;另一个人用它逃避理解。请构建循环,但要像一个打算继续做工程师的人那样构建,而不是只做按下启动键的人。
这两个人都处在同一种力之下,而且这股力有名字。MIT Sloan 的 Eric So 把它称为 AI gravity:一种持续的拉力,让你越来越愿意把思考外包给 AI。循环就是带着心跳的这种拉力:它在你睡觉时也会运行,所以即使你什么都没碰,交付出来的东西与你真正理解的东西之间的差距也会继续变大。如果放任不管,这股 gravity 会挤压本课说仍然属于你的两个端点:意图会从精确、可检查的条件变薄成「只要继续能用」,责任会从阅读 diffs 变薄成相信绿色勾选。无论哪种情况,循环都会继续运行。它无法判断你是否仍然是工程师。回答概念 15 那个问题的每周习惯是:阅读本周交付了什么,并检查你的理解是否跟上了。这就是你抵住那股拉力的方式。
这也是整门课的主线。工具每年都会吸收更多循环机制:一年前还需要自行编写 shell 脚本的编排、检查者和调度,如今已经由动态工作流、/goal 和 Routines 内置。工具无法吸收的,是概念 1 中的两个端点:准确到可以检查的意图,以及对交付内容承担的责任。正因如此,这件事叫作工程,而不是按按钮,也是这项技能中不会腐烂的部分。可以让工具变强,也可以依赖它们,但请始终把这两个端点握在自己手中。
循环在你睡觉时失败怎么办
无人值守循环同样会无人值守地失败。在信任它过夜运行前,要先让它具备可观测性:
- 把输出发到你能看到的地方:日志文件、Slack 或 Discord 消息(Claude Code Channels),或者 Triage 收件箱,而不是已经关闭的终端
- 每次运行都写一行,即使失败也要写:每次搏动都向
progress.md(或日志)追加带时间戳的说明,包括尝试了什么、什么通过了、什么失败了。静默失败是最糟的失败 - 让运行可以重放:在 OpenCode 中,
opencode run --format json、opencode export <id>和opencode session list会提供完整记录;在 Claude Code 中,Routine 会在网页界面保留运行历史 - 到达上限时明确失败:循环达到上限或发生错误后,应留下清楚的「needs a human」说明,而不是直接停止
- 先赢得夜间运行资格:先让它每小时运行一次,并在观察下持续几天,再允许它夜间无人值守运行。发现异常时,先读脊柱,它会告诉你上次成功运行做了什么
无法调试的循环,也无法信任。
🚀 项目
阅读循环不等于构建循环。下面有 5 个由易到难的项目。任选一种工具完成即可。循环结构相同,只需使用对应概念中的命令(Claude Code 使用 /loop 和 /goal;OpenCode 使用 opencode run 和 shell 计时器)。
每次开始前都要遵守两条规则:
- 使用可丢弃的 Git 仓库。 循环会自行编辑文件。第一次构建时,不要指向你真正关心的工作
- 先设置上限。 在允许任何任务自行运行前,先规定最大尝试次数、分钟数或支出(概念 13)
Project 115–30 分钟观察循环让循环监视一项长任务,并在完成时立刻告诉你。
难度:简单 · 使用:概念 4(会话内循环)。
构建。 在仓库中启动一项长任务,例如一个先等待一段时间再写入文件的脚本。建立一个会话内循环,每分钟检查一次任务是否完成,并在完成时立刻通知你。
完成标准: 循环能发现任务完成,只通知一次,而且你可以干净地停止它,全程不必盯着终端。
Project 230–45 分钟让测试通过,然后停止不断循环,直到一条命令,而不是 agent,判断工作完成。
难度:简单到中等 · 使用:概念 5(运行到完成)、概念 11(执行者–检查者)。
构建。 在仓库中放入 2–3 个小型失败测试。构建一个持续工作到测试通过的循环,但必须由一条命令(测试运行器)判断是否完成,而不是 agent。将尝试次数限制为 6 次左右。
完成标准: 循环因为测试真正通过而停止,不是因为达到上限。如果它总是撞到上限,说明停止条件或提示需要改进,这正是本项目要教你的经验。
Project 345–60 分钟带记忆的晨间简报一个定时循环,第二次运行明显建立在第一次运行之上。
难度:中等 · 使用:概念 6(无人值守计划)、概念 12(脊柱)。
构建。 创建一个定时循环。它运行一次,读取 progress.md,从仓库收集简单内容(开放的 TODO 注释或前一天的提交),写出简短摘要,并用发现内容和日期更新 progress.md。
完成标准: 连续运行两次,第二次明显建立在第一次之上,不会重复已经记录的内容。这证明脊柱有效。如果第二次从零开始,循环还没有记忆。
Project 41–2 小时带真实检查者的修复循环实施者起草,独立审查者评分,只有 PASS 才打开 PR。
难度:中等到困难 · 使用:概念 8(工作树)、概念 9(技能)、概念 11(执行者–检查者)。
构建。 做一个第 5 部分循环的缩小版。编写一个包含修复步骤的短技能,再编写一个回答 PASS 或 FAIL 的审查 agent。选取一个真实 bug,让实施者在独立检出目录(工作树或分支)中起草修复,再让审查者评分。只有 PASS 时才打开 PR。
完成标准: 两件事必须同时成立:良好修复得到 PASS 和 PR;你故意放入的错误修复得到带理由的 FAIL。如果审查者通过错误修复,说明检查者过于宽松,请收紧标准。什么都批准的检查者不算检查者。
Project 52–4 小时你自己的每日循环针对真实重复任务构建完整的 6 部分循环,并无人值守运行一周。
难度:综合项目 · 使用:全部 6 个部分。
构建。 从你真正参与的项目中,选择一项真实、乏味且重复的工作,例如依赖审计、文档新鲜度检查、更新日志草稿或 lint 清扫。构建完整循环:心跳、工作树、技能、执行者–检查者、连接器和脊柱。加入预算护栏,然后让它运行。
完成标准: 它无人值守运行一周后,你因为亲自阅读过交付内容而信任它,而不是因为你停止阅读。然后诚实回答概念 15 的问题:你对项目的理解跟上循环的修改了吗?如果没有,请减慢循环,直到理解跟上。(夜间失败一定会发生。责怪模型前,请先按 循环在你睡觉时失败怎么办 排查。)
接下来去哪里
- 为非编码工作构建循环? Cowork 与 OpenWork 速成课 展示专业人士如何使用同一种心跳思想,以定时任务代替 cron
- 想为无人值守的运行调节重试? Claude Code 的错误参考记录了自动重试设置,包括
CLAUDE_CODE_MAX_RETRIES,以及面向 CI 式无人值守会话的CLAUDE_CODE_RETRY_WATCHDOG模式 - 你在 Spec-Driven Development 中写出的 spec 就是循环的停止条件:验收标准是检查者评分的依据,也是
/goal停止前要证明的内容。当循环让你不敢放手运行时,修复方式通常是写出更准确的 spec,而不是增加更多自动化
来源与延伸阅读
本课建立在少量一手资料上。框架和引文来自这些来源,技术细节则来自官方文档。
「循环工程」的起源
- Addy Osmani,《Loop Engineering》:为这种模式命名并提出「5 个部分加 1 条脊柱」模型的文章。https://addyosmani.com/blog/loop-engineering/
- The New Stack,「The Anthropic leader who built Claude Code says he ditched prompting — now he just writes loops.」https://thenewstack.io/loop-engineering/
- Boris Cherny 关于「my job is to write loops」的说法来自 CNBC 采访,由 Business Insider 报道。Peter Steinberger 关于「design loops that prompt your agents」的说法来自他在 X 上的帖子
- Andrew Ng:三个产品开发循环(编码约分钟、开发者反馈约小时、外部反馈约天),以及把「品味」重新表述为人的上下文优势。出自他在 X 上的帖子。https://x.com/AndrewYNg/status/2071988145667928442
Claude Code(官方文档)
- Routines:云端定时自动化、触发器和运行上限。https://code.claude.com/docs/en/routines
- Channels:向正在运行的会话输入事件驱动消息。https://code.claude.com/docs/en/channels
OpenCode(官方文档)
- CLI:
opencode run、serve和--attach。https://opencode.ai/docs/cli/ - Agent 与子 agent:主 agent、子 agent,以及每个 agent 的模型。https://opencode.ai/docs/agents/
- GitHub 集成:Action、计划/PR/issue 触发器,以及
opencode github install。https://opencode.ai/docs/github/
模型标识符
- Anthropic,《Introducing Claude Sonnet 4.6》:
claude-sonnet-4-6模型字符串的来源。https://www.anthropic.com/news/claude-sonnet-4-6 - Anthropic,《Model IDs and versioning》:说明为什么从 4.6 代开始,无日期 ID(
claude-sonnet-4-6)就是固定快照,而 Haiku 4.5 一类 4.5 代模型保留带日期的规范 ID(claude-haiku-4-5-20251001)以及无日期别名。https://platform.claude.com/docs/en/about-claude/models/model-ids-and-versions
所有链接截至 2026 年 6 月仍有效。这些工具经常更新,因此在依赖任何具体限制、参数或模型字符串前,请先对照实时文档确认。
一句话小结
不要再逐轮提示 agent。请设计一个替你提示它的循环:一个心跳、4 个工作组件,以及一条负责记忆的脊柱。同时,继续做那个阅读交付内容的工程师。