Skip to content
Go back

复杂代码库中的 Coding Agent:上下文工程、人类审查与渐进式实现

Advanced Context Engineering for Coding Agents 不是一个可以安装运行的工具,而是 HumanLayer 团队持续更新的一组文章、实验和方法论。

这些材料试图回答一个很现实的问题:

为什么 Coding Agent 在新项目和小改动上表现很好,一进入大型、已有代码库,就容易写出方向错误、难以维护的代码?

作者最初把答案归结为上下文管理,并提出了“频繁且有意地压缩上下文”。随着实践深入,他又进一步意识到:上下文工程只能提高 Agent 的表现,不能消除模型在系统设计和长期维护上的局限。要真正把 Agent 用在复杂工程中,人仍然需要参与需求、架构、程序设计和实现节奏的判断。

Table of contents

Open Table of contents

从“模型不够聪明”转向“上下文没有组织好”

Coding Agent 可以把每次行动理解成一个近似无状态的过程:

当前上下文 → 模型推理 → 下一步行动

在不重新训练模型的前提下,我们能直接控制的,主要就是输入给模型的上下文。

复杂任务的问题是,上下文很快会被各种中间信息占满:

这些内容在当时可能有用,却不一定都值得长期保留。如果一直在同一个会话里边搜索、边讨论、边修改,真正重要的信息会逐渐被噪声淹没。

作者认为,一个好的上下文应同时满足四个条件:

  1. 正确:没有建立在错误理解之上。
  2. 完整:包含完成当前阶段所需的信息。
  3. 精简:不被无关搜索记录和日志占满。
  4. 方向一致:仍然沿着正确的目标推进。

其中,错误信息的危害最大,其次是缺失信息,最后才是上下文过长。单纯减少 token 并不是目的,真正的目标是提高上下文中的有效信息密度。

频繁且有意地压缩上下文

很多人已经在无意中使用“上下文压缩”:当对话变长或 Agent 开始偏离方向时,让它把当前进展写入 progress.md,然后开启一个新会话继续工作。

HumanLayer 把这个行为进一步变成了完整的工作流,称为 Frequent Intentional Compaction。

它不是等上下文快用完时才总结一次,而是主动把复杂任务拆成多个阶段,让每个阶段都产生结构化的 Markdown 工件:

Research → Plan → Implement

每个阶段都使用相对干净的上下文,上一阶段的结论经过压缩和人工确认后,再作为下一阶段的输入。

作者给出的经验值是:处理复杂任务时,尽量让上下文使用率保持在约 40%–60%,不要等到窗口接近极限才开始整理。这个数字不是硬性规则,它表达的是一种工作习惯:在上下文质量开始下降前主动切换阶段。

Research:先理解代码,再决定怎么改

Research 阶段不写实现代码,重点是把问题放回现有系统中理解。

一份有效的研究文档至少应该说明:

这个阶段可以大量使用子 Agent。子 Agent 的价值不是扮演“架构师”“测试工程师”等角色,而是使用独立的上下文完成搜索、阅读和总结,再把结论返回给主 Agent。

这样,主上下文不需要保存所有 grep、文件读取和试错过程,只需要保留经过整理的系统理解。

研究阶段也最容易产生高杠杆错误。错误地理解一个函数,只会影响一个局部判断;错误地理解整个系统的边界,则可能让后续计划和实现全部建立在错误方向上。

因此,研究文档不能直接当作真相。人需要检查它是否遗漏关键路径、是否误读现有行为,以及问题是否真的应该在 Agent 建议的位置解决。

Plan:把隐含的实现决定提前暴露

计划不是简单列出“修改后端、补充测试、更新文档”。真正有用的计划应该具体到:

研究之后再做计划,与直接让 Agent 根据需求生成计划,往往会得到明显不同的结果。

没有研究支撑的计划可能也能“修好问题”,但更容易选择错误的修改层级,或者使用不符合代码库惯例的测试方式。基于研究的计划更有机会在正确的模块解决问题,并复用已有抽象。

计划的另一个作用,是把原本要到代码审查阶段才发现的分歧提前暴露出来。文件结构、函数边界、错误处理方式和测试策略,在计划阶段调整只需要修改几段文字;等 Agent 已经生成几千行代码,再改变这些决定,成本会高得多。

Implement:按阶段实现,而不是一次生成全部代码

实现阶段按照计划逐步推进,每完成一个阶段就运行对应验证。

对于复杂任务,可以在每个阶段完成后,把当前状态重新压缩回计划文件:

计划中的阶段
→ 实现
→ 测试
→ 人工检查
→ 更新状态
→ 开启下一阶段

这样,即使中途更换会话或 Agent,也不需要依赖已经膨胀的聊天历史恢复现场。

只有实现阶段需要独立 worktree。研究和计划主要产生 Markdown 文档,可以在主分支上完成;真正修改代码时,再用 worktree 隔离并行方案或不同实现。

人应该审查哪里

这套方法最重要的观点,并不是必须遵循 Research、Plan、Implement 三个固定步骤,而是重新安排人的注意力。

一条错误代码通常只造成局部问题,但一条错误计划可能生成几百行错误代码;一条错误研究结论,则可能让整个实现方向偏离。

错误的系统理解

错误的实现计划

大量方向一致但整体错误的代码

因此,人应该优先检查杠杆最高的环节:

作者认为,认真审查 200 行研究和计划,往往比最后艰难地阅读 2,000 行 AI 代码更有效。

这并不意味着代码审查不再重要。代码审查除了发现错误,还承担团队认知同步的作用:让成员知道系统发生了什么变化,以及为什么这样变化。AI 提高代码产量之后,这种认知同步反而更加重要。

后来的反思:上下文工程还不够

在后续文章 Why Software Factories Fail 中,作者开始反思一种更激进的设想:

只要增加足够多的 Agent 循环、自动评审、测试和 lint,就可以让软件工厂在无人监督的情况下持续工作。

问题在于,测试很擅长回答“程序有没有满足某个可观察行为”,却不擅长判断:

测试结果通常几秒就能得到,而糟糕架构的代价可能几个月以后才会出现。那时,一个原本只需要修改一处的需求,可能不得不同时调整十几个文件。

自动评审和更多 token 可以提高下限,抓住明显错误,但不一定能提高系统设计的上限。可维护性暂时没有一个快速、可靠、可以大规模用于训练和验证的判定器。

因此,作者后来把早期的 Research、Plan、Implement 扩展成四个更贴近工程决策的阶段:

  1. Product Requirements;
  2. System Architecture;
  3. Program Design;
  4. Vertical Slices。

Product Requirements:先确认为什么要做

产品审查需要固定两个问题:

  1. 用户真正遇到了什么问题?
  2. 上线后如何判断这件事值得做?

成功标准最好是用户结果,例如完成某个流程所需时间缩短,或者某类支持工单明显减少;也可以是错误率、延迟等技术指标。

这个阶段应尽量停留在用户体验层面,不要过早陷入实现细节。对于界面功能,粗糙但可交互的 HTML 原型,常常比几段抽象描述更容易让团队达成一致。

并非所有任务都需要产品审查。文案调整、一次性脚本和复现明确的小 bug,可以直接交给 Agent。完整流程适用于理解错误代价较高的任务。

System Architecture:明确系统边界和数据流

架构阶段描述功能如何进入现有系统:

流程图、时序图和接口草案可以帮助对齐,但架构正确并不等于实现代码自然会正确。架构图通常停留在模块层面,模型仍然可能在模块内部生成难以维护的程序结构。

Program Design:深入到代码形状

Program Design 是作者认为最容易被忽略的一层。

在真正写代码前,还需要继续明确:

作者推荐使用轻量的伪代码,而不是制作过度精细的图。

调用链可以写成:

entrypoint
  runCommand
+   handleCreateResource
+     ResourceClient.create(input)
+       POST /resources
+     renderResult
-   legacyCreateFlow

文件结构可以写成:

src
└── resource
    ├── resource-client.ts       # 新增:封装接口调用
    ├── resource-client.test.ts  # 新增:验证数据映射
    └── resource-route.ts        # 修改:接入创建流程

类型和函数签名也应该提前写清楚。它们不需要成为完整代码,但应该暴露最重要的程序设计决定。

这些内容由 Agent 起草,人负责讨论和修正。很多原本要在代码审查时隐式做出的判断,可以在写代码前以更低成本完成。

Vertical Slices:用可运行的切片推进

Coding Agent 很容易生成横向计划:

数据库迁移
→ Service
→ API
→ 前端
→ 最后统一测试

这种方式在很长一段时间里都无法真正体验功能。如果早期方向错误,等到能够运行时,可能已经生成大量代码。

纵向切片更强调尽快打通一个可以观察的最小路径:

先定义 API,并返回 Mock 数据
→ 用 curl 验证
→ 前端接入 Mock API,并在浏览器中调整
→ 接入 Service
→ 接入数据库
→ 补充业务规则和异常处理

每个阶段都有可以触摸和验证的结果。一次只让 Agent 完成一到三个切片,检查 100–200 行代码后及时纠偏,比最后面对一个数千行的大型 PR 更可控。

这与传统工程中的 tracer bullet 思路接近:先打通一条真实路径,再逐步扩展宽度和完整性。

Benchmark 暴露出的长期维护问题

仓库还记录了使用 SlopCodeBench 评估 Coding Agent 的实验。

传统 benchmark 往往一次性给出完整需求,而 SlopCodeBench 会分多个 checkpoint 逐渐增加要求。模型需要持续修改同一个代码库,并保证前面已经实现的行为不发生回归。这种形式更接近真实软件维护。

作者在一个包含 17 个 checkpoint 的小规模实验中观察到:

这个实验样本很小,不能据此全面评价模型能力,但它提供了一个方向性信号:模型可以在单次任务中表现很好,却可能在连续需求下不断积累早期设计缺陷。

更值得注意的是,代码复杂度、函数数量和 lint 规则只能作为代理指标。它们可以重复计算,却不能单独回答“这个代码库未来是否容易演进”。这也是完全无人软件工厂当前最难解决的问题之一。

一套更适合实际使用的流程

综合这些文章,可以得到一套按任务复杂度分级的工作方式。

小任务

适用于文案调整、明确的小 bug、一次性脚本:

定位问题 → 修改 → 测试

不需要为了流程而制造文档。

中等任务

涉及多个文件,但系统边界相对清楚:

简短研究
→ 一份包含系统设计和实现步骤的计划
→ 分阶段实现与验证

大型任务

涉及新功能、核心重构、并发、性能或跨模块变化:

产品目标
→ 代码库研究
→ 系统架构
→ 程序设计
→ 纵向切片计划
→ 分阶段实现
→ 每阶段测试和人工审查

每完成一个阶段,就把经过确认的结论压缩成下一阶段需要的上下文。不要让下一阶段重新经历全部搜索过程,也不要把未经验证的猜测当作事实继续传递。

这套方法真正解决了什么

Advanced Context Engineering 并不是一组“神奇提示词”,也不能保证 Agent 自动解决所有复杂任务。

它解决的是三个更具体的问题:

1. 控制上下文质量

让每个阶段只保留完成当前工作所需的高密度信息,减少历史搜索、日志和失败尝试带来的噪声。

2. 把人工判断放在高杠杆位置

人在系统理解、需求、架构和程序设计阶段纠正一次,可以避免后续大量返工。

3. 让错误尽早暴露

通过纵向切片和阶段性验证,不让 Agent 一次生成几千行代码以后才第一次运行系统。

它最终追求的不是让 Agent 写出更多代码,而是让人和 Agent 形成一种更可靠的工程协作方式:

人负责目标、边界、判断和验收;Agent 负责搜索、整理、生成和执行。上下文则是两者之间最重要的接口。

参考资料


Share this post:

Next Post
如何更舒服地过一生:一份关于金钱、选择、关系与健康的行动清单