驾驭工程:在 Agent 优先的世界中利用 Codex

2026-03-19 · 原文链接

2026年2月11日 工程 驾驭工程:在 Agent 优先的世界中利用 Codex 作者:Ryan Lopopolo,技术人员

在过去的五个月里,我们的团队一直在进行一项实验:构建并发布一个 0 行手动编写代码的软件产品内部测试版。

该产品拥有内部日活用户和外部 Alpha 测试者。它发布、部署、崩溃并得到修复。不同之处在于,每一行代码——应用逻辑、测试、CI 配置、文档、可观测性以及内部工具——都是由 Codex 编写的。我们估计,构建这个产品所花费的时间仅为手动编写代码的 1/10 左右。

人类掌舵。Agent 执行。

我们有意选择了这一限制,以便构建出能够将工程效率提升几个数量级的必要条件。我们只有几周的时间来发布最终达百万行规模的代码。为此,我们需要理解:当软件工程团队的主要工作不再是编写代码,而是设计环境、指定意图并构建反馈循环(让 Codex Agent 能够可靠地工作)时,一切会发生怎样的变化。

这篇文章记录了我们通过 Agent 团队构建全新产品的所思所得——哪些地方出了问题,哪些地方产生了复利,以及如何最大化我们唯一真正稀缺的资源:人类的时间和注意力。

我们从一个空的 Git 仓库开始

2025 年 8 月底,空仓库迎来了第一次提交。

初始脚手架——包括仓库结构、CI 配置、格式化规则、包管理器设置和应用框架——是由 Codex CLI 使用 GPT-5 在一小组现有模板的指导下生成的。甚至引导 Agent 如何在仓库中工作的初始 AGENTS.md 文件本身也是由 Codex 编写的。

系统中没有任何预先存在的人类编写的代码。从一开始,仓库就是由 Agent 塑造的。

五个月后,该仓库包含了应用逻辑、基础设施、工具、文档和内部开发实用程序,代码量约为百万行。在此期间,一个仅由三名工程师组成的团队驱动 Codex 打开并合并了大约 1,500 个拉取请求(PR)。这相当于每位工程师每天平均产出 3.5 个 PR,令人惊讶的是,随着团队扩大到现在的七名工程师,吞吐量还在继续增加。重要的是,这并非为了产出而产出:该产品已被数百名内部用户使用,包括每日使用的内部核心用户。

在整个开发过程中,人类从未直接贡献过任何代码。这成了团队的核心哲学:绝不手动编写代码。

重新定义工程师的角色

由于缺乏亲自动手的人类编码,工程工作转向了另一种模式,重点在于系统、脚手架和杠杆作用。

早期的进展比我们预想的要慢,不是因为 Codex 能力不足,而是因为环境规格说明不足。Agent 缺乏实现高层目标所需的工具、抽象和内部结构。我们工程团队的首要任务变成了赋能 Agent 去做有用的工作。

在实践中,这意味着采用深度优先的工作方式:将大目标分解为较小的构建块(设计、代码、评审、测试等),提示 Agent 构建这些块,并利用它们解锁更复杂的任务。当某些环节失败时,修复方法几乎从来不是“再努力一点”。因为取得进展的唯一方法是让 Codex 完成工作,所以人类工程师总是会介入任务并询问:“缺失了什么能力?我们如何让它对 Agent 既清晰可见又可强制执行?”

人类几乎完全通过提示词(Prompt)与系统交互:工程师描述任务,运行 Agent,并允许其开启一个拉取请求。为了推动 PR 完成,我们指示 Codex 在本地检查自己的更改,请求本地和云端的其他特定 Agent 进行评审,回应人类或 Agent 给出的任何反馈,并在循环中迭代,直到所有 Agent 评审员都满意为止(实际上这就是一个 Ralph Wiggum 循环)。Codex 直接使用我们的标准开发工具(gh、本地脚本和仓库嵌入式技能)来获取上下文,无需人类手动复制粘贴到 CLI 中。

人类可以评审拉取请求,但并非必须。随着时间的推移,我们已经将几乎所有的评审工作都交给了 Agent 对 Agent 的处理模式。

提升应用的可读性

随着代码吞吐量的增加,我们的瓶颈变成了人类的 QA 能力。由于固定约束一直是人类的时间和注意力,我们努力通过让应用 UI、日志和应用指标本身能被 Codex 直接“读懂”,来为 Agent 增加更多能力。

例如,我们使应用可以根据每个 git 工作树(worktree)启动,这样 Codex 就可以针对每次更改启动并驱动一个实例。我们还将 Chrome DevTools 协议接入了 Agent 运行时,并创建了处理 DOM 快照、截图和导航的技能。这使得 Codex 能够复现 bug、验证修复方案并直接推理 UI 行为。

对于可观测性工具,我们也做了同样的处理。日志、指标和追踪通过一个针对特定工作树的临时本地可观测性栈暴露给 Codex。Codex 在一个完全隔离的应用版本上工作——包括其日志和指标,这些内容在任务完成后会被销毁。Agent 可以使用 LogQL 查询日志,使用 PromQL 查询指标。有了这些上下文,诸如“确保服务启动在 800ms 内完成”或“这四个关键用户路径中不得有超过两秒的 Span”之类的提示词就变得可行了。

我们经常看到单个 Codex 运行单项任务的时间超过六个小时(通常是在人类睡觉的时候)。

我们将仓库知识作为权威数据源

上下文管理是使 Agent 在处理大型复杂任务时发挥效用的最大挑战之一。我们学到的最早教训之一非常简单:给 Codex 一张地图,而不是一本 1000 页的说明手册。

我们尝试过“一个大的 AGENTS.md”方法,但它以预料之中的方式失败了:

所以,我们没有把 AGENTS.md 当作百科全书,而是把它当作目录。

仓库的知识库存储在结构化的 docs/ 目录中,被视为权威数据源(system of record)。一个简短的 AGENTS.md(约 100 行)被注入到上下文中,主要充当地图,指向其他地方更深层的真相来源。

(仓库内知识存储布局)

设计文档被编目和索引,包括验证状态和一套定义 Agent 优先操作原则的核心理念。架构文档提供了领域和包分层的顶层地图。质量文档对每个产品领域和架构层进行评分,追踪随时间推移产生的差距。

计划被视为一等公民产物。临时的轻量级计划用于微小更改,而复杂的工作则记录在包含进度和决策日志的执行计划中,并提交到仓库。活动计划、已完成计划和已知的技术债都进行版本化并共处一地,允许 Agent 在不依赖外部上下文的情况下运行。

这实现了“渐进式披露”:Agent 从一个微小、稳定的入口点开始,并被告知下一步该看哪里,而不是预先被海量信息淹没。

我们通过机械手段强制执行这一点。专门的 Linter 和 CI 任务会验证知识库是否最新、是否建立了交叉链接以及结构是否正确。一个定期运行的“文档园艺”Agent 会扫描那些不能反映真实代码行为的陈旧或废弃文档,并打开修复拉取请求。

目标是 Agent 的可读性

随着代码库的演进,Codex 进行设计决策的框架也需要随之演进。

由于仓库完全由 Agent 生成,它首先针对 Codex 的可读性进行了优化。就像团队致力于为新入职的工程师提高代码的可导航性一样,我们人类工程师的目标是让 Agent 能够直接从仓库本身推理出完整的业务领域。

从 Agent 的角度来看,任何它在运行时无法访问的上下文实际上都不存在。存在于 Google Docs、聊天记录或人们大脑中的知识是系统无法触及的。它能看到的只有仓库本地的、版本化的产物(例如代码、Markdown、Schema、可执行计划)。

我们发现,随着时间的推移,我们需要将越来越多的上下文推送到仓库中。那次让团队在架构模式上达成一致的 Slack 讨论?如果它对 Agent 不可见,那么它就是不可读的,就像三个月后入职的新员工不知道它一样。

给 Codex 更多上下文意味着组织和暴露正确的信息,以便 Agent 对其进行推理,而不是用临时指令淹没它。就像你会向新队友介绍产品原则、工程规范和团队文化(包括对 Emoji 的偏好)一样,给 Agent 这些信息会带来一致性更好的产出。

这种框架清晰化了许多权衡。我们偏好那些可以在仓库内完全内化和推理的依赖项和抽象。通常被称为“无聊”的技术往往更容易被 Agent 建模,因为它们具有组合性、API 稳定性以及在训练集中的高表达。在某些情况下,让 Agent 重新实现部分功能比绕过公共库中不透明的上游行为成本更低。例如,我们没有引入通用的 p-limit 风格的包,而是实现了我们自己的并发映射助手:它与我们的 OpenTelemetry 仪表紧密集成,拥有 100% 的测试覆盖率,并且行为完全符合我们运行时的预期。

将系统的更多部分转化为 Agent 可以直接检查、验证和修改的形式,不仅增加了 Codex 的杠杆作用,也增加了同样在该代码库上工作的其他 Agent(例如 Aardvark)的杠杆作用。

强制执行架构和审美

单靠文档无法保持全 Agent 生成的代码库的一致性。通过强制执行不变量(Invariant)而不是微观管理实现细节,我们让 Agent 在不破坏基础的前提下快速发布。例如,我们要求 Codex 在边界处解析数据形状,但不规定如何实现(模型似乎喜欢 Zod,但我们并没有指定这个特定的库)。

Agent 在具有严格边界和可预测结构的物理环境中最高效,因此我们围绕僵化的架构模型构建了应用。每个业务领域都被划分为一组固定的层,具有严格验证的依赖方向和有限的允许边缘。这些约束通过自定义 Linter(当然也是 Codex 生成的!)和结构测试进行机械式强制执行。

这种架构通常是你拥有数百名工程师时才会推迟进行的。对于编码 Agent 来说,这是早期先决条件:约束条件是允许速度而不会导致腐烂或架构漂移的关键。

在实践中,我们使用自定义 Linter、结构测试以及一小部分“审美不变量”来执行这些规则。例如,我们静态地强制执行结构化日志、Schema 和类型的命名约定、文件大小限制,以及通过自定义 Lint 强制执行平台特定的可靠性要求。由于 Lint 是自定义的,我们可以编写错误消息,将修复指令直接注入到 Agent 的上下文中。

在人类优先的工作流中,这些规则可能显得琐碎或具有约束性。对于 Agent 来说,它们变成了乘法器:一旦编码,它们就会立即应用到所有地方。

同时,我们明确哪些地方约束很重要,哪些地方不重要。这类似于领导一个大型工程平台组织:集中强制执行边界,局部允许自主。你深切关注边界、正确性和可复现性。在这些边界之内,你允许团队——或 Agent——在解决方案的表现形式上拥有极大的自由。

生成的代码并不总是符合人类的审美偏好,这没关系。只要产出是正确的、可维护的,并且对未来的 Agent 运行具有可读性,它就达到了标准。

人类的审美会持续反馈到系统中。评审评论、重构拉取请求和用户反馈的 bug 会被捕获为文档更新,或直接编码到工具中。当文档不足以约束时,我们就将规则提升为代码。

吞吐量改变了合并哲学

随着 Codex 吞吐量的增加,许多传统的工程规范变得适得其反。

仓库运行时的阻塞合并门槛降到了最低。拉取请求的生命周期很短。测试抖动(Flake)通常通过后续运行来解决,而不是无限期地阻塞进度。在一个 Agent 吞吐量远超人类注意力的系统中,纠错成本很低,而等待成本很高。

在低吞吐量环境中,这样做是不负责任的。但在这种环境下,这通常是正确的权衡。

“Agent 生成”的真正含义

当我们说代码库是由 Codex Agent 生成时,指的是代码库中的一切。

Agent 产出:

人类始终处于循环中,但工作在与以往不同的抽象层。我们划分工作的优先级,将用户反馈转化为验收标准,并验证结果。当 Agent 遇到困难时,我们将其视为一种信号:识别缺失了什么——工具、护栏、文档——并将之反馈回仓库,而且始终是通过让 Codex 自己编写修复方案来完成。

Agent 直接使用我们的标准开发工具。它们拉取评审反馈、内联回复、推送更新,并且经常自行压缩并合并(squash and merge)自己的拉取请求。

提升自治水平

随着越来越多的开发闭环被直接编码到系统中——测试、验证、评审、反馈处理和恢复——仓库最近跨越了一个有意义的阈值:Codex 可以端到端地驱动一个新功能的开发。

给定一个提示词,Agent 现在可以:

这种行为严重依赖于该仓库特定的结构和工具,不应假设在没有类似投入的情况下可以泛化——至少现在还不行。

熵与垃圾回收

完全的 Agent 自治也引入了新问题。Codex 会复制仓库中已有的模式——即使是不平衡或次优的模式。随着时间的推移,这必然导致偏差。

最初,人类手动处理这个问题。我们的团队以前每周五(周工作时间的 20%)都在清理“AI 垃圾(slop)”。不出所料,这无法规模化。

相反,我们开始将所谓的“黄金原则”直接编码到仓库中,并构建了定期清理流程。这些原则是固执己见的、机械的规则,旨在保持代码库对未来 Agent 运行的可读性和一致性。例如:(1) 我们偏好共享的实用工具包而不是手写的助手函数,以保持不变量集中;(2) 我们不进行“YOLO 式”的数据探测——我们会验证边界或依赖类型化的 SDK,这样 Agent 就不会意外地构建在猜测的数据形状上。我们有一组后台 Codex 任务定期运行,扫描偏差、更新质量等级并开启针对性的重构拉取请求。其中大多数可以在一分钟内评审完毕并自动合并。

这起到了类似垃圾回收的作用。技术债就像高利贷:连续进行小额偿还,几乎总是比让它复利堆积然后进行痛苦的突击清理要好。人类的审美被捕获一次,然后持续强制执行到每一行代码中。这还让我们能够每天捕捉并解决坏模式,而不是让它们在代码库中扩散数天或数周。

我们仍在探索

到目前为止,这种策略在 OpenAI 的内部发布和采用过程中表现良好。构建面向真实用户的真实产品有助于将我们的投入锚定在现实中,并引导我们走向长期可维护性。

我们尚不清楚在一个完全由 Agent 生成的系统中,架构连贯性在数年内会如何演进。我们仍在学习人类判断在何处能产生最大的杠杆作用,以及如何对这种判断进行编码以产生复利。我们也不知道随着模型能力持续增强,这个系统会如何演化。

已经明确的是:构建软件仍然需要纪律,但这种纪律更多地体现在脚手架上,而非代码本身。保持代码库连贯性的工具、抽象和反馈循环变得越来越重要。

我们现在最艰巨的挑战集中在设计环境、反馈循环和控制系统上,以帮助 Agent 实现我们的目标:规模化地构建和维护复杂、可靠的软件。

随着像 Codex 这样的 Agent 承担起软件生命周期的更大份额,这些问题将变得更加重要。我们希望分享这些早期教训能帮助你思考应该在何处投入精力,从而让你能够单纯地去“构建”。