正在到来的循环
"我已经不再提示 Claude 了。我有一批循环在跑,它们负责提示 Claude、决定接下来做什么。我的工作是写循环。"
—— Boris Cherny
过去几个月,我看到越来越多的人在编码智能体之上构建某种东西——它和"使用一个编码智能体"有实质性的不同。其中一部分就发生在 Pi 之上,这当然很酷!但模式在哪里都一样:工作被放进某种队列,一台机器把它捡起来、尝试它、停下——然后由某个 harness 判断这是否真的算结束。
如果不算,harness 就继续同一个会话、注入另一条消息、用修改过的上下文开一个全新会话、或者把任务发给另一台机器。任务在模型自己本会说"我做完了"的那个时刻之后,继续活着。
我对这种循环的思考,比我愿意承认的要多。
每个编码智能体内部本来就有一个 agent loop:模型调用工具、消化结果、再调用另一个工具、读文件、改文件、跑测试,最终产出一个答案。这个循环我们早就熟悉了。另一个循环是 harness 层的循环:套在 agent loop 外面的那个循环。它也不新鲜——从 Claude Code 早期我们就在做各种版本——但它在智能体工程(agentic engineering)里的存在感越来越强,最近几周开始统治 Twitter 上的讨论。
我还不擅长这个
我目前的状态是:对于我深度在乎的代码——事实证明那是相当多的代码——这种工作方式还没给我带来多少成功。
一部分是品味问题,一部分是控制问题。我试图给代码的样子设一个高标准,而且我想理解我交付的代码。在压力之下,或者在与另一个人类的讨论中,我希望能解释这个系统在做什么,而不必先请一台"clanker"(铁皮家伙)解释给我听。当然,有一个显而易见的问题:几年之后我是否还会有这种"想要理解代码"的欲望。至少现在,我还没有走过"理解对我很重要"这个节点。
带着这个前提,那些在我不注意时写出来的代码——尤其是循环产出的——让我缺了点什么。当今的模型倾向于产出过于防御、过于复杂、推理过于局部的代码。它们回避强不变量(invariants)。它们添加兜底(fallbacks),而不是让坏状态不可能存在。它们复制代码、发明糟糕的抽象、用更多机械装置糊住不清晰的设计。更糟的是:我几乎看不到这方面在改善的迹象。真要说的话,我感觉我们甚至在往错误的方向迈步。至少就我的品味而言,当今的免管(hands-off)harness——比如带 ultracode 的 Claude Code——产出的代码比我们去年秋天产出的更差。因为 Claude Code 配上 Fable 会在一个问题上不受打扰地工作三十分钟以上,而以前这个过程要"人在回路"得多。
此外,一个众所周知的现象是:模型观察到某个局部失败,就添加一个局部防御。Karpathy 说过它们"对异常怕得要死"(mortally terrified of exceptions)。在有重要不变量的系统里——尤其是持久化数据格式或核心基础设施——正确的修复不是"处理每一种畸形情况",而是让畸形情况不可表示、或从一开始就写不进去。然而即便大量人工引导,这种代码也不会从 LLM 手里自然长出来;就算长出来了,它们还是会试图去处理那些已经不可能发生的错误。
把这种行为放进循环里,你往往会放大它。如果每次迭代都添一个小防御,系统会在看起来更健壮的同时,慢慢变得更不可理解。你越撒手,这种事发生得越多。当这类工具在没有清晰指导的情况下交给初级工程师,它还会教出真正糟糕的习惯——因为你要是问起为什么这么写,得到的会是振振有词的辩护。
循环在哪里管用
与此同时,假装循环模式不管用是不诚实的——它在某些领域已经好用得惊人。
代码移植是其一。已经有一些令人印象深刻的大规模自动移植案例,包括据报道 Bun 从 Zig 向 Rust 迁移的部分工作。我自己也成功用它把 MiniJinja 移植到了 Go。性能探索是另一个绝佳场景:机器可以做实验、跑基准、丢弃失败、继续搜索。安全扫描天然合适,几乎所有调研类工作也是:让系统探索一个复杂的问题空间然后汇报,未必要提交长期存在的代码。这些场景的共同点是:它们要么不产生新代码、只转换已有代码,要么产出的代码故意不需要长寿命。它们产出概念验证或想法、浮现发现,或者更像机械化的转换。
我认为,"循环产出无需长寿命的工件"或"进行某种可清晰验证的机械翻译",比"harness 能否机械地度量一个目标"更重要。许多成功的循环应用用另一个 LLM 当裁判或编排者。机械翻译场景可以用二元测试用例验证,但同样可以由一个 LLM 来评判!
比如 Claude Code 越来越擅长创建完整的实验工作流然后自己执行。当然,它产出的代码是烂泥(slop),但那更多是模型的锅,而不是 harness 不擅长判断工作流的某一步有没有带来净改进或完成。
Harness 只需要某种能让它继续下去的信号。它不必客观,不必二元,只要有用到足以驱动下一次迭代。
那些把我一天中无聊的部分拿走、替我实验、测量、给我点子的循环——我已经爱不释手。
软件作为有机体
另一方面,用同样的循环方法论去写长期存在的代码,我还是过不了自己这关。我喜欢用的隐喻是:从"软件作为确定性的机器"迁移到"软件作为有机体"。
我成为软件工程师的环境鼓励我理解机器。总有一层可以剥开,让你加深理解。不表现出确定性可观测行为的机器或许被接受,但通常被认为不太理想。在软件架构上,我认为朝更多确定性推进是可取的,而不是更少。同样,理解代码的能力是一个不容置疑的目标。实践中未必总能做到,但我们仍以此为傲:通过聪明的架构把代码写到连新工程师都能在复杂代码库里导航。在设计良好的系统上,总有工程师知道不变量住在哪里、哪些部分是承重的、哪些改动是安全的。理想情况下这些还都有良好的文档。哪里缺了这种理解,哪里就被视为需要改进的地方。
显然,这个理想一直承压。许多软件系统——尤其是非常成功的那些——在某些时期能被团队工程师保持干净。大型软件系统常常太大、太动态、太依赖外部服务,塞不进任何人的脑袋。即使没有 LLM,我们诊断分布式系统的方式也已经有点像医生:观察症状、形成假设、"多开几项检查"、试一些疗法、再观察。
但有了 LLM,我们在这个方向上推得更远、也快得多。我们用它们写代码,也用它们做诊断和补救。已经有大量工程师活在这样一个世界里:生产事故发生后的第一步,是让一台 clanker 去读日志、提出根因、主动挂出一个补丁。产出的补丁常常又被另一台机器接手审查,有时甚至在没有任何人类监督的情况下落到 main 上。
这显然很强大,我无法否认它听起来诱人。但接受这个想法——尤其是在越来越少的人类监督下——意味着接受我们可能不再以同样的方式理解整个系统。我们治疗它、监控它、稳定它,但我们不必然理解它。
我不怀疑对某些软件来说这没问题。不是每一行代码都配得上人类署名,过去也写过更糟的代码。
但我想要所有软件都以这种方式被创作吗?
你没法真正置身事外
非常令人不适的一点是:置身于这个完全机器驱动的未来之外,可能不是一个选项。
安全是今天最清晰的例子。就算你不用循环构建你的软件,别人也会用循环来对付你的软件。攻击者会持续跑机器;就算不是攻击者,安全研究员也会——其中一部分自动化工作会搅起不少噪音,但也会找到真问题。信号和噪声都会以一种几乎无法应对的体量涌向你,除非你自己也扔一台机器上去。
Daniel Stenberg 关于 curl 的"极乐之夏"的帖子,是维护者已经承受的压力的好例子。据我所知,AI 在今天 curl 的核心开发里没扮演什么重要角色。尽管如此,维护者们还是被报告淹没了——其中大多数如今是 AI 生成的。
如果攻击者和报告者在循环,防御者最终也需要循环才能跟上。也许不是直接写补丁,也许只是做分诊和复现——而压力只会增加。
竞争侧同理:有些团队会靠纯粹的速度把别人卷出局。有些项目会突然跑得飞快,因为一小群人搞清楚了怎么有效地编排机器。有些创业公司会用五个人做过去五十个人的事。有些人真的会把一台机器对准你的产品放进循环里,让它"做一个和那个一样的"。而如果他们的用户是满意的——这重要吗?
不是所有软件都受同等影响。有些领域会惩罚马虎、要求信任和责任;但大量软件活在一个"原始速度、快速实验、广覆盖"极其重要的世界里。
建立新的依赖
对我来说最可怕的部分是:我们以新的方式依赖上了这些新机器。软件一直依赖工具。我还记得要花钱买编译器的年代。这些新工具是对"创造软件有真实成本"的年代的闪回。但现在它不再是一次性付费,而是持续的依赖——不只是对钱包的依赖,还有认知上的依赖。
如果一个代码库由循环产出、由循环审查、由循环打补丁、靠循环维生——当你不再能访问同级别的系统时会发生什么?当某些贸易限制拿走你对最强模型的访问权时会怎样?如果仅仅是成本变得无法承受呢?如果你和你的团队只是失去了"不借助机器就能理解代码"的最后一点能力呢?
我们可能正在创造这样的代码库:它们不只是难以由人类维护,而是把机器参与假定为其维护模型的一部分。这已经在发生!不是到处发生,甚至未必以被视为问题的方式发生,但我们看到的越来越多。越来越多的人合并他们无法完整解释的代码。越来越多的人失去了不借助 clanker 提供的上下文来写 issue 报告、在聊天里讨论问题的能力。太多人越来越依赖机器来总结或补充语境。我越来越多地遇到隔着一层 LLM 间接与我对话的人。
再说一遍,也许这最终都不算错——但这是对我们做事方式的巨大改变。
未来的 Harness
我几乎不怀疑事情正往这个方向去。但要走到那里,我们必须对所有地方的工具动手,而不只是编码智能体。
仅仅编排更多循环是不够的。对变更、编排、智能体的更好可视化,都无法恢复我们的理解。要么我们找到聪明的办法把人类猛地拉回循环里、让循环的变更长期可读(legible),要么我们找到更好的方式来组合这些日益复杂的系统。
这也是我对 Pi 的角色的想法正在改变的地方。Pi 一直很谨慎,我认为这种谨慎是好的。我不想要一个"每次交互都变成一群不受控的机器做出我跟不上的变更"的未来。我不希望 Pi 为了赢得"软件自己写自己"的竞赛而变成一团不可维护的烂摊子,也不希望 Pi 去推广这种工程方式。但与此同时,Pi 是一个 harness,而 harness 正处在人们跑这些新型实验的中心。
编码任务的任务队列、智能体编排、subagent、可持久会话,会越来越重要。即便是我们这些心存保留、不盲目拥抱循环的人,也必须开始做这些实验。我们必须做——因为我们需要搞清楚怎样让这个未来是有边界的、可存活的。
控制循环
从这篇文章你能读出来,我对这个未来非常不安。不是出于恐惧,而是出于迄今为止与这项技术打交道的经验所给的审慎。
接受 harness 循环的想法,意味着由 harness 决定工作何时结束。在 agent loop 里,模型最终说"完成",然后我来 review。甚至在那之前,我通常一路都在掌舵。我参与其中,我享受一路学习。而在 harness 操作的循环里,我不确定我的角色还是什么。连"完成"信号都失去了全部意义,只是被传达给又一台负责评判的机器。我的角色被降格为一名信使。
今天,我不太喜欢从那样构建的系统里看到的代码,也不太享受与太多 AI 辅助构建的软件打交道。循环很强大,但它越来越多地移除责任,而且至少在今天,它非常鼓励我们向机器缴械。
然而我毫不怀疑,这个循环的未来将是我们的未来——尽管我目前对它心怀怨气。我已经看到小得惊人的团队以不可能的速度构建,也看到代码库越来越变成晦暗、混乱、只能靠更多机器诊断的有机体。那些代码库同时是有用的和一团糟的。
所以我想我正在与之和解:问题不是我们会不会循环,因为显然我们会。也许问题是——在一个循环的未来里,我们如何不放弃判断力,如何保住良好工程的规则,如何确保负责任的人类能继续监督,以及我们需要如何重新思考代码的架构方式,好让理智一路保得住。