原文:dexhorthy - 2025.09.24

AI 编程工具在真实的生产代码库中表现不佳,这似乎已是普遍共识。斯坦福大学关于 AI 对开发者生产力影响的研究发现:

  1. AI 工具生成的大部分“额外代码”最终只是在返工上周提交的草率代码。
  2. 编码代理非常适合新项目或小改动,但在大型成熟的代码库中,它们往往会降低开发者的生产力。

对此常见的反应介于悲观的“这永远不会成功”和更为审慎的“也许有一天模型会更智能”之间。

经过几个月的反复尝试,我发现,如果能掌握核心的上下文工程原则,即使是今天的模型也能发挥巨大作用

这并非又一个“让你的生产力提升十倍”的宣传。对于与 AI 炒作机器打交道,我通常持相当审慎的态度。但我们偶然发现了一些工作流程,让我对未来的可能性充满乐观。我们已经让 Claude Code 成功处理 30 万行 Rust 代码库,实现一天内完成一周的工作量,并保持通过专家评审的代码质量。我们采用了一系列我称之为“频繁有意压缩”(frequent intentional compaction)的技术——在整个开发过程中,有意识地构建如何向 AI 提供上下文。

我现在完全相信,AI 编码不仅仅是玩具和原型,而是一项深具技术性的工程实践。

AI 工程师的上下文基础

AI Engineer 2025 年的两场演讲,从根本上塑造了我对这个问题的思考。

第一场是 Sean Grove 关于“规范即新代码”的演讲,第二场则是 斯坦福大学关于 AI 对开发者生产力影响的研究

Sean 认为,我们都在错误地进行“氛围编程”(vibe coding)。那种与 AI 代理聊上两小时,阐明需求,然后丢弃所有提示,只提交最终代码的做法……就像 Java 开发者编译 JAR 包,只提交编译后的二进制文件却丢弃源代码一样。

Sean 提出,在 AI 驱动的未来,规范将成为真正的代码。他预测,两年后,你打开 IDE 中的 Python 文件,其频率大概会和你今天打开十六进制编辑器阅读汇编代码一样(对我们大多数人来说,这意味着“从不”)。

Yegor 关于开发者生产力的演讲则探讨了一个正交的问题。他们分析了 10 万名开发者的提交记录,并发现了以下几点:

  1. AI 工具常常导致大量返工,从而削弱了其带来的感知生产力提升。

  1. AI 工具在新项目(greenfield projects)中表现良好,但对于旧有代码库(brownfield codebases)和复杂任务,它们往往适得其反。

这与我从创始人那里听到的反馈不谋而合:

  • “太多冗余。”
  • “技术债务工厂。”
  • “在大仓库中不好用。”
  • “对复杂系统无效。”

对于使用 AI 编码处理难题的普遍感受是:

也许有一天,当模型更智能的时候……

就连 Amjad 九个月前也在 Lenny 的播客上提到,产品经理用 Replit 代理来构建原型新功能,然后将其交给工程师进行生产实现。

每当我听到“也许有一天模型更智能的时候”这句话时,我通常会忍不住高呼:这就是上下文工程的全部意义所在——最大限度地利用现有模型。

如今真正可能实现什么

我会在下文深入探讨这一点,但为了证明这并非纸上谈兵,我先举一个具体例子。几周前,我决定在 BAML 上测试我们的技术,这是一个 30 万行代码的 Rust 编程语言库,用于处理 LLM。我充其量只是一个业余 Rust 开发者,以前从未接触过 BAML 代码库。

大约一小时内,我提交了一个 修复 bug 的 PR,第二天早上就获得了维护者的批准。几周后,@hellovai 和我合作,向 BAML 提交了 3.5 万行代码,增加了取消支持WASM 编译——这些功能团队估计需要一名资深工程师花费 3-5 天才能完成。而我们在大约 7 小时内就完成了两个 PR 草稿的准备工作。

同样,这一切都围绕着我们称之为“频繁有意压缩”的工作流程——本质上是围绕上下文管理来设计整个开发过程,将利用率保持在 40-60% 的范围,并在关键节点嵌入高效的人工审查。我们采用“研究、规划、实现”的工作流程,但这里面的核心能力/经验远比任何特定的工作流程或提示集更具普遍性。

我们走到这里的奇特旅程

我曾与我见过的最高效的 AI 程序员之一共事。他们每隔几天就会提交 2000 行 Go 语言 PRs。而且这并非 Next.js 应用或 CRUD API。这是复杂的、容易出现竞态条件的系统代码,它通过 Unix Socket 进行 JSON RPC,并管理来自分叉的 Unix 进程的流式标准 I/O(主要是 Claude Code SDK 进程,稍后会详细介绍 🙂)。

每隔几天仔细阅读 2000 行复杂的 Go 代码,这根本不可持续。我开始有点像 Mitchell Hashimoto,他为 Ghostty 添加了“AI 贡献必须披露”的规定。

我们的方法是采用类似于 Sean 的规范驱动开发

一开始这让人很不适应。我必须学会放手,不再阅读 PR 中的每一行代码。我仍然会非常仔细地阅读测试,但规范成为了我们构建内容和原因的真相之源。

这种转变持续了大约 8 周。对所有参与者来说,这都非常不舒服,尤其是我。但现在我们进展神速。几周前,我一天之内提交了 6 个 PR。在过去三个月里,我亲手编辑非 Markdown 文件的次数屈指可数。

针对编码代理的高级上下文工程

我们所需的是:

  • 能在旧有代码库中良好运作的 AI
  • 能解决复杂问题的 AI
  • 没有冗余
  • 保持团队内部的思维一致性

(当然,也要尽量消耗更多的 Token。)

我将深入探讨:

  1. 我们将上下文工程应用于编码代理所学到的经验
  2. 掌握使用这些代理需要跨越多个技术维度——这是一门很讲究技术功底的工艺
  3. 为什么我不认为这些方法具有普适性
  4. 我在第 3 点上被反复证明错误的次数

但首先看下:管理代理上下文的朴素方法

我们大多数人最初使用编码代理就像使用聊天机器人一样。你与它来回交谈(或者大喊大叫),在问题中摸索前行,直到你耗尽上下文、放弃,或者代理开始道歉。

稍微聪明一点的方法是,当你偏离轨道时,直接重新开始,放弃当前会话并启动一个新会话,也许在提示中加入更多指导。

[原始提示],但请确保使用 XYZ 方法,因为 ABC 方法无效。

稍微聪明一点:有意压缩

你可能已经做过我称之为“有意压缩”的事情。无论是否在正轨上,当上下文开始填满时,可能都想暂停工作,然后用一个全新的上下文窗口重新开始。为此,你可能会使用这样的提示:

“将我们目前为止的所有进展写入 progress.md,务必注明最终目标、我们正在采用的方法、已完成的步骤,以及我们当前正在解决的失败败之处。”

你也可以使用提交信息进行有意压缩

我们究竟在压缩什么?

什么会占用上下文?

  • 搜索文件
  • 理解代码流
  • 应用修改
  • 测试/构建日志
  • 来自工具的巨大 JSON 块

所有这些都可能淹没上下文窗口。压缩仅仅是将它们提炼成结构化的人工制品。

有意压缩的良好输出可能包含类似这样的内容:

为什么执着于上下文?

正如我们在12-factor agents中深入探讨的那样,LLM 是无状态函数。唯一影响输出质量的因素(在不训练/调整模型本身的情况下)就是输入质量

这对于使用编码代理来说也是如此,就像通用代理设计一样,只是你的问题空间更小,我们谈论的是使用代理,而不是构建代理。

在任何给定时刻,像 Claude Code 这样的代理中的一个“回合”都是一次无状态函数调用。上下文窗口输入,下一步输出。

也就是说,你的上下文窗口内容是影响输出质量的唯一杠杆。所以,这确实值得我们深思。

你应该优化你的上下文窗口以获得:

  1. 正确性
  2. 完整性
  3. 大小
  4. 轨迹

换句话说,你的上下文窗口可能发生的最糟糕的事情,按顺序排列是:

  1. 不正确的信息
  2. 缺失的信息
  3. 太多噪音

如果你喜欢方程式,这里有一个可以参考的简单公式:

正如 Geoff Huntley 所说:

关键在于,你只有大约 170K 的上下文窗口可以使用。 因此,尽可能少地使用它至关重要。 你使用的上下文窗口越多,得到的结果就越糟糕。

Geoff 针对这一工程限制的解决方案是一种他称之为“像软件工程师一样做 Ralph Wiggum”的技术,它基本上涉及在一个 while 循环中永远运行一个代理,并使用一个简单的提示。

1
2
3
while :; do
  cat PROMPT.md | npx --yes @sourcegraph/amp
done

Geoff 形容 Ralph 是解决上下文窗口问题的“一个可笑的笨办法”。我并不完全确定它是不是笨办法

回到压缩:使用子代理

子代理是管理上下文的另一种方式,通用子代理(即非自定义子代理)自早期就一直是 Claude Code 和许多编码 CLI 的一项功能。

子代理并非为了玩过家家和拟人化角色。子代理是为了上下文控制。

子代理最常见/直接的用例是让你使用一个全新的上下文窗口进行查找/搜索/总结,这样主代理就可以直接开始工作,而无需用 Glob / Grep / Read 等调用来混淆其上下文窗口。

理想的子代理响应可能类似于前文的临时压缩示例。

让子代理返回这个结果并非易事:

效果更好的方法:频繁有意压缩

我想讨论的、也是我们最近几个月采用的技术,属于我称之为“频繁有意压缩”的范畴。

本质上,这意味着围绕上下文管理来设计你的整个工作流程,并将上下文利用率保持在 40%-60% 的范围(具体取决于问题的复杂性)。

我们的做法是将其拆分为三(左右)个步骤。

我说“左右”是因为有时我们会跳过研究直接进入规划阶段,有时我们会在准备好实施之前进行多轮压缩研究。

我将在下面的具体示例中分享每个步骤的输出示例。对于一个给定的功能或 bug,我们通常会进行:

研究(Research)

了解代码库、与问题相关的文件以及信息流,也许还有问题的潜在原因。

这是我们的研究提示词。它目前使用的是自定义子代理,但在其他仓库中我使用了一个更通用的版本,它使用 Claude Code 的 Task() 工具和 general-agent。通用版本的效果也差不多。

规划(Plan)

概述我们将采取的修复问题的确切步骤,以及我们需要编辑哪些文件以及如何编辑,对每个阶段的测试/验证步骤要非常精确。

这是我们用于规划的提示词

实现(Implement)

分阶段执行计划。对于复杂的工作,我通常会在每个实施阶段验证后,将当前状态压缩回原始计划文件。

这是我们使用的实现提示词

附注——如果你经常听说 git worktrees,这是唯一需要在 worktree 中完成的步骤。我们倾向于在 main 分支上完成所有其他工作。

我们如何管理/共享 Markdown 文件

为了简洁起见,我将跳过这一部分,但如果你有兴趣,可以在 humanlayer/humanlayer 中启动一个 Claude 会话,询问“thoughts tool”是如何工作的。

将其付诸实践

我每周都会和 @vaibhav 一起进行每周实时编码会话,我们在其中进行白板讨论并编写一个高级 AI 工程问题的解决方案。这是我每周的亮点之一。

几周前,我决定分享更多关于这个过程的信息,好奇我们的内部技术是否能一举修复 BAML(一个用于处理 LLM 的编程语言)的 30 万行 Rust 代码库。我从 @BoundaryML 仓库中挑了一个小 bug,然后开始工作。

你可以观看这一集来了解更多关于这个过程的信息,但总的来说:

值得注意的是:我充其量只是一个业余的 Rust 开发者,而且我以前从未在 BAML 代码库中工作过。

研究阶段

  • 我进行了一次研究,阅读了结果。Claude 认为这个 bug 是无效的,代码库是正确的。
  • 我丢弃了那份研究,重新开始了一次,加入了更多的引导。
  • 这是我最终使用的最终研究文档

规划阶段

  • 在研究进行期间,我有点不耐烦,在没有研究的情况下开始了一项计划,看看 Claude 是否能直接制定实施计划——你可以在这里看到
  • 研究完成后,我启动了另一个使用研究结果的实施计划——你可以在这里看到

这两个计划都相当简短,但它们有显著差异。它们以不同的方式修复了问题,并采用了不同的测试方法。无需过多细节,它们都“本可以奏效”,但那个基于研究构建的计划在最佳位置解决了问题,并规定了符合代码库约定的测试。

实施阶段

  • 这些都发生在播客录制的前一晚。我并行运行了两个计划,并在睡前将它们都提交为 PR。

第二天早上 10 点我们上节目时,那个包含研究的计划所产生的 PR 已经得到了 @aaron 的批准,他甚至不知道我正在为播客做些什么 🙂。我们关闭了另一个 PR

所以,我们在最初的 4 个目标中,实现了:

  • ✅ 在旧有代码库中工作 (30 万行 Rust 项目)
  • 解决复杂问题
  • ✅ 没有冗余 (PR 已合并)
  • 保持思维一致性

解决复杂问题

Vaibhav 仍然持怀疑态度,我想看看我们能否解决一个更复杂的问题。

所以几周后,我们两人花了 7 小时(3 小时研究/规划,4 小时实施),向 BAML 提交了 3.5 万行代码,增加了取消和 WASM 支持。 取消功能的 PR 上周刚刚合并WASM 功能的 PR 仍在开放中,但它有一个可工作的演示,展示了如何在浏览器中的 JS 应用中调用 WASM 编译的 Rust 运行时。

虽然取消功能的 PR 需要更多的努力才能最终完成,但我们在一天之内取得了令人难以置信的进展。Vaibhav 估计,BAML 团队的资深工程师完成每个 PR 都需要 3-5 天的工作量。

✅ 所以,我们也能解决复杂问题。

这不是魔法

还记得我在示例中读了研究报告然后把它丢弃,因为它错了那一部分吗?或者我和 Vaibhav 深度投入了 7 个小时?当你做这件事的时候,你必须全身心投入到任务中,否则它不会奏效

有些人总是寻找那个能解决所有问题的神奇提示。它不存在。

通过研究/规划/实施流程进行的频繁有意压缩会提升你的表现,但使其足以解决难题的关键在于,在你的流程中构建高杠杆的人工审查。

颜面尽失

几周前,@blakesmith 和我花了 7 个小时,试图从 Parquet Java 中移除 Hadoop 依赖——关于所有出错之处以及我的理论,我会留待另一篇文章。简而言之,进展并不顺利。最核心的问题是研究步骤没有深入到依赖树中,并假设类可以在不引入深层嵌套的 Hadoop 依赖的情况下上移。

有些非常困难的问题,你无法在 7 小时内仅仅通过提示词来解决。我们仍在好奇而兴奋地与朋友和合作伙伴一起探索这些边界。我认为另一个教训是,你可能至少需要一个代码库的专家,而在这个案例中,我们俩都不是。

关于人力杠杆

如果你能从这一切中学到一件事,那就是:

一行糟糕的代码……还是一行糟糕的代码。但一份计划中的一行错误,可能导致数百行糟糕的代码。而一份研究中的一行错误,对代码库工作方式或特定功能位置的误解,可能导致数千行糟糕的代码。

所以,你需要将人力投入和注意力集中在流程中杠杆率最高的部分。

当审查研究和规划时,你获得的杠杆作用比审查代码时更大。(顺便说一句,我们 @ humanlayer 的主要关注点之一就是帮助团队构建和利用高质量的工作流提示,并为 AI 生成的代码和规范打造出色的协作工作流。)

代码审查的目的是什么?

人们对代码审查的目的有很多不同的看法。

我更喜欢 Blake Smith 在《软件团队代码审查要点》中的框架,他认为代码审查最重要的部分是思维一致性——让团队成员了解代码如何变化以及为什么变化。

还记得那些 2000 行 Go 语言的 PR 吗?我关心它们是否正确且设计良好,但团队内部最大的不安和挫败感来源是缺乏思维一致性。我开始与我们的产品是什么以及它是如何工作的失去联系

我预计,任何与高效 AI 程序员合作过的人都曾有过这种经历。

这实际上是我们进行研究/规划/实施中最重要的部分。每个人都提交更多代码的必然副作用是,你的代码库中会有更大比例的部分对任何特定工程师来说都是不熟悉的。

我甚至不会试图说服你,研究/规划/实施是大多数团队的正确方法——它可能不是。但你绝对需要一个工程流程,它能做到:

  1. 保持团队成员的步调一致
  2. 使团队成员能够快速了解代码库中不熟悉的部分

对于大多数团队来说,这是拉取请求和内部文档。对于我们来说,现在是规范、计划和研究。

我无法每天阅读 2000 行 Go 语言代码。但我可以阅读 200 行写得很好的实现计划。

当出现问题时,我无法花一个多小时深入探究 40 多个守护进程文件(好吧,我可以,但我不想)。我可以引导研究提示,让我快速了解应该关注哪里以及原因。

总结

基本上,我们得到了我们想要的一切。

  • ✅ 在旧有代码库中工作
  • ✅ 解决复杂问题
  • ✅ 没有冗余
  • ✅ 保持思维一致性

(哦,对了,我们三人的团队每月平均在 Opus 上花费大约 1.2 万美元)

所以,为了让你不认为我只是另一个炒作的胡子销售员,我要指出的是,这并不能完美地解决所有问题。

八月份,整个团队花了两个星期在处理一个非常棘手的竞态条件上打转,最终陷入了 MCP sHTTP keepalives 在 Go 语言中以及其他一堆死胡同的问题。

但现在这只是个例外。总的来说,这对我们来说效果很好。我们的实习生在第一天就提交了 2 个 PR,第八天提交了 10 个。我曾真心怀疑这是否对其他人有效,但我和 Vaibhav 在 7 小时内提交了 3.5 万行可工作的 BAML 代码。(如果你还没见过 Vaibhav,他是我认识的在代码设计和质量方面最细致的工程师之一。)

未来展望

我相当确信编码代理(coding agents)将商品化。

难点在于团队和工作流程的转变。在一个 AI 编写我们 99% 代码的世界里,协作的一切都将改变。

我坚信,如果你不解决这个问题,你将被那些解决了这个问题的人超越。