Context Engineering:让 AI Coding Agent 真正读懂大型代码库
明明找对了文件,为什么还是改错了?
假设你给 AI Coding Agent 一个需求:
允许商家配置用户可以在下单后多少分钟内取消订单;没有配置时,仍然使用 15 分钟。
Agent 搜索 CANCEL_WINDOW_MINUTES,很快在订单服务里找到常量,把它改成配置项,补了一条单元测试,然后告诉你:“任务已完成。”
代码看上去挺干净。真正跑起来,问题却一个接一个:
- Web 端仍然在第 15 分钟隐藏取消按钮;
- 对外 API 仍然把截止时间写死;
- 后台 Worker 里也有一个
15,但它控制的是另一种超时,根本不应该改; - 项目架构规定配置必须通过
PolicyService读取; - 单元测试绿了,用户在第 20 分钟仍然无法取消。
Agent 找到了一个正确文件,却没有找到这项行为的完整路径。
很多人遇到这种情况,会立刻想到三个办法:
- 再多选一些文件;
- 换一个上下文窗口更大的模型;
- 给代码库加向量索引、RAG 或 RepoWiki。
这些办法都可能有用,但没有一个会自动解决问题。

想把这些工具用好,我们得先弄明白一件事:Agent 所谓的“理解代码库”,并不是把整个项目背下来。它是在不断重复一个过程:找线索、读少量材料、做一次判断、执行,然后根据结果继续找。
上下文工程,就是认真设计“它下一步应该看见什么”。
一、Agent 不会从第一个文件读到最后一个文件
一个开发者刚加入项目,通常不会按字母顺序读完所有源码。他会先理解任务,再问几个问题,顺着调用链找下去,最后运行代码验证。
AI Coding Agent 的工作方式很像,只是速度更快、动作更机械:
理解需求
→ 查看项目规则和目录结构
→ 搜索可能相关的代码
→ 打开少量文件
→ 跟踪调用方和消费者
→ 修改代码
→ 运行测试、检查 Diff
→ 证据对不上时继续搜索在这个过程中,模型不需要把整个仓库一直放在上下文里。它真正需要的是:足够做出下一步判断的证据。
以取消窗口为例,只搜索“哪些文件出现了 cancel”还不够。真正有用的问题是:
- 谁负责判断订单还能不能取消?
- 默认 15 分钟从哪里来?
- 哪些模块会消费这个截止时间?
- 哪个看起来相似的超时,其实属于另一项业务?
- 哪条测试能证明用户在第 20 分钟仍可取消?
搜索工具当然重要,但会不会提出这些问题,同样重要。
二、Agent 寻找代码的五种办法
把大型代码库想象成一座图书馆。Agent 大致有五种找书方式。
1. 直接逛书架:目录和精确搜索
最简单的工具,往往是最好的第一步:
rg -n "CANCEL_WINDOW_MINUTES|cancelUntil|canCancel" .它像是拿着准确书名去查目录:速度快、结果容易核实,而且查到的是当前分支上真实存在的文件。
精确搜索特别适合:
- 函数名和类名;
- 配置 Key;
- API 字段;
- 错误信息;
- 数据库字段;
- 被复制的常量。
它的弱点是“只认字面”。如果需求写的是“取消窗口”,代码却叫 revocationDeadline,精确搜索就可能错过。
2. 描述意思找书:语义搜索和 Embedding
语义搜索不要求用词完全一样。你可以问:“系统在哪里判断订单是否仍然允许取消?”它可能找到包含 deadline、eligibility 或 policy 的代码。
背后常见的技术是 Embedding:系统把代码和问题都转换成一组数字,数字距离越近,就认为语义越相似。向量数据库负责快速找出距离较近的代码块。
不懂数学也不妨碍使用。记住一句话就够了:
向量检索擅长找到“可能在附近”的代码,不擅长证明“谁真正负责这项行为”。
它可能同时找回退款超时、订阅宽限期和 Worker 重试窗口。Agent 仍然要打开源码,确认它们承担的是不是同一项职责。
3. 翻通讯录:符号、定义和引用
找到 canCancel 以后,下一个问题通常不再是“还有什么代码和它长得像”,而是“谁调用了它?它实现了哪个接口?”
这正是 LSP、SCIP 等符号索引擅长的事情。Sourcegraph 的精准代码导航就公开说明了基于 SCIP 的定义与引用关系。
符号导航比文本搜索更能看清显式调用关系,但它也不是全能的。框架自动装配、反射、生成代码、事件消费者和 Shell Script,都可能不在这本“通讯录”里。
4. 看城市地图:Repo Map 和代码图谱
Repo Map 会把重要文件、符号和关系压缩成一张小地图,让模型先知道大致往哪里走,再打开具体文件。Aider 的 Repository Map会对重要符号进行排序,并在有限 Token 内把高价值内容放进地图。
代码图谱则会保存 Import、Call、继承、模块依赖等关系。
它们适合回答:
“哪条路线值得继续追?”
却不能单独回答:
“这条路线是不是正确的架构?”
图谱能看见 Web 调用了 API,却不能判断 Web 是否应该自己计算业务规则。
5. 找导游:文档、Memory 和 RepoWiki
架构文档、ADR、AGENTS.md、生成式 Wiki 和 Agent Memory,会用自然语言解释项目。
它们特别适合快速了解全貌。RepoWiki 可能直接告诉新人:“订单领域负责取消资格,策略值统一从 Policy Service 获取。”这句话能省下很多次搜索。
但导游手册也会过期。Qoder 的 Repo Wiki 介绍说明了如何分析仓库、生成结构化知识,并在代码变化时更新内容。即使有更新机制,Wiki 里的句子仍然是从源码派生出来的。真正修改前,必须在当前分支重新核实它点名的文件、符号和调用路径。
三、Codebase Index 到底是什么?
“代码库索引”听起来像一项庞大的单体技术。现实中,它通常只是对一组可搜索目录的统称:
字面目录 → 标识符、路径、字符串
语义目录 → 代码与文档的 Embedding
符号目录 → 定义、引用、签名
关系目录 → Import、Call、模块连接
附加信息 → 语言、Package、分支、Revision有些产品会组合其中几种;有些 Agent 更依赖实时搜索和语言工具。界面上出现“Search codebase”,并不能证明它底层一定使用了某种向量数据库。
如果要构建语义索引,大致会经过下面的过程:
仓库文件
→ 排除密钥、生成文件和 Ignore 路径
→ 按函数、类或有意义的段落切成 Chunk
→ 补充路径、符号、语言和 Revision
→ 计算 Embedding
→ 保存起来供后续检索当你提出问题时,系统会找出几段可能相关的代码,也可能把它们和精确搜索结果合并,再进行排序,最后只把选中的部分交给模型。
这里有个很容易忽略的重点:**索引通常在模型当前上下文之外。**它负责挑选什么内容进入上下文,并不会把整个向量库一次性塞给模型。
可以把向量库理解为仓库目录。真正搬到 Agent 工作台上的,仍然只是少数几个箱子。
Cursor 的安全文档提供了一个具体例子:它公开描述的索引边界涉及代码 Chunk、Embedding 和经过混淆的文件路径元数据。其他工具可能选择不同的本地或托管方式。敏感仓库启用索引前,一定要看当前产品的真实文档,而不是只听“本地知识库”这个名字。
四、主流 AI Coding Agent 只是组合方式不同
行业里并不存在唯一的“标准 Agent 架构”。从用户能够观察的行为来看,大致可以分成几类。
Codex、Claude Code 这类终端型 Agent,可以实时查看目录、执行命令行搜索、读取文件、检查 Git 和运行测试。即使没有先给项目建立专用向量库,它们也可以一边工作一边探索。AGENTS.md、CLAUDE.md 等文件则负责提供长期项目规则。
Cursor 这类编辑器型产品,除了代码库检索和项目 Rules,还能利用当前打开的文件、光标位置、诊断信息和最近看过的代码。编辑器本身就提供了一部分天然上下文。
Sourcegraph 这类代码智能平台,更强调经过索引的搜索、符号关系和跨仓库导航。当“查找所有引用”要跨多个仓库时,它们的价值会更明显。
Aider Repo Map、RepoWiki 这类地图和知识工具,会先压缩仓库再交给模型。前者偏结构导航,后者偏自然语言解释。
真正好用的流程,往往会把它们组合起来:
项目规则告诉 Agent 应该怎样工作
精确搜索寻找已知名称
语义搜索发现不同说法
符号和地图连接文件关系
文档解释设计意图
测试告诉 Agent 现在到底是不是真的所以,不要再问“哪个工具能一次理解整个仓库”。更实用的问题是:
我下一步最需要消除哪一种不确定性?
五、为什么窗口更大,Agent 仍然会迷路?
桌子大当然有用,但把整个仓库倒在桌上并没有用。
假设你一次附加 80 个文件、三份架构文档、一个月的对话历史和完整测试日志。正确答案可能真的在里面,但模型现在多出了好几个难题:
- 哪份文档描述的是当前分支?
- 四个相似服务中,谁拥有这项规则?
- 最新修改之后,那段长日志还有没有用?
- 旧对话和当前源码冲突时应该信谁?
- 哪条验收条件最重要?
长上下文利用研究曾发现,重要信息被埋在很长输入中间时,模型不一定能稳定利用。不同模型和任务会有差异,但工程结论很朴素:装得下,和选得对,是两件事。
Agent 会话还有另一个问题。每次工具调用都会继续增加搜索结果、源码、测试日志和中间计划。任务做得越久,工作台可能越乱。Anthropic 的上下文工程指南也强调,要把上下文看成需要持续整理的有限资源。
好上下文并不只是“少”,而是同时满足:
- 和当前判断有关;
- 对当前分支足够新;
- 权威性足以支撑这条结论;
- 最后有办法验证。
六、跟着一个任务走完整条代码路径
回到取消窗口需求。
搜索前,先把一句需求整理成小型任务契约:
行为
- 商家可以配置取消窗口,单位为分钟。
兼容性
- 未配置的商家继续使用 15 分钟。
- 现有 API 客户端保持兼容。
明确不改
- 支付捕获时间。
- 废弃取消请求的过期逻辑。
验收证据
- 默认值和自定义值的单元测试。
- API 契约测试。
- 端到端:配置 30 分钟后,第 20 分钟仍可取消。这样一来,Agent 的每次搜索都有明确目的。
先找显眼的名字
rg -n "CANCEL_WINDOW_MINUTES|cancelUntil|canCancel" \
services packages apps tests docs这一步会找到当前常量、对外字段和明显消费者。
再找背后的职责
rg -n "CancellationPolicy|PolicyService|eligib|deadline" \
services packages apps tests docs如果工具支持语义搜索,还可以直接问:“系统在哪里判断订单是否仍然允许取消?”它可能找到完全不同的命名。
对强候选项向外扩一跳
找到 canCancel 后,继续检查:
- 谁调用它;
- 哪个公开响应暴露了结果;
- 哪段 UI 消费截止时间;
- 策略和配置从哪里读取;
- 哪些测试真正观察这项行为;
- 最近是否有 Commit 移动过这项职责。
正是在这“一跳”里,Agent 会发现两个重要事实:
- Web 端复制了 15 分钟规则,不应该继续自己做决定;
- Worker 的 15 分钟只是数字相同,实际属于另一项行为,不能改。
能够正确排除一条看似相关的路径,也是“理解代码库”的一部分。检索结果更多,不代表检索更好。
七、把探索结果整理成一个小工作包
搜索完成后,不要让所有打开过的文件一直占据前台。让 Agent 把已经核实的内容压缩成一个上下文包:
# 上下文包:可配置的取消窗口
## 必须改变什么
- 每个商家可配置分钟数,默认仍为 15。
- 现有 API 客户端继续兼容。
## 当前分支上已经核实的事实
- PolicyService 已经提供商家策略值。
- 订单服务拥有 canCancel,并计算 cancelUntil。
- Web 端重复了旧的 15 分钟判断。
- 过期 Worker 的 15 分钟属于另一条生命周期。
## 计划修改
- 在策略读取模型中增加 cancellationWindowMinutes。
- 保持订单服务为唯一决策 Owner。
- 让 Web 端消费 cancelUntil。
## 怎样证明完成
- 单元测试:默认 15、自定义 30。
- 契约测试:现有响应仍然有效。
- 端到端:第 20 分钟可取消。
- Diff:支付和过期 Worker 没有变化。
## 仍然不知道
- 产品是否规定了最大值。这种上下文包不是所有小修改都必须走的形式流程。跨模块、公共契约、数据模型、权限或业务规则发生变化时,它才特别有价值。
它最大的好处,是把事实、计划和未知问题分开了。
八、明天就能使用的四步工作法
完整流程可以记成四个词:定框 → 画图 → 装包 → 证明。
第一步:定框——先说清用户行为
告诉 Agent:用户应该观察到什么、什么必须保持兼容、哪些内容明确不改、什么结果算成功。
太弱的需求:
把取消时间改成可配置。更好的写法:
按商家配置订单取消窗口,默认仍为 15 分钟。保持现有 API 结构兼容。
不要修改支付和废弃请求过期逻辑。用“配置 30 分钟后,第 20 分钟
仍能取消”的端到端场景证明完成。第二步:画图——陌生区域先别急着改
对风险较高或不熟悉的模块,可以直接告诉 Agent:
先不要修改代码。找出决策 Owner、配置来源、公开契约、消费者和测试。
每个候选文件都说明为什么相关。名字相似但核实后排除的路径也要记录。这样可以避免“第一个看起来合理的搜索结果”直接变成全部方案。
第三步:装包——只保留核实过的证据
要求 Agent 简短整理:当前事实、项目约束、计划修改、验证方法和未知问题。整理后的上下文包,应该明显小于用来构建它的材料。
如果摘要里出现五次“可能”“大概”,说明还没查清;如果摘要里列了 40 个文件,说明还没压缩好。
第四步:证明——让执行结果刷新地图
执行会继续产生新的仓库知识:
- 编译错误暴露了另一个消费者;
- 契约测试暴露了兼容性边界;
- 运行日志暴露了缓存或另一个进程;
- Diff 暴露了意外修改。
不要把这些只当成需要绕开的障碍。它们是新证据。先更新地图,再继续修改。
九、看到什么症状,就用什么办法
不是每个任务都需要全套上下文工具。可以从眼前的失败现象出发。
“Agent 只改了最显眼的文件”
修改前要求它找出 Owner、调用方、消费者、公开契约、配置来源和测试。配合符号引用或“一跳扩展”。
“Agent 读了很多文件,却迟迟没有计划”
先补任务契约,再给一个探索预算。让它输出候选文件和理由,只沿最强的几条路线继续。
“语义搜索总是返回相似但不相关的代码”
增加一个精确锚点:符号、Package、API 字段或领域边界。语义检索负责提高召回,精确条件负责提高精度。
“RepoWiki 说得很肯定,但路径已经不存在”
检查它对应的来源 Revision。重新生成,或者只把 Wiki 当成搜索线索。判断当前代码库状态时,以当前源码和测试为准。
“会话开始时很聪明,做久了越来越乱”
新目标使用新任务,或者围绕当前上下文包做一次压缩。旧日志、废弃计划和无关探索不要一直留在活动工作区。
“每次任务都要重新发现相同命令和项目约定”
把稳定的仓库级规则写进 AGENTS.md 或对应产品的规则文件。AGENTS.md 规范支持按目录放置更具体的说明。一次性验收条件仍然留在当前任务里,不要写成永久规则。
十、什么时候值得建设本地知识库?
当下面一种或多种情况反复出现时,本地 Index、Repo Map 或 Wiki 才值得维护:
- 新成员总要花很久寻找同一批子系统边界;
- Monorepo 横跨很多 Package 或编程语言;
- 仅靠符号导航无法解释架构和业务意图;
- 团队经常重复相同的跨模块任务;
- 仓库数据必须留在受控环境中。
不要因为“RAG 听起来先进”就建设知识库。对于命名清楚的小仓库,rg、LSP、可靠测试和一份简洁的 AGENTS.md,可能已经是更好的系统。
无论采用什么工具,都要分清三层:
真相源 代码、Schema、测试、已接受 Spec、ADR
导航层 项目规则、搜索方法、Repo Map
缓存层 Embedding、生成式 Wiki、图谱快照、Memory缓存可以很快,也可以很好用,但它应该能够重建。如果一条架构决策只存在于生成式 Wiki,就把它移到经过评审的真相源文档中。
还要确认安全边界:
- 哪些路径会被排除?
- 源码是否离开本机?
- Chunk、Embedding 和路径元数据存在哪里?
- 谁可以查询共享索引?
- 文件删除或权限收紧后,索引中的副本怎样清理?
上下文质量也包括:不要检索出 Agent 本来就不该看到的材料。
十一、怎样判断上下文真的变好了?
不要看生成式 Wiki 有多漂亮,而要拿几个真实的跨文件任务来验证:
- 第一版计划是否找到了正确的决策 Owner?
- Review 时发现的漏改消费者是否减少?
- Agent 是否少读了无关文件?
- 每条验收条件是否都有对应测试?
- 过期摘要是否曾把实现带偏?
- “再试一个 Patch”的循环是否减少?
在取消窗口案例里,新流程只有同时找到策略来源、订单决策、API 契约、Web 消费端和端到端测试,并且正确排除 Worker 超时,才算成功。
这比“Prompt 少用了 30% Token”更有意义。目标不是上下文越小越好,而是:在不漏掉完整行为的前提下,让上下文尽可能小。
一段可以直接复用的 Prompt
修改前,先为这个任务建立代码路径地图。
1. 重述用户可观察行为、兼容性要求和明确排除项。
2. 阅读当前目录适用的项目规则。
3. 找出决策 Owner、数据或配置来源、公开契约、消费者和测试。
4. 已知名称使用精确搜索,不熟悉的说法使用语义搜索。
5. 从强候选项沿定义和引用向外扩一跳。
6. 分开记录已核实事实、推断和未决问题。
7. 返回一个简短上下文包,包含改动集合与验收证据。
然后再实施。把编译错误、失败测试、日志和 Diff 当成新证据。如果它们
改变了地图,先更新计划,再继续修改。结语:不要把整个仓库倒在 Agent 桌上
Codebase Index、向量库、符号图谱、Repo Map 和 RepoWiki,并不是同一个问题的五种竞品。它们是帮助 Agent 找到下一条有效线索的不同办法。
模型仍然需要判断:
- 什么行为真正重要;
- 哪些证据符合当前分支;
- 哪条关系必须继续追;
- 哪条看似相关的路径应该排除;
- 什么结果能够证明任务完成。
这些判断,一部分由 Agent 完成,一部分由工具提供,另一部分来自你设计的上下文。
大窗口只是给 Agent 一张更大的桌子;上下文工程负责把正确的证据放到桌上。