智能体(agent)生成的 diff 在你的电脑上显示为绿色。测试通过了,lint 通过了,开发服务器也干净地完成了热重载。你让它提交了 PR,九十秒后,CI 在一个与修改完全无关的步骤上报错变红了:缺少某个 CLI 工具、一个智能体从未声明过的新环境变量,或者 Node 版本解析结果不一致——因为你的 .nvmrc 是通过 runner 并不具备的全局 shim 进行解析的。智能体并没有写出有问题的 diff。它写出的是一个依赖于你机器环境的 diff,而你的机器和 runner 并不是同一台电脑。
“在我的机器上能运行”曾是一个人为 Bug。解决办法是保持纪律性——锁定版本、编写 Dockerfile、阅读 CI 日志。而编程智能体大规模地继承了这个 Bug,却丢弃了曾经用来弥补它的纪律性。因为智能体不知道它所依赖的东西哪些来自代码库,哪些来自你 shell 历史记录中的“温热沉淀物”。每个开发者的笔记本电脑都是一个配置独特的环境,智能体在不知不觉中吸收了这些环境。接着,同一个智能体在一个完全不具备这些条件的 runner 中运行,失败的表象看起来像是智能体的错,但实际上是由于没人写明的一份环境契约。
没人列出的“温热环境税”
你的笔记本电脑是一个“温热环境”。你的 shell 会加载一个导出 AWS_PROFILE 的点文件(dotfile),有一个包含私有仓库令牌的 ~/.npmrc,有一个通过 Homebrew 安装的全局 Node——当代码库的 .nvmrc 缺少次版本号时,你的 nvm shim 会解析到这个版本;还有一个六个月前就在本地仓库缓存好的 Docker 镜像,已经认证过的 gh,指向某个集群的 kubectl,以及在 localhost 上运行的 Postgres——那是你在 2024 年的某个周四用 brew 安装的。
智能体没有安装其中的任何东西,它只是继承了它们。当它运行 npm install 时,私有仓库能解析是因为令牌已经存在于你的 shell 中。当它运行 make seed 时,容器能启动是因为镜像早已拉取。当它运行测试套件时,数据库已经在那里了。所有这些机制都不在代码库中,而 runner 也不具备其中的任何一项。
这种“税”会产生复利,因为智能体看不到它正在使用的东西。当人类开发者运行 npm install 并看到 npm warn deprecated using cached token 时,至少还有机会注意到它。而智能体将同样的输出作为工具执行结果读取时,会将成功视为契约:命令运行了,测试通过了,任务完成了。接着智能体写下一条 commit 信息,断言修改已完成。CI runner 读取同一个代码库,运行同样的命令,却发现这一切都不是真的。
Runner 是你 Shell 的陌生人
CI runner 被刻意设计得非常“简陋”。它们拥有标准的 shell、最精简的工具集、没有缓存的凭据、被清理过的环境变量、全新的文件系统,以及一个不会静默允许你访问私有仓库的网络策略。这是正确的设计——runner 的存在是为了证明 git 中的产物是自洽的。任何 runner 能做到但 git 产物无法描述的事情,都是认证过程中的漏洞。
对于人类开发者来说,runner 的简陋虽然烦人但清晰易懂。你阅读失败日志,找到缺失的部分,然后提交它。对于编程智能体来说,runner 的简陋是不可见的,因为智能体从未见过 runner。智能体接触的是你的 shell,运行的是你的命令,观察的是你的输出,并据此得出任务已完成的结论。对智能体而言,runner 仅作为一种失败日志存在,它必须在事后解释这些日志——通常是在一两轮对话之后,人类将 CI 输出“粘贴炸弹”般丢回对话框的时候。
这种偏差是不对称的。仅本地成功的代码可能被发布,而仅 CI 成功的代码则不会。智能体吸收的每项开发环境便利,都变成了 CI runner 以构建失败(红标)形式支付的“税款”,而智能体并没有原生渠道去询问:“这在决定能否发布的那个环境中运行得通吗?”
为什么智能体闻不出它继承了什么
一位高级工程师运行 npm install 时,在某种程度上会知道这次安装使用了私有仓库,知道凭据来自某处,知道 lockfile 解析到了特定的 Node 版本,并且知道其中一些事实并不在代码库中。高级工程师脑中有一个不成文的模型,即“一个陌生人需要什么才能复现这个过程”。正是这个模型促使工程师提交 .tool-versions 文件,或者编写 make bootstrap 目标,亦或是将凭据设置写进脚本。
除非有人把它写下来,否则智能体没有这种模型。智能体读取仓库,看到 package.json,运行 npm install,获得成功,然后继续。它内部没有信号会提示:该命令成功的原因并不包含在你刚刚阅读的代码库中。智能体的训练目标是“完成任务”。找出任务未明确声明的依赖项并不是任务本身。
这就是失败模式上升到架构层面的时候。智能体并不是在偷懒。它的运行完全符合设计——将目标转化为行动,观察输出,当输出为绿色时宣告完成。没有人设计的部分是这样一个层级,它会问:“如果你不在这个开发者的机器上,这还会是绿色的吗?”
缩小差距的模式
修复方法不是“让 Agent 变得更聪明”。而是给 Agent 一份关于环境必须包含什么的、可读的合同(contract),并在编辑代码之前验证该合同。
仓库中固定的显式环境清单(explicit environment manifest)。 这就是新一代仓库规范中 AGENTS.md 之类文件存在的意义。在 Agent 第一轮读取的地方,记录项目所需的准确工具、版本、环境变量和凭据。不是 “node 20” —— 而是确切的次版本号、包管理器、注册表配置、缺失会导致构建中断的环境变量,以及测试套件调用的可选系统二进制文件。清单应该足够具体,以至于陌生人可以仅凭清单配置运行环境,而无需阅读仓库的其他部分。
Agent 在编辑前运行的与 CI 一致的预检(pre-flight)。 在 Agent 提交任何一行代码之前,它应该运行一个探测程序,断言本地环境与清单匹配。Node 次版本号错误:停止并告知你。缺少 CLI:停止并告知你。环境变量中有认证令牌:发出警告并询问是否在继续之前从会话中清除它。这种探测不是构建 —— 它是一种合同检查,在编写任何针对幻觉依赖(phantom dependency)的代码之前,在成本为零的时刻捕捉到漂移。
能够优雅降级的能力探测工具(Capability-probing tools)。 当 Agent 的工具层想要运行 gh pr create 时,它应该先调用 gh auth status,并在工具缺失或未授权时路由到备用方案(fallback)。当它想要 docker compose up 时,应先检查 Docker。目前“调用命令、解析失败、在困惑中重试”的模式是一个消耗 token 的循环,掩盖了缺失的能力检查。能力探测是廉价的;而通过堆栈跟踪来解释失败的操作则不然。
开发期间在 Runner 镜像中运行 Agent 的 “CI 优先”评估。 这是最昂贵的模式,也是能捕捉到最多问题的模式。在真实的 CI Runner 镜像中启动 Agent,使用干净的文件系统,以及真实流水线所使用的脱敏环境变量,并让它也在那里尝试任务。将其轨迹(trajectory)与本地运行进行对比。差异(Divergence)就是信号。如果你在 Agent 的本地计划中引用了 Runner 镜像中没有的工具,你会在提交 PR 之前就发现了 bug。
顺序很重要。清单(Manifest)第一 —— 它不花任何成本,并为其他所有验证提供了合同。预检探测(Pre-flight probe)第二 —— 它在任何工作开始前,在开发者的机器上断言合同。能力探测(Capability probes)第三 —— 它们使单个工具调用具备漂移感知能力。CI 优先评估(CI-first eval)第四 —— 它是保真度最高的检查,也是运行成本最高的,因此将其用于差异成本高的任务。
根据差异评分,而非根据成功评分
让这一切行之有效的评估准则是:根据“开发环境 vs CI 环境”的差异对 Agent 进行评分,而不是根据“笔记本电脑上构建成功”来评分。每个非琐碎的 Agent 任务都应该运行两次 —— 一次在开发者的热环境(warm environment)中,一次在干净的 Runner 镜像中 —— 评分标准是两条轨迹之间的差异,而不是其中任何一次的成功率。
如果两者都成功且轨迹一致,那么变更(diff)就是可移植的。如果两者都成功但轨迹在工具调用或运行命令上发生分歧,说明 Agent 默默依赖了清单中未声明的东西,清单在下一次会话前需要更新。如果本地成功而 CI 失败,说明 Agent 吸收了一个未声明的依赖,而 Runner 镜像才是必须获胜的事实来源。如果 CI 成功而本地失败,你遇到了一个更奇特且更有趣的 bug —— 你的笔记本电脑才是出问题的那个。
每个 Agent 任务都公布这一分数的团队,能获得环境合同过时的领先指标。只关注 PR 通过率的团队得到的是滞后指标,以及不得不对 Runner 日志进行取证调试(forensics-debug)的审查者。
审查者支付的代价
编码 Agent 交付了在本地通过但在 CI 中失败的变更,其真实的代价不是浪费的 Runner 分钟数 —— 那些很便宜。代价是审查者(reviewer)的时间。Agent 在变更中埋下的每一个未公开的环境依赖,都是一个微型的逆向工程项目,人类审查者现在必须在没有编写代码的 Agent 帮助的情况下完成它,因为 Agent 并不知晓其哪些操作依赖于人类的 Shell 环境。
一个每周交付十个此类变更的团队,无异于向其资深工程师征收了一项“取证调试税”,这项税收随着 Agent 的采用而线性增长,而且没人将其归因于 Agent,因为症状表现为“CI 很不稳定”或“Runner 需要升级”。Runner 不需要升级。仓库需要一份合同。
成本框架(cost frame)是说服领导层为清单、预检、能力探测和 CI 优先评估提供预算的关键。没有这个框架,Agent 的生产力会以开启的 PR 数量来衡量,而对审查者时间的无声扣除将一直被忽视,直到审查者与 Agent 的比例发生倒置,终于有人注意到资深工程师已经停止交付他们自己的工作了。
一个 Agent 可复现的代码库
这一切背后的架构认知是,代码库(repo)而非开发者的笔记本电脑,必须成为 Agent 复现的基本单元。任何 Agent 需要但不在代码库中的东西都是一种泄漏。你 shell 中的 Token、PATH 中的 CLI、通过 shim 解析的 Node 版本 —— 全都是泄漏。每一个泄漏都是未来的一次 CI 失败、一次 Reviewer 调试会话,或者当同一个 Agent 在生产环境中自主运行并以吸收你的特性同样的方式吸收生产环境的特性时,所引发的未来事故。
将代码库视为 Agent 环境唯一真理来源的团队,交付的 Agent 编写的 Diff 可以在任何地方通过。而那些让 Agent 继承已配置好的开发环境的团队,交付的 Diff 在作者的笔记本电脑上能通过,但在 Reviewer 的电脑上失败,接着在 CI 中失败,最后偶尔在生产环境中因为同样的原因失败 —— 于是团队得出的结论是“Agent 不可靠”,而实际上 Agent 只是在忠实地执行一份无人编写的契约。
编写契约。在 Agent 修改之前验证它。在 Runner 镜像中运行 Agent。根据偏差进行评分。除此之外的一切,都只是换了件昂贵制服的“在我的机器上能跑”。
会员专享
余下内容仅对会员开放。
会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。
- —完整文章,包含未公开存档的部分
- —可落地的工作框架,附带权衡与决策依据
- —新文章抢先看,先于公开发布
随时取消 · 一次订阅,畅读全部