今年我几乎把整条流水线都自动化了,而且我会拼命捍卫它保持这样。
现在大多数改动的初稿都由一个智能体来写。第二个智能体审查那份 diff——比我周五下午 5 点时的审查更严格,而且它审到第四百份也不会觉得无聊。测试自己跑。类型检查、lint、预发布环境部署、针对一个接近真实的数据库做冒烟检查。等一个改动到我面前时,已经有五个独立的自动化步骤看过它并说了 yes。它确实比我三年前手动操作的那套版本更好,而打字从来都不是我会怀念的那部分。
于是在某个 sprint 里,我做了那件显而易见的最后一步。我看着一个已经有四个绿色勾选和一份干净审查的 pull request,心想:为什么我的手还放在合并按钮上?我自己点这一下,到底添加了什么四个自动化的 yes 还没挣到的东西?于是我把最后一步也交给了智能体。生成、审查、测试、预发布、合并。完全闭环。大约有九天,它感觉像是未来。
然后其中一次合并让一位付费客户在凌晨两点被锁在了自己的账户之外,我为此付出了昂贵的代价——在生产环境里,在一通电话中——才明白这条流水线里哪一个步骤是我绝不该交出去的。它不是大多数人会猜的那个。它是几乎所有人最先自动化的那个。
那份 diff 很干净。这正是全部问题所在。
下面是它自己发布上线的经过。
这个改动是一条写入路径。它在真正把行持久化之前,就先确认了请求——把“收到”发回给了客户端。如果你干这行有一阵子了,你的胃现在一定沉了下去,因为你认识这个 bug。它很干净。它很地道。它有类型、有测试,而且读起来完美无瑕直到重试在错误的那一刻命中的瞬间:“收到”发出去了,保存却从未落地,而系统现在对一件从未发生过的事自信满满。
是作者智能体写的它。审查智能体读了它并批准了——我想对审查智能体公平一点,因为它很优秀:那个 sprint 里它抓到了两个我本会漏掉的真实问题。它只是没抓到这个一,因为这一个看起来不像 bug。它看起来像是完成的工作。测试是绿的,因为测试测的是正常路径,而这恰恰是这段代码正确的路径。预发环境没问题,因为没人在错误的毫秒对预发环境重试过请求。五个自动化的“是”,每一个在技术上都是诚实的,每一个都盯着 diff,却没有一个盯着凌晨两点。
它自动合并了。它自动部署了。在一个普通的糟糕夜晚,一次重试命中了,ack 发出去了,写入却没有,一个付我们钱的真实用户被锁在了自己的账户之外,日志里没有任何东西能证明他们曾经存在过。我至今还能感受到那通电话——不是因为那个 bug 有多离奇,而是因为在那条漂亮的闭环里,从头到尾都没有一个时刻,让那个会接到那通电话的人看过这东西并且承担发布它的责任。
我们称之为“审查”的两件事——它们并不是同一回事
这就是一个被锁在门外的客户教会我的区别,也是整篇文章的核心。
我们用一个词,“审查”,来指代两种完全不同的工作。
第一种工作是评判代码:它是否正确,是否地道,是否处理了边界情况,命名是否合理。那是一种技能。它是对海量既有代码和系统心智模型的模式匹配。而这恰恰是今年变得廉价的那类技能。一个好的审查智能体可以不知疲倦、始终如一地在凌晨 3 点、在第 400 个 diff 上做这件事,而不会有人类资深工程师那种让人在周五下午的 PR 上随手放行的疲劳。你绝对应该把这自动化。我这么做了,而且这是正确的决定。
第二种工作是为合并负责:接受这东西现在要面向真实用户了,如果它出错了,那责任在我。那不是一种技能。没有模式可以匹配。那是一个具体的、可问责的主体把自己的名字押在一个不可逆的、对外的决定上,并在事情变糟时承担后果的行为。
二十年来,这两项工作被焊在了一起,因为审查代码的人同时也是会被呼叫的人,所以我们从来不需要注意到它们是两回事。自动化把它们撕开了。机器现在能把第一项工作做得比我更好。它完全做不了第二项完全做不了——不是因为它不够聪明,而是因为第二项工作与智力无关。它关乎问责,而问责需要某种能真正承担后果的东西。
你可以自动化判断。你无法自动化问责。
一个合并了糟糕 diff 并搞垮 prod 的智能体无法被呼叫。它无法感到抱歉。它无法把凌晨 2 点那通电话的分量带入它下一次决策的方式中,因为它所做的决策里没有它的利害关系——它只是在以无人能及的速度产出自信的输出,并且无论那个 diff 是杰作还是定时炸弹,都用同样平稳的语调签字放行。无限吞吐,零问责。合并是整个流水线中唯一一个本该由责任止步的地方,而我把交给了唯一一个没有责任可止的参与者。
我们守错了那一下点击
现在到了应该让你不安的部分,因为一旦你看见了它,你就会到处都看见它。
看看团队本能地保留人工的环节,对比他们乐于自动化的环节。
他们在代码审查上保留了一个人代码审查。“必须有人读每一个 diff。”他们给 pull request 加上强制人工批准,争论审查覆盖率,因为有人眼看过那些行而感到安全。然后他们接上了绿灯自动合并以及持续部署到 prod,并为此感到聪明,因为那部分只是管道而已。
这恰恰是反的。
代码审查是可逆的那一步可逆。如果人在审查中漏掉了什么,什么都还没发生。你可以再审查一遍。你可以评论。你可以在 staging 里抓住它。什么都还没有跨入真实世界。它是整条流水线中唯一最可恢复的节点——而它正是我们坚持保留人工的那一步,尽管它如今已是机器做得更好的那一步。
合并到生产是不可逆的、对外的不可逆的、对外的步骤。这正是变更不再是一项提案、而开始成为客户凌晨 2 点能撞上的东西的那一刻。它是流水线中唯一一个重试可能把某人锁在自己账户之外的点。而它也是我们最乐于交给机器人的一步,恰恰因为它看起来像管道——一个按钮、一个 webhook、一个绿色对勾——而不是一个决策。
我们用人工守护廉价且可逆的步骤,却把昂贵且不可逆的步骤自动化。我们把双手留在机器使其安全的那一处,却从机器无法使其安全的那一处移开。
合并什么也不产生——而这正是破绽所在
合并按钮之所以感觉如此可自动化,是有原因的,而这也正是“否”和被避免的故障总是被低估的同一个原因:它不产生任何可见的东西。
一次顺利的合并是隐形的。什么也没发生。没有产物,没有上升的曲线图,没有一行署你名字的代码——代码是作者智能体写的,bug 是审查智能体发现的,而你所做的只是点了一个四个绿色对勾早已证明其合理的按钮。它看起来是大楼里最可删除的工作。
一次出问题的合并则是一个可计数、且署着名字的事件:一次事故、一份复盘、一个被锁在账户外的客户、一通电话。所以坐在合并按钮前的人处于一个纯粹只有下行风险的位子——出问题时承担所有指责,不出问题时得不到任何功劳。用任何你能指出的生产力指标衡量,那个人都在无所事事。而这恰恰就是这份工作无法被自动化掉的原因:他们真正提供的不是吞吐量,而是一个让后果落地的地方。你保留一个人在合并环节,不是因为他们更快,也不是因为他们比智能体更会读 diff。你把他们留在那里,是因为当凌晨 2 点出问题时,“流水线干的”不是一个你能对客户、对监管者、或对自己给出的答案。
需要说明的是——这并非“在环路中保留一个人”
这篇文章的偷懒版本是“看吧,你总是需要一个人类盯着 AI”,而正是这种自我安慰会毁掉团队,所以让我先把它掐死。
在每一个环节都保留一个人步骤是表演。一个人盯着每一个生成的 diff,重读审查智能体已经读过的东西,批准测试已经证明的东西——这不是安全,这是一个慢吞吞的人假装靠挡路来增加价值。机器现在读 diff 比一个疲惫的资深工程师更好;让人类给每一个都盖橡皮图章,只是把机器人的判断通过一个其实已经没在看的人洗一遍。我自动化了生成。我自动化了审查,然后我把审查做得更难比我以前更严格,因为智能体不会因为追求彻底而疲惫。我绝不会回头。产出只是入场券,而对代码的判断现在是可以按千购买的东西。
这个主张很窄,而且我认为一旦把这两项工作分开,就很难反驳:流水线中恰好有一步必须留给人,那就是合并——不是因为人做得更好,而是因为人是唯一能被追责的参与者。那个按钮上游的一切都是技能,而技能已经变便宜了。按钮本身不是技能。它是一个签名。
我实际改了什么
我没有拆掉自动化。我只是把人类重新指向了那个需要人的唯一位置:
- 智能体仍然生成和审查。我让两者做得更多,而不是更少。作者起草,怀疑者试图攻破它。这个循环的这一部分,闭合比开放更好——机器不知疲倦,而我曾经疲惫。
- 每一次合并到生产都由人负责,除此之外什么都不负责。不是作为重读 diff 的代码审查者——智能体已经做得更好了。而是作为负责的一方,去看爆炸半径:这触及了什么,如果出错谁会收到告警,重试方案是什么,凌晨 2 点会不会打电话给我。人在合并时问的问题不是“这段代码正确吗”——而是“我愿意把自己的名字署在这次上线吗。”
- 我手动加回来的唯一一件事,是重试问题。现在每条写入路径在合并前都要有一句人写的话:如果客户端重试,这会做什么?这正是那个本可以抓住这个 bug 的问题。它不是智能体缺少的技能——它是一个在闭环内没有人负责去问的问题。
- 部署仍然自动化;部署的决定没有。这套管道是机器人式的,而且本就应该如此。选择把它发出去——那不可逆的向外一步——上面挂着一个名字。
为什么这就是我所构建之物的形态
再往下一层,这就是我所从事的智能体平台的全部架构,现在你知道原因了。
有一个作者——那个生成 diff 的东西。廉价、快速、无穷无尽,是充裕之物。把它彻底自动化。
有一个独立的怀疑者——它唯一的职责就是试图击垮作者所做的东西,而不是欣赏它。那个“不”,拥有自己的一席之地,在人类审查者会疲惫之处不知疲倦。也把它自动化,并且让它刻薄,因为机器在更擅长持续保持怀疑,胜过一个人在下午 5 点的状态。
还有一个人类守在合并环节。不是审查代码行——怀疑者已经做得比人类所能做的更狠。而是拥有决定权。看清爆炸半径。在凌晨 2 点重试命中时,成为后果落地的那个地方。作者有无限的吞吐量,我让它跑。怀疑者有无限的耐心,我让它跑。人类两者皆无——却是三者中唯一能被追究结果的那个。那是我永远不会再自动化的唯一一个席位。
这就是xenition的形态,它对你所做的也正是如此:你在一个聊天里描述你想要什么——一份文档、一个追踪器、一个完整可用的应用——它就把东西构建出来,让怀疑者跑一遍,然后在任何东西上线之前停下来等你批准。那两个廉价的工作自行运转;你保留那无法撤销的一次点击。把技能自动化,把签名留给人类。
我曾把整个闭环彻底跑通过一次。代价是凌晨 2 点让一个客户失去了他们的账户,以及一通我至今仍能听见的电话。教训不是“别信任智能体”——我比以往任何时候都更信任它们去写作和检查。而是:这条流水线里恰好有一个按钮,它从一开始就从来不是一项技能,而它恰恰是我最急于自动化的那个,因为它看起来最容易。