循环工程(Loop engineering)

循环工程(Loop engineering)就是把你从「给 agent 写 prompt 的那个人」这个角色上换下来。改由你来设计那套替你做这件事的系统。 这里说的 loop,可以理解成一个递归目标——你定义一个目的,AI 就一直迭代直到完成。 它大致由五块积木组成,而 Claude Code 和 Codex 如今都凑齐了这五块。

我相信这或许就是我们和 coding agent 协作方式的未来。不过现在还很早,我自己也持怀疑态度,而且你绝对必须当心 token 成本(取决于你 token 充裕还是紧张,用量模式会差得离谱)。你也仍然需要某种办法保证质量不掉,关于 slop(粗制滥造的产出)的担心是站得住脚的。话虽如此,咱们还是来看看这到底是怎么回事。

@steipete 最近:「你不该再给 coding agent 写 prompt 了。你应该去设计那些给你的 agent 写 prompt 的 loop。」类似地,Anthropic 的 Claude Code 负责人 @bcherny 也:「我不再给 Claude 写 prompt 了。我让一堆 loop 跑着,由它们给 Claude 写 prompt、琢磨该做什么。我的工作是写 loop。」

好,那这些话到底是什么意思?

差不多有两年时间,你想从一个 coding agent 那儿拿到点东西,靠的是写一个好 prompt、给够上下文。你敲一段,读它返回什么,再敲下一段。agent 是个工具,而你自始至终都攥着它,一轮接一轮。这种玩法基本上要过去了,至少有些人觉得它会过去。

现在你搭一个小系统:它找出要做的活、把活分派出去、检查结果、记下做完了什么,然后决定下一件事——你让这个系统去催那些 agent 干活,而不是你自己去催。我之前写过跟它很相近的一个东西,agent harness 工程,也就是把单个 agent 跑在里头的那个环境造出来,还有工厂模型——那套造软件的系统。loop 工程比 harness 高一层。它就是 harness,只不过跑在定时器上,会生出小帮手,还会自己喂自己。

让我意外的是,这已经不再真是一个工具层面的事了。一年前你想要一个 loop,得写一大堆 bash,然后永远维护那堆东西,它是你的、也只属于你一个人。现在这些零件直接内建进产品里了。Steinberger 列的那张清单,几乎一对一地落在 Codex app 上,然后又几乎原样落在 Claude Code 上。一旦你注意到形状是一样的,你就不再纠结用哪个工具了,你只管设计一个 loop,让它不管你眼下用的是哪一个都照样能跑。

这五块零件,然后是一些备注

一个 loop 需要五样东西,再加一个记东西的地方。我先把它列出来,再逐一对应。

  1. Automations,按计划自动触发,自己去做发现和 triage。
  2. Worktrees,让两个并行干活的 agent 不会互相踩脚。
  3. Skills,把项目知识写下来,否则 agent 只能瞎猜。
  4. Plugins 和 connectors,把 agent 接进你本就在用的工具里。
  5. Sub-agents,让其中一个出主意、另一个来检查它。

然后是第六样,记忆。一个 markdown 文件,或者一块 Linear board,任何活在单次对话之外、记着做完了什么和接下来做什么的东西。听起来蠢得不值一提。但这正是每个长时间运行的 agent 都依赖的同一个把戏,我在长时间运行的 agent 里展开讲过——模型在两次运行之间会忘掉一切,所以记忆必须落在磁盘上,而不是待在上下文里。agent 会忘,仓库不会。

两个产品现在都凑齐了这五样。

这儿那儿的叫法稍有不同,但能力是同一回事。让我一个一个过,因为说实话,细节才是一个 loop 到底是稳稳兜住、还是悄悄到处漏水的分水岭。

Automations,这是心跳

Automations 才是让一个 loop 成为真正的 loop、而不只是一次性运行的东西。在 Codex app 里,你在 Automations 标签页里建一个,挑好项目、它要跑的 prompt、多久跑一次,以及它跑在你的本地 checkout 上还是某个后台 worktree 上。找到东西的那些运行会进一个 Triage 收件箱,啥也没找到的运行就自己归档了,这点挺好。OpenAI 内部就拿它们干些枯燥的活,比如每天的 issue triage、汇总 CI 失败、写 commit 简报、揪出上周某人埋下的 bug。而一个 automation 可以调用一个 skill,这样你就能让这个反复跑的东西保持可维护——你触发 $skill-name,而不是把一大堵墙的指令糊进一个永远不会有人去更新的计划里。

Claude Code 殊途同归,靠的是调度和 hooks。你可以用 /loop 按间隔跑一个 prompt 或命令,可以排一个 cron 任务,可以用 hooks 在 agent 生命周期的某些节点触发 shell 命令,或者你想让它在你合上笔记本之后还接着跑,就把整套东西推到 GitHub Actions 上。一模一样的思路:你定义一个自主任务,给它一个节奏,发现的东西自己送上门来,于是不必你绕来绕去地去查。

还有一个值得知道的会话内原语,它跟整篇文章要讲的东西更贴近。/loop 按节奏重跑。/goal 则一直跑下去,直到你写的某个条件真的为真为止,而且每一轮之后会有一个单独的小模型来检查你是不是完成了——这样写代码的那个 agent 就不是给它打分的那个。你给它类似「test/auth 下所有测试通过、lint 干净」这样的东西,然后走开就行。Codex 有同样的东西,也叫 /goal,它跨多轮一直干,直到某个可验证的停止条件成立,带 pause、resume 和 clear。同一个原语,两个工具都有,这差不多就是整篇文章的套路。

所以这部分是把活浮出水面的那一环。loop 的其余部分,是对这些活下手的那一环。

Worktrees,别让并行变成一团乱

你一旦跑起不止一个 agent,文件就开始打架,这就成了失败点。两个 agent 写同一个文件,跟两个工程师事先谁也没打招呼就往同样的行里提交,是一模一样的头疼事。一个 git worktree 能解决它,它是一个独立的工作目录,待在自己的分支上、共享同一份仓库历史,于是一个 agent 的改动根本碰不到另一个的 checkout。

Codex 把 worktree 支持直接内建进去,于是好几个线程同时怼同一个仓库也不会互相撞上。Claude Code 用 git worktree 给你同样的隔离:一个 --worktree 标志在自己的 checkout 里开一个会话,还有一个 isolation: worktree 设置,你把它贴在某个 subagent 上,每个小帮手就拿到一份用完会自我清理的全新 checkout。我在编排税里写过这一切里属于人的那一面——worktree 拿掉了机械层面的碰撞,但仍然是天花板,你的 review 带宽决定了你实际能跑几个,不是工具决定的。

Skills,让你不必每一次都从头解释你的项目

skill 是你不必像条金鱼一样、每次会话都把同样的项目上下文重新解释一遍的办法。两个工具用同样的格式:一个文件夹,里头一个 SKILL.md,装着指令和 metadata,然后是可选的 scripts、references、assets。Codex 在你用 $/skills 调用它时跑一个 skill,或者当你的任务跟 skill 的描述匹配上时它自己跑——这正是为什么一句又精准又无聊的描述胜过一句机灵的描述。Claude Code 做法相同,我在 agent skills 里把这套模式写过。

skill 也是让「意图」不再一遍遍向你收费的地方。我在意图债里论证过,一个 agent 每次会话都从冷启动开始,它会用一个自信的猜测填上你意图里的任何窟窿。一个 skill 就是把那份意图写在外面:那些约定、那些构建步骤、那句「我们不这么干,是因为出过那一次事故」,写一次,放在 agent 每次运行都会读到的地方。没有 skill,loop 每个周期都把你整个项目从零重新推导一遍;有了 skill,它就有点像在复利滚雪球。

有一点要拎清楚:skill 是写作格式,而 plugin 是你分发它的方式。当你想跨多个仓库共享一个 skill、或者把几个打包到一起时,你就把它们封装成一个 plugin。在 Codex 里成立,在 Claude Code 里也成立。

Plugins 和 connectors,让 loop 碰到你真正的工具

一个只能看见文件系统的 loop 是个很小的 loop。Connectors(建在 MCP 之上)让 agent 读你的 issue tracker、查一个数据库、打一个 staging api、往 Slack 里丢一条消息。Codex 和 Claude Code 都讲 MCP,所以你为其中一个写的 connector,在另一个里通常直接就能用。而 plugins 把 connectors 和 skills 捆到一起,于是你的队友一步就装上你的整套配置,而不是凭记忆把整个东西重搭一遍。

这就是「这是修复方案」的 agent,和「自己开了 PR、关联了 Linear ticket、CI 一绿就 ping 一下频道」的 loop 之间的差别。connectors 正是 loop 能在你真实环境里动手的原因,而不只是告诉你「如果它能、它会怎么做」。

Sub-agents,让动手的那个离检查的那个远点

一个 loop 里最有用的结构性手段,没有之一,就是把写的那个和检查的那个分开。写代码的那个模型,给自己的作业打分时实在太宽厚了。一个带着不同指令、有时还是不同模型的第二个 agent,能逮住第一个把自己说服了的那些东西。

Codex 只在你要求时才生出 subagent,让它们同时跑,然后把结果折回成一个答案。你把自己的 agent 定义成 .codex/agents/ 里的 TOML 文件,每个带一个名字、一段描述、一份指令,以及可选的 model 和 reasoning effort,于是你的安全审查员可以是个高强度跑的强模型,而你的探索者是个快速、只读的小东西。Claude Code 也一样,用 .claude/agents/ 里的 subagents,还有在彼此之间传递工作的 agent team。两个工具里通常的分工都是:一个 agent 探索,一个实现,一个对着 spec 验证。

这个观点我已经讲过两次,一次叫代码 agent 交响乐团,一次叫对抗式代码评审。它之所以在一个 loop 里尤其要紧,是因为 loop 在你没盯着的时候跑,所以一个你真正信得过的验证者,是你能走开的唯一理由。subagents 确实更烧 token,因为每个都得做自己的模型和工具活,所以把它们花在「第二意见值得为之买单」的地方。这其实也基本上就是 Claude Code 的 /goal 在底下干的事——一个全新的模型来判定 loop 是不是完成了,而不是那个干完活的模型,把「动手者和检查者分开」用到了停止条件本身上。

一个 loop 长什么样

把它们拼到一起,一个单独的线程就变成了一个小小的控制面板。这是我一直在用的一种形状。

一个 automation 每天早上在仓库上跑。它的 prompt 调用一个 triage skill,去读昨天的 CI 失败、还开着的 issue、最近的 commit,把发现写进一个 markdown 文件或一块 Linear board。对于每一个值得做的发现,这个线程开一个隔离的 worktree,派一个 sub-agent 去起草修复,再派第二个 sub-agent 对着项目 skills 和现有测试评审那份草稿。

connectors 让 loop 开 PR、更新 ticket。loop 搞不定的任何东西,都落进给我的 triage 收件箱。状态文件是整个东西的主心骨,它记着试过什么、过了什么、还有什么开着,于是明早的运行从今天停下的地方接着来。

然后看看你实际上干了什么。你设计了它一次。那些步骤你一个都没去 prompt。这就是 Steinberger 的整个观点变成了现实,而且在 Codex 里和在 Claude Code 里是同一个 loop,因为零件是同样的零件。

loop 仍然没替你做的事

loop 改变了工作,它没有把你从工作里删掉。 而且有三个问题,是随着 loop 变好而变得更尖锐、不是更轻松。

验证仍然在你身上。 一个无人值守跑着的 loop,也是一个无人值守地在犯错的 loop。你把验证者 sub-agent 从动手者那儿分开,整个理由就是让 loop 那句「做完了」有点分量,可即便如此,「完成」是一个声明,不是一个证明。我一直在重复AI 时代的代码评审里的那句话:你的工作是交付你确认过能跑的代码。

你的理解仍然会烂掉,如果你由着它。 loop 越快地交付你没写的代码,「存在的东西」和「你真正搞懂的东西」之间的鸿沟就越大。这就是理解债,一个顺滑的 loop 只会让它涨得更快,除非你去读 loop 造出来的东西。

而且是的,那个舒舒服服的姿态,多半就是有风险的那个。当 loop 自己跑起来,人会特别想干脆别再有自己的看法,它丢回来什么就照单全收。我把那个叫认知投降。设计这个 loop,在你带着判断力去做时是解药,在你为了逃避思考而去做时是助燃剂——同一个动作,相反的结果。

搭好这个 loop。继续当那个工程师。

我觉得这是我们工作会如何演变的一段预告。话虽如此,如果我不亲自评审代码,或者我完全依赖自动化的 loop 去修它,我的产品质量会受损。我多半会落进一个向下的螺旋,不停地把自己往一个更深的坑里挖。

话虽如此,尽管去搭你的 loop,但别忘了直接给你的 agent 写 prompt 仍然有效。关键全在于找到那个对的平衡。

loop 还会因人而异、给出不同的结果。两个人搭出一模一样的 loop,可以得到完全相反的结果。一个人用它在自己深刻理解的工作上跑得更快。另一个人用它来彻底逃避去理解那份工作。loop 分不出这个差别。你分得出。

这正是为什么 loop 设计比 prompt 工程更难,而不是更容易。Cherny 的观点不是说工作变容易了。而是说,那个杠杆点挪了位置。

搭好这个 loop。但要像一个打算继续当工程师的人那样去搭它,而不是只当那个按下启动键的人。