软件工厂正在大规模驾驭循环。你可以在有人参与的情况下运行这个循环(亮工厂):用判断力和专注力去换速度与更少的破坏。也可以忽略人类(暗工厂),让那些代理去范围界定、构建并交付代码,而没有人真正去阅读细节。但如果人们停止阅读,他们也就会停止理解你的软件。你现在最难的工作,是知道该构建哪些检查,以及要下放多少自治权。
“软件工厂”这个概念可以追溯到 Bob Bemer 于 1968 年发表的论文《程序生产的经济学》(“The economics of program production”)。半个世纪以来,许多人都梦想着这样一个世界:软件是一种可重复、可仪器化的生产过程(类似于工厂里冲压汽车零件),而不是个人的孤立手艺。历史上,这个梦想大多(虽然并非总是)落空了,部分原因在于“冲压”出思想本身就很难。
但在过去两年里,情况发生了足够剧烈的变化,以至于现在重新审视这个旧梦想是有意义的。由于一些微妙之处很容易被一带而过,因此有必要较为准确地说明:究竟哪些才是真正新的、不同的,哪些又可能只是换上新外衣的老陷阱。
HumanLayer 联合创始人 Dex Horthy 最近在 AI Engineer World’s Fair 上做了一场很棒的演讲,题为“Harness Engineering 还不够:为什么软件工厂会失败”,非常值得一看。
结构就是一切,而一切都始于小单元。整个栈实际上是层层叠加的三个概念:循环、控制框架和工厂。
循环(loop)是单个代理反复执行一件事:收集上下文、采取行动、检查结果,然后再次重复,直到满足某个条件。它是代理式工作的最小单位,而其上的一切,不过是一个又一个循环的堆叠。
循环工程(loop engineering)的要点在于:你不再逐轮地提示代理,而是设计一个小系统,由它来替你提示代理。
控制框架(harness)是包围循环的墙:它运行在什么沙箱里、能接触哪些工具、运行间可以保留哪些记忆,以及哪些门槛决定“完成”意味着什么。循环是行为;控制框架是这种行为所处的环境。
把一个原始模型丢进没有控制框架的环境里,它会乐意一直转下去。控制框架就是围绕它、让它真正有用且可安全运行的一切。
软件工厂是在同一时间运行的大量受控循环,它们从任务队列中取活,并通过一个评审闸门流向生产环境,整个系统由人类从上层负责。它不是一个更大的代理;它是由循环组成的组织架构。
最后一步范式转变,是从编写代码转向构建并运行生产它的工厂。工作单位上升到了更高一层,变成循环、控制框架以及它们之间的流转,而不再是单个代码 diff。
循环 → 控制框架 → 工厂。工厂不是更聪明的代理;它是许多受控循环汇入一个评审闸门,由人类拥有外层循环。
Dex 花最多时间讲的核心幻灯片非常出色,因为它是一张澄清思路的接线图,把原本显而易见的循环可视化了。下面是我对它的理解:
工厂是一个闭环:意图和生产信号流入队列,控制框架负责构建,自动检查和评审进行把关,部署完成发布,监控再把生产环境的信息反馈回最初触发循环的信号中。
意图既来自工程领导层的愿景,也直接来自工程师本身,进入一个待办事项队列。由事故和用户请求驱动的信号,也推动同一个队列。控制框架只是从队列中取出一个事项,并为其构建一个变更。再往外看,我们能看到所有为了让变更足够安全、可以进入生产而必须执行的自动检查。这些自动检查几乎无需任何工程师的有意识参与,就能同时、轻松地运行——得益于 CI、测试、静态分析以及各种扫描。这里唯一的决策点是评审闸门。批准之后,变更会被部署并在生产环境中接受监控,而监控数据又会反馈到最初把循环启动起来的那些信号中。
总体而言,这张图里的每个方块几乎都是零成本的:生成、测试、扫描。它们都能以极低成本规模化运行。真正顽固、难以扩展的昂贵方块只有一个,那就是评审闸门。那个亮橙色方块就是“判断力”,而我们是否能够让开发更快、更频繁,争论的关键也正落在这里。
暗工厂是在物理上关灯运转的工厂,因为现场只有机器,而机器并不需要光来“看见”。暗软件工厂也是同样的做法:代码被交付出去,却没有任何人类读过,只由其他机器验证。
这个意象借自制造业。它的起源是物理世界而非数字世界,根植于那些灯光关闭、由机器人完成工作的设施。日本的 FANUC 自 2001 年以来一直在运行这种“熄灯工厂”;小米则在 2024 年开设了自己的高度自动化暗工厂。它们的共同点是:产品被组装并发货,却没有任何一个人类读过其中任何部分。所谓“暗”,就是当“阅读”这一动作从流程中被拿掉时出现的状态。
我借用这个概念,并不是因为它的氛围感,也不是为了贬义。尽管这个词听起来有些瘆人,但这里的“dark”只是一个简单的物理描述:原本的工厂车间,但没有灯光。在软件里,车间就是 diff。写出 diff 的人、审查它的人、交付它的人,这些人类都不见了,剩下的只是一个仅由构建它的机器验证过的 diff。
这件事出人意料地容易做到,至少起初如此。之所以容易,是因为缺少审查这一步会挡住一切。没有它,你对团队纵向吞吐量的感知会突然、剧烈地变高。感觉就像突破了音障。尽管看起来很轻松,但要在这种暗流程里生存下来,并承受其隐藏成本,远比表面上看起来更难。
编排、沙箱原型和工具调用这些控制框架,当模型与世界及彼此交互时,会变得越来越强大、越来越有效。然而,在长期、增量式变更的过程中,要维持代码库质量,模型内部本身就存在一种失效机制;我认为有充分理由相信,仅靠模型最终会输给理解债务(comprehension debt)。
理解债务,就是代码存在多少与任何人类仍能理解多少之间不断扩大的鸿沟。暗工厂并不会把它还掉;它只是尽可能快地把债务堆上去,而测试一路都保持绿色。
这一区别很重要,因为模型确实擅长某些任务。但对于不是代码库中某个小部分的即时改动、尤其是在复杂的遗留系统中,仅靠模型的自动编码会遇到不可逾越的障碍。周末玩具和副项目有相似之处:几个月的开发周期通常足以把事情做成能运行的样子,至少也差不多。但一个开发了十年甚至更久的企业系统则完全不同;它必须在专业环境中、以专业节奏持续维护。项目开始三到六个月后,你就已经被看不懂的代码淹没了。这种环境,尤其是生产代码施加的约束,足以让再强大的代理也表现不佳——这与开发者在周末玩具上进行 vibe coding 的体验形成鲜明对比。
Dex 基于亲身经验指出,这是一项重大失效,严重到需要费尽心力进行手动调试才定位出来。这来自一个完全自动化的代码工厂运行了大约四个月,在此期间,没有任何人看过生成的代码。这个经历背后,是两项相互冲突的指标之间的权衡。一项是最大化 token 利用率——这正是我们目前当作进展的数字。另一项则是它悄悄最小化的东西:在任意时刻,系统中仍有多少是任何人类参与者能够理解的。
暗工厂真正擅长的,是在测试始终绿色的情况下,把干净的代码快速消耗掉。最终的清算来临时,不会是戏剧性的“全盘出错”时刻;它会安静而迟缓地发生。
暗与亮,是同一条流水线,只是灯开在不同的位置。亮工厂版本不只是把评审加回到末尾——它还把人类判断前移到了设计和架构层面。
软件工厂的根本约束,不是我们能产出多少代码,而是我们能多快验证它。
背压(back pressure)规则是:你只能赋予一个循环与可被廉价且可靠验证相匹配的自治权,不能多一分。工厂真正的约束不是生成,而是验证。
由于无限的生成能力与人类注意力这一有限且无法规模化的资源之间永远处于张力之中,核心问题就是廉价生成与受限评审之间的鸿沟。看这个漏斗:只要代表验证的那个瓶颈没有变宽,它就一定会堵住。正如 Dex 所指出的,问题并不只是量大:我们真正遭受的是大量糟糕 PR 的过剩。当高产出缺少可信的闸门时,制造缺陷就不可避免。这又回到了背压:自治权无法扩展到超出可被廉价且可靠验证的范围。
第二层问题在于,为什么模型能力提升并不会自动抹平“能生成什么”和“能验证什么”之间的差距。在经过良好架构设计的系统上训练,或许比通过简单测试更难。别忘了,衡量架构卓越性的成本函数,不是按秒、甚至不是按分钟来测量,而是按月和年。整洁的梯度在功能上几乎不可能计算出来,所以一个期待对复杂设计决策进行清晰、即时评估的系统,不会被训练成真正擅长识别优秀范例。
生成口很宽;验证口很窄。把口子开得更大,只会让瓶颈处堆得更高。
亮工厂是同一条流水线,只不过把灯留在判断所在之处。代理仍然承担大部分构建工作,但在交付前,人类会先阅读产物,并且凡是错误代价高昂的地方,灯都保持开启。
亮工厂版本并不是把评审附加在末尾,而是把人类判断的时点前移到产品、设计和架构层面,在代理开始循环之前就完成。
这种前置的一小时有一个很棒的好处:它会减少后续的实现工时。它把一次冗长、令人沮丧的代码评审,变成对一份两百行计划的快速阅读。你可以在它被构建之前先审查一个决策,这样之后你就不必在两千行生成代码里到处追着找,才知道当初到底做了什么决定。有些决策代价高、影响久,值得在更早的时候就让人参与进来,让成本在复利之前被控制住。当然,即便你已经在前期投入了时间,还是会有一些时候需要看 diff。
你也许会觉得这一切听起来不够酷。没错。这个安全网由一些我们一直都知道、却大多忽视的、再普通不过的架构实践组成:良好的类型和方法签名,让错误在编译器那里而不是在生产环境中被捕获;可测试的缝隙(test seams),让我们能够固定行为并让变化可观察;合理的代码布局,让下一位阅读者——不管是人还是模型——都知道去哪里找自己关心的东西;保持调用栈短而清晰;保持组件边界定义明确,避免一次变更造成过大的爆炸半径;以及依赖注入,使我们能够把一个部件替换成另一个。这里面没有任何新鲜事。我们一直说自己重视良好的架构。但现在我们在使用自动化编码代理,这套架构终于又多了一项职责:成为一个廉价、且难以伪造的安全网,用来抵御代理会犯下的错误。
这个安全网必须存在于模型之外,因为模型不会自己提供它。那些最显得能力强的编码代理——包括 Claude Code 和 Codex——本质上都是在它们自己的控制框架和工具上接受强化训练的:它们对这个行业的工具和惯用法非常流利,但对长期可维护性之类的问题并不擅长。我们一直在谈论的那套刻意设计的架构,正是捕获这种债务的工具,而我们对它的投入,就是在把我们的自治权买回来。把这一点与安全基础设施结合起来之后,就会有一些紧凑、低风险的循环可以无人值守地运行。Horthy 在最近的一篇文章里描述过其中一个例子:一个每晚运行的 GitHub Actions 定时任务,每次只修复一个反模式、一个 lint 违规,或者一个没有必要设置为可选的 prop,自动提交并打开一个很小的 PR;第二天团队醒来时,代码库已经稍微好了一点,而且那个 diff 短到可以轻松读完。但对于风险足够高的循环,你可不想第二天醒来发现认证系统、计费引擎或公共 API 契约已经坏了。在这些地方,把灯开着,信任一个具备判断力、并且真正了解系统的人去捕捉错误。
这条规则无论你把它叫做背压、验证,还是电灯开关,都适用。
一个循环只有在以下条件都满足时,才配获得完全自动化的资格:检查成本低、运行频率高,并且依赖某种不容易被轻易伪造的东西。是非分明的绿/红判定器、类型闸门、性质测试(property tests),以及结合真实评分标准的评审代理,都是合适的。你还需要这个判定器能立即给出答案,并且不会随着时间漂移。当“完成”不仅能被你证明,而且能被机器证明时,你才真正进入了自动化。
短循环比长循环更容易验证。Dex 的经验法则是:一个代理能稳定撑住三到十步,但超过二十步后就会开始丢线索。原因在于上下文累积:它携带的东西越多,越容易跑偏。循环越短,验证成本越低。冗长的循环会把错误藏在角落里;换句话说,这说明它从来就没真正配得上熄灯运行。
保持灯亮则是相反的情况。如果一个错误答案代价高昂,而且只有人类才能发现,那这个循环就必须被审查。测试无法捕捉的微妙生产漏洞、很大的爆炸半径,以及会影响一年乃至更久工作的决策,都属于这一类。在这些情况下,你的注意力才是真正的产品,而且是昂贵但不可或缺的那一种。
危险在于忘记逐个切换,而是把所有开关都设成同一种模式。全暗,你会在四个月后不得不把一切推倒重来;全亮,审查永远做不完,你又会陷入一个巨大的瓶颈。真正困难、真正需要技能的工作,是决定每一个开关该放在哪里。
当你把任务交给代理时,你大概率会围绕它构建一个图,无论你把这个图称为有限状态机,还是一组条件连接的服务调用。这个框架意味着软件不是在遵循某些抽象规则,而是在执行一个结构化工作流:每个节点都是一个明确步骤,每条节点之间的边都是一个明确条件。听起来结构很多,但其实大部分结构在任何软件里本来就存在,因为任何代码都能表示为控制流图。所以,真正的新意只是:一个一再坚持自治的代理,本质上只是在某个特定的图里移动,而它的自由只被限制在某个节点内部。更重要的是人们常常忘记的一点,Dex 一年前就写下来过:软件本来就一直会有这种结构。我们过去之所以要画流程图,是有原因的。真正新的动作,是试图把图表扔掉,转而依赖一个循环,让模型一次次按工具调用决定路径,直到它自己宣布完成。那感觉像是一种解放,直到它撞上一个十年老代码库;而现在每个人重新发现的那种纪律——拥有你的控制流——其实只是把图重新绕回循环里。所以,从循环回到图的转变,几乎等于承认:我们从一开始就需要那张流程图。
下面看实践中是什么样子。拿一个 bug 来修。若把它当作纯循环来处理,你坐下来会想:先弄清哪里坏了,改一些代码,跑测试,看看结果,如果那一轮没把任务杀掉,就回到起点再来一次。整个过程中的每一步都由你临场决定:追哪个问题、改哪段代码、跑哪些测试、按什么顺序跑、要不要跑测试,以及是再试一次还是宣布胜利。若把它画成图,第一件事就是把该发生什么先规划出来。复现 bug,或者去要更多信息,找到原因,尝试修复,运行测试,让失败的运行回到修复步骤,而通过的运行继续进入评审,只有批准才能抵达完成。代理在每个方框内部仍然很聪明;它只是不能偏离你授权的路径。Santi 用了一张图把这种差异讲得非常清楚。
当然,这个图真正吸引人的地方在于:它把背压画成了一张图。你放弃了代理的一部分自由,换来强制检查和清晰可读的失败点;因此当一次运行挂掉时,你可以直接指向那个把它“杀死”的节点。这与 Dex 那句直白的话背后的直觉是一致的:大多数所谓代理其实并不怎么“代理”,而是“主要是确定性代码,只是在恰当的位置点缀了一些 LLM 步骤”。而这并不只是人们当前碰巧这样构建系统的产物:你能在 LangGraph、LlamaIndex Workflows 里看到这种模式,也能在 Jerry Liu 所说的“以代理为中心的混合工作流图”中看到——它带着一个外层循环,在运行过程中逐步长出图的部分;还可以在 David Khourshid 的提醒里看到:这其实不过是状态机和 actor 模型换了身新衣服。
再澄清一句,因为这个词容易严重歧义:我一直把它叫作图,但这里说的不是知识图谱。我指的是一个预先定义好的、有向图,描述工作应该如何流动,包括所有条件边,从而给循环赋予一个你真正可以信任的形状。
注意,人并没有离开工厂。他们只是移动了位置。
我认为,工程师需要越来越多地拥有外层循环(outer loop)的职责。代理可以调查 bug、写出诊断、实现修复、运行测试,并写出报告。这是内层循环的执行,它们可以和任何人一样高效地完成。但那从来都不是人类的工作。你真正拥有的部分,我称之为外层循环:判断这是不是解决问题的正确方式,验证诊断和实现是否可靠,批准变更,并承担判断失误的后果。两层循环之间的边界是证据:diff、测试、日志,以及把它们联系起来的简短说明。类型、缝隙和评分标准(rubrics)让你可以在不为每次变更付出大量劳动的情况下监督这一切。
我觉得这样表述很有用:你不再是在生产线上埋头写改动;你是在生产线末端设计它、守住闸门。你当然可以做很多事来让模型更强、让控制框架更有能力,但我的观察是,识别那些长期代价高昂的问题,通常不是你能通过自动化完全替代掉的。真正仍然属于人的核心工作,是比任何纸面流程和算力流更好地运用判断力。
机器人在黑暗中运作没问题,但人类需要看见自己在做什么。如果工厂地面上一片漆黑,你什么都看不见,甚至连电灯开关都找不到,那危险就在那里。
Pangram 将这篇文章评为 100% 由人类撰写。