我之所以翻译这篇文章,是因为看下来有挺多触动点,无论是技术层面还是非技术层面都是如此。

因为我最近也在开发 AI Agent。作者在末尾提到,认为未来的 Harness 想要让循环带来的变更在长期内保持可读,就需要找到一个更好的方法来组合这些越来越复杂的系统。

其实我觉得,我之前做的 NKG AI Follow 就是我自己给出的答案。在不断开发 Agent 的过程中,我不断总结、汇总并理解这些短板和缺点,最终给出了自己的答案,我觉得其实还挺适合的。

所以,我把这篇文章翻译出来给大家看一看,希望也能对大家有所启发。

原文地址:The Coming Loop,原文作者:Armin Ronacher,许可协议:CC BY-NC 4.0

即将到来的循环

我不再亲自提示 Claude 了。我有一些循环在提示 Claude,并判断下一步该做什么。我的工作是写这些循环。

– Boris Cherny

过去几个月,我看到越来越多人在编程 Agent 上面再搭一层东西。那种感觉,已经不是“用一个编程 Agent”这么简单了。有些发生在 Pi 上,这当然很酷;但模式到处都差不多:把工作扔进某个队列,一台机器接手,尝试完成,停下,然后由某个 Harness 判断这事是不是真的结束了。

如果还没结束,Harness 就接着跑同一个会话,塞进另一条消息;或者带着改过的上下文开一个新会话;再或者把任务转交给另一台机器。任务会越过模型本来会说“我完成了”的那个点,继续活着。

我想这种循环,想得比自己愿意承认的还多。

每个编程 Agent 里面,本来就有一个 Agent loop:模型调工具,吸收结果,再调下一个工具,读文件、改文件、跑测试,最后给一个回答。这个循环我们已经熟了很久。另一层是 Harness loop,也就是 Agent loop 外面的循环。它也不新鲜。Claude Code 早期我们就一直在做各种版本。但在 agentic engineering 里,这层循环越来越显眼;最近几周,它甚至开始主导 Twitter 上的讨论。

我还不擅长这个

我现在的状态是:对那些我真正上心的代码,这套工作方式还没给我带来多少成功。而事实证明,我上心的代码比自己想的多得多。

一半是品味,一半是控制欲。我会给代码设很高的门槛,也想理解自己交付出去的东西。遇到压力,或者和另一个人讨论时,我希望自己能解释系统到底做了什么,而不是先让一个铁皮家伙替我讲一遍。当然,这里有个问题:几年之后,我还会不会有这种“想理解代码”的愿望。至少现在,我还没走过“理解对我很重要”这一关。

也正因为如此,那些不是我亲自盯着写出来的代码,尤其是循环产出的代码,经常让我觉得少了点什么。今天的模型写代码,往往太防御、太复杂,推理也太局部。它们绕开强不变量。它们给坏状态加回退逻辑,而不是一开始就让坏状态无法出现。它们复制代码,发明糟糕的抽象,再用更多机制去遮住不清楚的设计。更糟的是,到目前为止,我几乎看不到这件事在变好。

如果有什么变化,至少在我看来,我们甚至可能在往错误的方向走。以我的品味看,如今 Claude Code 加 ultracode 这种几乎不用人工介入的 Harness,写出来的代码比去年秋天我们写出来的还差。比如 Claude Code 配合 Fable 时,会在一个问题上连续跑三十分钟甚至更久;以前,这个过程中会有更多人留在循环里。

另外,大家也知道,模型很容易看到一个局部失败,然后补一个局部防御。Karpathy 提过,它们“极度害怕异常”。在有重要不变量的系统里,尤其是持久化数据格式或核心基础设施里,正确的修复不是“处理每一种畸形情况”。正确的修复是让畸形情况无法被表示,或者从一开始就写不进去。可即使有人大量手动引导,这类代码也不太会自然地从 LLM 里冒出来;就算偶尔写成了这样,它们也还是会试着处理那些已经不可能发生的错误。

把这种行为放到循环后面,问题往往会被放大。每次迭代再加一点小防御,系统看起来更健壮了,却也慢慢变得更难理解。你越放手,这种事越容易发生。把这类工具在缺少明确指导的情况下交给初级工程师,还会教出很糟糕的实践。因为你问他们为什么这么做,他们往往能很有说服力地为自己的选择辩护。

循环在哪里有效

但与此同时,假装循环模式不起作用也不诚实。它已经在一些领域里跑出了很惊人的效果。

代码移植就是一个例子。现在已经有一些大型自动移植的精彩案例,包括据说围绕将 Bun 的部分代码从 Zig 移植到 Rust 的工作。我自己也成功用它把 MiniJinja 移植到 Go。性能探索也很适合这种玩法:机器不断尝试实验、跑基准、丢掉失败项,再继续搜索。安全扫描也一样。几乎任何研究类任务都类似:让一个系统探索复杂的问题空间,把结果报回来,但不一定提交一段需要长期存在的代码。

这些场景有一个共同点:它们要么不生成新代码,而是转换已有代码;要么产出的代码本来就没有很长的保质期。它们产出的可能是概念验证、想法、浮现出来的发现,或者更接近机械翻译的东西。

我相信,比起让 Harness 机械地度量一个目标,真正重要的是那些不产出长期工件的循环,或者那些能生成清晰、可验证的机械翻译的循环。许多成功的循环应用,会用另一个 LLM 当裁判或编排器。机械翻译可以用二进制测试用例验证,但也可以交给 LLM 判断。

比如 Claude Code 越来越擅长创建完整的实验工作流,然后自己执行。当然,它生成的代码很烂,但这更多是模型的问题,而不是 Harness 不能判断工作流里的某一步有没有带来净改善,或者任务有没有完成。

Harness 只需要一个能让它继续前进的信号。这个信号不必客观,也不必是二元判断;只要足够有用,能驱动下一次迭代就行。

我已经很喜欢那些循环了。它们能把我一天里实验、度量、找想法时最无聊的部分拿走。

软件作为有机体

另一方面,把同样的循环方法拿来写长期存在的代码,我现在还不太舒服。我喜欢借用的隐喻是:我们正在从“软件作为确定性的机器”,转向“软件作为有机体”。

我是在一个鼓励我理解机器的环境里成为软件工程师的。总有一层东西可以剥开,让你理解得更深。那些行为无法确定观测的机器,也许能被接受,但通常不会被视为特别理想。从软件架构角度看,我一直认为更值得追求的是更多确定性,而不是更少。理解代码,也一直是一个很难否认的目标。

实践中,这并不总是做得到。但我们仍然会为能写出这样的代码而自豪:靠聪明的架构,即使是新工程师也能在复杂代码库里找到路。在设计良好的系统里,总有人知道不变量在哪里,哪些部分承重,哪些改动安全。理想情况下,这些也都会被好好记录下来。哪里缺少这种理解,通常就意味着那里需要改进。

当然,这种理想一直承受着压力。许多软件系统,尤其是很成功的软件系统,都经历过团队里的工程师还能让它保持整洁的阶段。大型软件系统常常太大、太动态,也太依赖外部服务,没法装进任何一个人的脑子里。即使没有 LLM,我们诊断分布式系统时也已经有点像医生:观察症状,形成假设,“安排更多检查”,试一些疗法,再继续观察。

有了 LLM 之后,我们只是更深、更快地往那个方向走。我们用它们写代码,也用它们诊断和治疗问题。已经有不少工程师生活在这样的世界里:生产问题发生后,第一步就是让一个铁皮家伙读日志、提出根因,并主动提交补丁。随后,这个补丁又常常被另一台机器接手审查,有时甚至会在没有任何人工监督的情况下落到 main 上。

这当然很强大,我也不能否认它听起来很诱人。但向这个想法让步,尤其是在人工监督越来越少的情况下,等于接受一件事:我们可能不再以过去那种方式理解整个系统。我们治疗它、监控它、稳定它,但不一定理解它。

我毫不怀疑,对某些软件来说,这没问题。并不是每一行代码都值得由人类亲自创作,过去也不是没写出过更糟的代码。

但我想让所有软件都用这种方式被创作吗?

你并不能完全退出

真正让人不舒服的是,想完全退出这个由机器驱动的未来,可能并不是一个选项。

安全是今天最清楚的例子。即使你不用循环来构建自己的软件,别人也会用循环来对付你的软件。攻击者会持续运行机器;即便不是攻击者,安全研究员也会这么做。有些自动化工作只会扬起尘土,但也会找到真实问题。信号和噪声都会以几乎无法处理的规模涌向你,除非你自己也把一台机器扔进这个问题里。

Daniel Stenberg 关于 curl 的 summer of bliss 一文,是维护者已经承受这种压力的一个好例子。就我所知,AI 目前在 curl 的核心开发中还没有扮演很大的角色。即便如此,维护者已经被报告淹没,而其中大多数现在都是 AI 生成的报告。

如果攻击者和报告者都在循环,防御者最终也需要循环,才能跟上。也许不是直接写补丁,也许只是用来分诊和复现,但压力会增加。

竞争层面也一样。一些团队会靠原始速度超过另一些团队。有些项目会突然跑得更快,因为一个很小的团队弄明白了怎么有效编排机器。有些创业公司会用五个人完成过去五十个人才能完成的事。有人可能真的会把一台机器放进循环里,对着你的产品下命令:“把它做得像另一个一样。”如果他们的用户满意,这真的重要吗?

并不是所有软件都会受到同等影响。有些领域会惩罚草率,要求信任与责任;但大量软件生活在另一个世界里,那里最重要的是原始速度、快速实验和巨大的覆盖面。

新的依赖

对我来说,最可怕的部分是:我们会以新的方式依赖这些新机器。软件一直依赖工具。我还记得必须为编译器付费的年代。这些新工具让人闪回到创造软件需要付出真实成本的时期。但这一次不是一次性付款,而是一种持续依赖。不只是对充足钱包的依赖,也是认知上的依赖。

如果一个代码库由循环产出、由循环审查、由循环打补丁,并由循环维持生命,那么当你不再能访问同一类系统时,会发生什么?当某些贸易限制切断你对最强模型的访问时,会发生什么?如果只是成本变得无法承受呢?如果你和团队只是失去了最后那点不用机器也能理解代码的能力呢?

我们可能会创造出这样的代码库:它们不只是人类难以维护,而是把机器参与当成维护模型的一部分。这已经在发生了。它不是到处都在发生,发生的方式也未必都会被视为问题,但我们确实看得越来越多。人们越来越多地合并自己无法完整解释的代码。人们也在失去一种能力:不借助铁皮家伙提供的上下文,就无法写 issue 报告,或者在聊天里讨论问题。太多人越来越依赖机器来总结和补上下文。我也越来越常遇到那些隔着 LLM 这层中介和我对话的人。

再说一次,也许这甚至并不是错的。但它相对于我们过去做事的方式,确实是一个巨大的变化。

未来的 Harness

我几乎不怀疑,事情会往这个方向走。但要走到那里,我们需要把各处的工具问题一个个处理掉,而不只是处理编程 Agent。

光是编排更多循环还不够。把变更、编排或 Agent 可视化得更好,并不会自动恢复我们的理解。我们要么需要找到聪明的方法,把人类重新震回循环里,并让循环带来的变更在长期内保持可读;要么需要找到更好的办法,去组合这些越来越复杂的系统。

这也是我对 Pi 的角色思考正在变化的地方。Pi 一直很谨慎,而我认为这种谨慎是好事。我不想要一个每次交互都会变成失控机器群的未来,做出我跟不上的改动。我不希望 Pi 为了赢得“软件自己编写自己”的竞赛,变成一个无法维护的混乱体;也不希望 Pi 推广这种工程方式。与此同时,Pi 是一个 Harness,而 Harness 正处在人们运行这些新型实验的中心。

编码任务队列、Agent 编排、子 Agent、持久会话,会变得越来越重要。即使是那些对循环有所保留、并没有盲目拥抱循环的人,也不得不开始做这些实验。我们需要这么做,因为我们得理解怎样让这个未来有边界,并且能存活下来。

控制循环

正如你能从这篇文章里读到的,我对这个未来非常不安。不是因为恐惧,而是因为我基于目前的经验,对这项技术保持谨慎。

接受 Harness loop 这个想法,意味着由 Harness 决定工作什么时候完成。在 Agent loop 里,模型最终会说“完成”,然后我来审查。甚至在那之前,我通常也会一路引导。我参与其中,也享受沿途学习的过程。而在 Harness 操作的循环里,我甚至不确定自己的角色到底是什么。连“完成”信号都失去了原来的意义,只是被传给另一台机器去判断。我的角色被缩减成了一个信使。

今天,我并不喜欢从这类系统里看到的许多代码,也不享受和太多用 AI 辅助构建的软件打交道。循环很强大,但它越来越多地移走了责任。至少在今天,它很鼓励我们向机器让步。

可是我也毫不怀疑,这个循环的未来会成为我们的未来,尽管我现在还很厌恶它。我已经看到小到惊人的团队以不可能的速度构建东西,也看到代码库越来越像晦涩、混乱的有机体,只能靠更多机器来诊断。那些代码库既有用,又混乱。

所以我想,我正在接受这样一件事:问题不是我们会不会循环,因为很显然,我们会。也许真正的问题是,在一个循环的未来里,我们怎样不放弃判断,怎样保留良好工程的规则,怎样确保负责任的人类仍然可以继续监督,以及我们需要怎样重新思考代码架构,才能在一路向前时保住理智。