上下文管理:让它看见该看的,而不是全部
一个任务效果差,八成不是模型不行,而是它看到的东西不对:贴了 800 行文件,其实只用一个类型定义;真正该读的三个调用方没给;反倒把锁文件、生成代码、三个月前的报错日志一起塞了进去。代码场景的上下文素材是可检索的仓库,所以核心动作就三步:先定位、再给料、用完就清。
三层上下文:常驻、按需、临时
| 层次 | 载体 | 放什么 | 容量控制 |
|---|---|---|---|
| 常驻 | AGENTS.md、README、docs/agents/codemap.md | 命令入口、目录职责、约定与边界 | 越短越稳,控制在百行内 |
| 按需 | 具体文件、类型定义、调用方、测试、接口文档 | 本次任务真正相关的代码 | 用检索与 @file 精确取,不整仓灌入 |
| 临时 | 报错日志、git diff、本次任务描述 | 当次排错所需的最小现场 | 用完即弃,别留成历史包袱 |
分清这三层,大部分「效果不稳定」问题就有了答案:要么常驻层没写好(每次都要重讲规矩),要么临时层没清干净(旧错误被当成新事实)。
别贴代码,给检索指令
它自己检索回来的上下文比你手工挑的更准,也更省事——前提是把检索步骤写清楚。
先不要改代码。按下面顺序检索并汇报:
1) 用 grep 找出 UpdateOrderStatus 的所有定义与调用点,列出「文件:行号」;
2) 打开调用点最多的 3 个文件,说明调用链与各自的责任;
3) 列出相关测试文件,以及它们覆盖了哪些场景、漏了哪些;
4) 汇报你读了哪些文件、哪些没读、还缺什么信息。
我确认后你再动手改。
第 4 条是关键:让它交代读了什么,你才能判断它的结论有没有建立在错误或缺失的上下文上。
给任务配一个「上下文包」
比零散交代更好用的是把上下文写成一份短文件,随任务一起给它。六项即可,缺哪项就在那一项写「待确认」:
## 任务:给 GET /orders 增加分页
- 验收:`go test ./internal/order/... -run TestListOrders -count=1 && make check`
- 涉及:`internal/order/handler.go`、`service.go`、`repo.go`
- 参照:`internal/user/handler.go` 的 ListUsers(照抄它的分页风格与参数名)
- 约束:不改表结构;不引入新依赖;现有响应字段与顺序保持不变
- 样例:见 `testdata/orders_page.json`
- 未知:前端是否依赖返回数组长度?——先确认,再动手
这份东西同时解决三件事:范围清楚、风格有参照、未知项被显式提出。它还可以直接落盘成 docs/agents/task-orders-page.md,下次开新会话接着用。
大仓库怎么给上下文
仓库大到无法整体读完时,先给「地图」,再给「街区」:
# 先确认仓库边界与规模(这些数字本身就是重要上下文)
git rev-parse --show-toplevel
go list ./... | head -30 # Go:包清单
find apps -maxdepth 2 -type d | head -20 # monorepo:应用边界
rg --files-with-matches 'type Order struct' # 关键类型的落点
rg -c 'UpdateOrderStatus' --glob '!vendor/**' | sort -t: -k2 -nr | head
再配一份人工维护的、只有二三十行的代码地图 docs/agents/codemap.md:
# 代码地图
- 订单主链路:handler → service → repo,入口 `internal/order/handler.go`
- 幂等:Redis 键 `order:idem:<key>`,实现见 `internal/platform/idempotency`
- 金额单位:全链路统一用「分」,出入参转换只在 handler 层做
- 异步任务:`internal/worker/`,任务定义集中在 `task.go`
这是投入产出比最高的一份上下文:人维护它只需半小时,却能让 AI 少走一半弯路。
上下文预算:三类内容不值得给
| 内容 | 为什么不该给 | 换成什么 |
|---|---|---|
锁文件、dist/、vendor/、*.min.js、快照 | 体积大、信息密度低,挤掉真正相关的代码 | 写进工具的排除配置 |
| 整个文件只为看一个类型 | 无关代码稀释注意力 | 只贴该类型,或让它自己 grep |
| 历史对话里的旧结论 | 可能已被推翻,却被当成前提 | 只保留最新结论,开新会话 |
{
"excludeGlobs": [
"**/vendor/**", "**/dist/**", "**/*.lock", "**/*.min.js",
"**/__snapshots__/**", "**/testdata/fixtures/large/**"
]
}
会话管理:一个任务一个会话,结论落到文件
上下文里最贵的不是代码,而是已经过期却还在被引用的对话历史。两条纪律:
- 换任务就开新会话,把上一段结论写成文件带走,而不是靠聊天记录回忆。
- 阶段性成果落盘,让下一次会话有干净的起点:
请把当前进展写到 docs/agents/task-orders-page.md,包含四节:
已完成(含改动文件清单)、当前状态(make check 的结果)、未决问题、下一步命令。
写完就停,不要继续改代码。
下一次只要说「读 docs/agents/task-orders-page.md,继续未决问题」,就省掉了重新交代背景的成本。
常见坑
| 坑 | 后果 | 规避 |
|---|---|---|
| 一次粘贴十个文件 | 关键信息被稀释,答案泛化 | 先给检索指令,只喂相关片段 |
| 只给文件不给验收方式 | 它不知道「读到哪里算够」 | 上下文包里写清验收命令 |
| 让它在无关代码里找答案 | 编造调用方与工具函数 | 指明关键类型的落点,或用 grep 定位 |
| 生成代码、锁文件进了上下文 | 白占预算还引入噪音 | 配排除规则 |
| 一个会话从需求聊到上线 | 旧结论被反复当成前提 | 按任务切会话,结论落盘 |
| 代码地图只在某人脑子里 | 人和 AI 都要重新摸索 | 落成二三十行的 docs/agents/codemap.md |
小结:上下文管理的目标不是「给得多」,而是常驻层薄而稳、按需层用检索精确取、临时层用完就清;别贴代码,写检索指令并让它汇报读了什么;每个任务配一份六项上下文包,阶段性结论落成文件而不是留在对话里——你给它的现场越准,它需要猜的东西就越少。