软件工厂,明与暗

软件工厂,就是让置于 harness 约束之下的循环规模化运行。你可以让人参与其中(亮灯工厂),在判断力、专注力与速度、故障风险之间做取舍;也可以不让人参与(黑灯工厂),让 agent 自行界定任务、构建并发布代码,而没有人真正阅读其中的细节。但如果人们不再阅读代码,也会逐渐不再理解自己的软件。现在最难的工作,是判断应该建立哪些检查,以及该把多大的自主权交给 agent。

“软件工厂”这个概念,可以追溯到 Bob Bemer 1968 年发表的论文《程序生产的经济学》。半个世纪以来,许多人一直梦想把软件开发变成一种可重复、可观测的生产过程,就像工厂冲压汽车零件,而不再依赖个人各自为战的手艺。过去,这个梦想大体上——虽然并非无一例外——都落空了,部分原因在于创意很难像零件一样批量生产。

但过去两年发生了足够剧烈的变化,现在已经值得重新审视这个古老的梦想。由于其中的一些细微差别很容易被一笔带过,我们有必要说得更准确一些:到底什么才是真正的新变化,哪些又可能只是披着新机遇外衣的老陷阱。

HumanLayer 联合创始人 @dexhorthy 最近在 AI Engineer World's Fair 上做了一场很精彩的演讲,题为 《仅有 Harness 工程还不够:软件工厂为何失败》,很值得结合这个话题观看。

循环是原子,工厂是规模化的循环

结构决定一切,而结构始于一个个小单元。整个体系其实就是三个层层叠加的概念:循环、harness 和工厂。

循环,就是一个 agent 反复执行一项工作:收集上下文、采取行动、检查结果,然后再次执行,直到满足某个条件。它是 agent 工作的最小单元,上面的一切都只是循环层层叠加。

循环工程(loop engineering)的要点,是不再逐轮向 agent 输入提示,而是设计一个替你提示 agent 的小型系统。

Harness 是循环周围的围墙:供它运行的沙箱、它能调用的工具、两次运行之间得以保留的记忆,以及用来判断何谓“完成”的门控。循环是行为,harness 则是承载这种行为的环境。

如果把一个没有 harness 的原始模型直接投入工作,它很乐意永远转下去。Harness 是模型周围让它变得有用、也能安全运行的一切。

软件工厂,就是许多置于 harness 约束之下的循环同时运行:工作从队列进入,经审核门控流入生产环境,而人从更高一层掌控整个系统。它不是一个更大的 agent,而是一张由循环组成的组织架构图。

最后一层范式转变,是从亲手写代码,转向构建和运行一个能够编写代码的工厂。工作的基本单位上移了一层:不再是单个代码 diff,而是循环、harness,以及它们之间的流转过程。

循环 → harness → 工厂。工厂不是更聪明的 agent,而是许多置于 harness 约束下的循环共同流向一道审核门控,由人掌控最外层循环。这就是工厂的示意图。

Dex 花最多时间讲解的核心幻灯片非常精彩,因为它像一张清晰的接线图,把原本只会被简单看成一个循环的东西呈现了出来。下面是我的理解:

工厂是一个闭环:意图和生产信号进入队列,harness 负责构建,自动检查与审核负责把关,部署负责发布,监控再把生产环境中的情况转回信号。工程领导者的愿景以及工程师本人提出的意图,会进入待办队列;事故和用户请求产生的信号,也会进入同一个队列。

Harness 所做的,无非是从队列里取出一项任务并为它构建一项变更。Harness 之后,是为了让变更足够安全、可以进入生产环境而设置的所有自动检查。借助 CI、测试、静态分析和各种扫描,这些检查可以同时轻松运行,完全不需要工程师有意识地介入。这里唯一的决策点是审核门控。变更获批后会被部署并在生产环境中接受监控,监控数据再反馈为最初启动这个循环的信号。

总体而言,图中几乎每个方框的成本都接近于零:生成、测试、扫描,都能以极低成本规模化运行。只有一个昂贵的方框始终难以扩展,那就是审核门控。那个闪亮的琥珀色方框代表“判断力”;关于开发能否变得更快、更频繁,争论的关键就在这里。

为什么称它为“黑灯”

黑灯工厂会真的关掉灯,因为车间里只有机器,而机器不需要灯光也能工作。黑灯软件工厂采用的是同一种做法:代码未经任何人阅读便直接发布,只由其他机器验证。

这个意象借自制造业。它最初描述的是实体世界,而不是数字世界:工厂关掉灯,由机器人完成工作。日本 FANUC 自 2001 年起就在运行这种“熄灯工厂”;2024 年,小米也开设了自己的高度自动化黑灯工厂。它们的共同点,是产品在没有任何人逐一查看的情况下完成组装并发货。流程中一旦去掉了“人的阅读”,软件工厂也就进入了“黑灯”状态。

我借用这个概念,并不是为了营造某种氛围,也不是把它当作贬义词。尽管“黑灯”听上去有些瘆人,它在这里表达的只是一个简单的物理事实:原来的工厂车间不再需要灯光。在软件世界里,车间就是代码 diff。编写 diff、审核 diff、发布 diff 的人都消失了,剩下的只有一份由构建它的机器来验证的 diff。

至少在一开始,这件事做起来出奇地容易。之所以容易,是因为缺失的审核步骤原本会挡住一切。去掉它之后,你会感觉团队端到端完成任务的吞吐量突然有了飞跃,仿佛突破了音障。但黑灯工作流虽然表面上轻而易举,想长期承受它暗藏的全部成本,却比看上去困难得多。

仅有 Harness 工程还不够

随着模型彼此互动并与现实世界交互,由编排、沙箱化原型和工具调用组成的 harness 会越来越强大、越来越有效。然而,面对代码库长期积累的增量变更,仅靠模型本身就存在一种难以避免的失效方式:它跟不上代码库质量的长期要求。我有充分理由相信,模型最终会输掉对抗理解债务的这场仗。

理解债务,是现有代码量与任何一个人仍能理解的代码量之间不断扩大的差距。黑灯工厂不会偿还这笔债;它会在测试一路保持通过的同时,以最快速度把债务背到身上。

这个区别很重要,因为模型确实擅长一些任务。但只要任务不是对代码库某个小范围立即做出改动,尤其面对复杂的 Brownfield(存量)系统,完全由模型自动编码就会遇到无法跨越的障碍。Greenfield(新建)应用、周末随手做的小玩具和业余项目有一个共同点:通常只需几个月的开发周期,就能让它们正常工作,或者至少足够接近可用状态。

但一个已经开发了十年甚至更久的企业系统完全是另一种情况;它必须在专业环境中,以专业的节奏持续维护。项目进行三到六个月后,你就已经淹没在无人读过的代码里。这样的环境,尤其是生产代码施加的种种约束,即使是强大的 agent 也会表现不佳;这与开发者用周末小项目享受的 vibe coding 体验形成了鲜明对比。

Dex 根据亲身经历指出,这是一个严重的失败模式,甚至需要费尽力气进行人工调试才能找出症结。他们曾让一个全自动代码工厂运行约四个月,其间没有任何人查看生成的代码。这个经历的背后,是两个彼此冲突的指标之间的取舍。一个是尽可能提高 token 利用率——我们目前把这个数字视为进展;另一个却被前者悄悄压低:在任何时刻,参与其中的人究竟还理解这个系统的多少部分。

黑灯工厂真正“大放异彩”的地方,是它能在测试始终保持通过的同时,飞快侵蚀一个原本整洁的代码库。最终清算到来时,不会出现戏剧性的“整个系统突然失控”时刻;它会安静地发生,而且来得很晚。

黑灯与亮灯工厂使用的是同一条流水线,只是开灯的位置不同。亮灯版本不只是把审核重新加到末尾,还会把人的判断提前到设计和架构阶段。瓶颈从来都不在生成。

软件工厂的根本约束,不是我们能产出多少代码,而是我们能以多快的速度验证这些代码。

反压(back pressure)是一条规则:你只能把自己能够低成本、可靠验证的那部分自主权交给循环,一寸也不能多。工厂真正的约束是验证,而不是生成。

无限的生成能力与有限且无法扩展的人类注意力始终相互拉扯,因此核心问题就在于廉价生成与受限审核之间的差距。看看这个漏斗:只要代表验证能力的瓶颈没有变宽,任务就必然淤积。正如 Dex 所说,问题不只是数量太多;我们真正遭遇的是劣质 PR 过剩。如果产量很高,却没有值得信赖的门控,批量制造缺陷就不可避免。这仍然是反压问题:自主权不能超出我们能够低成本、可靠验证的范围。

更深一层的问题在于,为什么模型能力提升,并不会自动缩小“能生成什么”与“能验证什么”之间的差距。让模型从架构优良的系统中学习,恐怕比让它通过简单测试困难得多。别忘了,衡量优秀架构的代价函数,不是以秒甚至分钟来计算,而是要经过数月乃至数年才能显现。我们实际上不可能计算出清晰的梯度,因此,一个期待复杂设计决策能得到明确、即时评价的系统,也就无法依靠优质范例完成训练。

生成端开口很宽,验证端瓶颈却很窄。加快生成,只会让验证瓶颈前的积压越堆越多。

重新把灯打开

亮灯工厂仍是同一条流水线,只是在需要判断力的地方保持照明。大部分构建工作依然由 agent 完成,但产物发布之前会有人阅读;只要错误决策的代价高昂,那里的灯就会一直亮着。

亮灯版本不是在流程末尾生硬地加上审核,而是把人的判断提前:在 agent 启动循环之前,就介入产品、设计与架构。

前期投入的一个小时有一个很大的好处:它会减少后续实现所需的时间。漫长而令人沮丧的代码审核,可以变成快速阅读一份两百行的方案。你可以在决策落地前先审核它,而不必等到后来钻进两千行生成代码,才追查当初究竟做了什么决定。有些决策代价高昂、影响长远,值得尽早让人参与,在成本层层累积之前把关。当然,即使前期投入了时间,有时仍然需要查看 diff。

你也许觉得这些做法听起来毫不光鲜。没错。安全网由一些再普通不过的架构实践组成,我们一直知道它们,却大多没有真正落实:使用良好的类型与方法签名,让编译器在错误进入生产环境之前将其捕获;设置测试切入点,以便固定行为并让变更可观察;合理组织代码,让下一位读者——无论人还是模型——知道去哪里寻找自己关心的内容;让调用栈保持简短清晰;明确划分组件边界,避免一项变更造成巨大的影响范围;采用依赖注入,以便替换其中的某个部分。这些都不是新东西。我们一直声称自己重视良好架构。但到了使用自动编码 agent 的今天,架构终于承担了第二项职责:成为一道成本低、难以蒙混过关的安全网,防住 agent 必然会犯的错误。

这张安全网必须存在于模型之外,因为模型本身不会提供它。那些让人感觉能力最强的编码 agent——包括 Claude Code 和 Codex——接受的强化训练针对的是它们自己的 harness 和工具;它们熟悉这个行业的各种工具和惯用方法,却并不擅长长期可维护性之类的问题。我们一直讨论的审慎架构,正是捕获这类债务的工具;对架构的投入,就是我们为重新获得自主权所付出的代价。

把这种架构与安全的基础设施结合起来,就可以让一些边界明确、风险低的循环无人值守地运行。Horthy 在最近的一篇文章中介绍了一个例子:每晚由 GitHub Actions 定时任务只修一处反模式——比如一处 lint 违规,或一个没必要设为可选的 prop——然后自行提交并创建一个很小的 pull request。第二天团队醒来时,代码库会比前一天好一点,而且 diff 短到足以轻松读完。但对于风险足够高的循环,你肯定不愿一觉醒来,发现认证系统、计费引擎或公共 API 契约已经损坏。这些地方要保持亮灯,并相信一个具备判断力、真正熟悉系统工作方式的人能够发现错误。

什么样的循环才配关灯运行

无论你把这条规则称为反压、验证,还是电灯开关,它都同样适用。

一个循环只有在检查成本低、运行频率高,而且依赖不容易被蒙混过关的判定依据时,才有资格完全自动化。只给出通过或失败两种结果的判定器、类型门控、属性测试,以及配合真实评分准则的审核 agent,都符合要求。这个判定器还必须立即给出答案,并且不能随时间逐渐偏移。当“完成”不仅可以由你证明,也可以由机器证明时,自动化才真正成立。

短循环比长循环更容易验证。Dex 的经验法则是:agent 在三到十个步骤内还能保持良好表现,超过二十步后就开始跟丢主线。原因是上下文会不断累积;agent 拖着的内容越多,就越容易偏离方向。循环较短时,验证成本也低。范围失控的循环会把错误藏在角落里,换句话说,它们从未赢得关灯运行的资格。

反过来,保持亮灯的情况也很明确:如果错误答案代价高昂,而且只有人能发现,循环就需要接受人工审核。测试无法捕获的生产环境隐蔽 bug、影响范围巨大的变更,以及会影响未来一年甚至更久工作的决策,都属于这一类。在这些情况下,你的注意力才是真正的产品——昂贵,但不可或缺。

危险在于忘记分别拨动每个开关,而把所有循环一律设成同一种模式。全部关灯,四个月后你会被迫推倒重来;全部亮灯,所有审核都来不及完成,你又会困在一个巨大的瓶颈里。真正困难、也真正需要专业能力的工作,是决定每个开关应该放在哪里。

循环、图,还是状态机?

你应该读一读 @DavidKPiano 的《两分钟理解状态机》。

当你把一项任务交给 agent 时,多半会在它周围构建一张图,无论你称这张图为有限状态机,还是一组按条件连接的服务调用。在这种思路下,软件不只是遵循某些抽象规则,而是沿着结构化工作流运行:每个节点都是一个明确步骤,节点之间的每条边都是一个明确条件。

这听上去结构很多,但大部分结构原本就存在于任何软件中,因为任何代码都可以表示为控制流图。因此,唯一真正的新意,是坚持自主行动的 agent 实际上只是在某张特定的图上行走,它的自由仅限于一个节点内部。还有一点常被忽略,而 Dex 一年前就已经写了下来:软件本来就会具备这样的结构。我们过去之所以把程序画成流程图,是有原因的。

真正的新做法,是试图扔掉流程图,转而依靠一个循环:由模型逐次选择工具调用路径,直到它自行宣布任务完成。这一度让人感觉获得了解放,直到它撞上一套已有十年历史的代码库。如今所有人重新发现的那套纪律——掌控自己的控制流——其实只是把图重新套回循环周围。因此,讨论是否应该从循环转回图,几乎等于承认:我们从一开始就需要那张流程图。

看看实践中是什么样子。假设要修复一个 bug。如果把它当成纯循环,你坐下来想的是:弄清哪里出了问题、改一些代码、运行测试、看看结果;如果这一轮没有让整个任务中止,就回到开头再来一遍。整个过程都在行进中临时决定:追查哪个问题、具体改哪些代码、运行哪些测试以及按什么顺序运行、究竟要不要运行测试,还有是再试一次还是宣布胜利。

如果把它当成一张图,第一件事则是画出应该发生的流程:复现 bug,或者去询问更多信息;找出原因;尝试修复;运行测试;失败就沿路径返回修复步骤,通过则进入审核,只有获得批准才能抵达完成状态。Agent 在每个方框内部仍然很聪明,只是不能偏离你批准的路径。Santi 用一张图说明了这个过程,让两者的差别一目了然。

当然,这张图真正的吸引力,在于它把反压直接画了出来。你放弃 agent 的一部分自由,换来强制检查和清晰可辨的失败位置;一次运行失败时,你可以明确指出是哪个节点让它停了下来。Dex 有一句直白的话,背后也是同一种思路:大多数所谓的 agent 其实并没有多强的自主性,“基本上是确定性代码,只在恰到好处的位置撒入几个 LLM 步骤。”而且,这不仅是当下人们碰巧采用的构建方式:你可以在 LangGraph 和 LlamaIndex Workflows 中看到这种模式,也可以在 Jerry Liu 提出的“工作流图包裹 agent”的混合方案中看到——外层循环会在运行过程中扩展图的某些部分;David Khourshid 也提醒我们,这其实只是状态机和 Actor 模型换了一身新衣再次出现。

这里需要澄清一点,因为“图”这个词的含义被用得太杂了:我一直说的图,不是知识图谱,而是一张预先定义好的有向图,用来规定工作应该如何流转,其中也包括所有条件边。它让循环沿着一种你可以真正信任的结构运行。

人究竟去了哪里

请注意,人从未离开工厂,只是换了位置。

我认为,工程师需要越来越多地掌控外层循环。Agent 可以调查 bug、写出诊断、实现修复、运行测试,再提交一份报告。那是内层循环的执行工作,它们可以做得和任何人一样高效。但工程师真正的工作从来不是这些。你真正要负责的,是我所说的外层循环:判断这是否是解决问题的正确方式,验证诊断和实现是否可靠,批准变更,并承担判断错误的后果。两个循环之间的边界是证据——diff、测试、日志,以及一段把这些证据串联起来的简要说明。类型、测试切入点和评分准则,让你无需为每项变更投入大量工作,也能监督整个过程。

换个角度说:你不再站在生产线上亲手编写每项变更,而是站到生产线末端,设计整条生产线并把守关口。你可以做很多事来改善模型、增强 harness,但根据我的观察,那些长期代价高昂的问题通常无法靠自动化彻底识别。这份工作真正不变的核心,是作出任何文书流程和算力都替代不了的人类判断。

机器人在黑暗中作业没有问题,但人必须看得见自己在做什么。如果工厂车间里一片漆黑,你什么都看不见,甚至连电灯开关都找不到,危险就在那里。

Pangram 对本文进行了检测,判定其为 100% 由人类撰写。