上下文工程:让模型看对东西
智能体的表现取决于它每一步读到了什么。本课讲清上下文里装了什么、为什么越长越差,以及压缩、即时检索、外部记忆、子智能体四种手段各自适合什么场景,最后落到项目说明文件该怎么写。
学完这一课,你能
- 能说清一次模型请求里到底装了哪些内容,并用工具查看自己会话的构成
- 能解释上下文变长为什么会让表现变差,并据此决定何时清空、何时压缩
- 能按任务特征在压缩、即时检索、外部记忆、子智能体之间做选择
- 能写出一份短而有效的 CLAUDE.md / AGENTS.md,并删掉没用的内容
下午两点开的会话,到了五点半,智能体开始「变笨」:说过不要碰迁移文件,它碰了;让它用项目里已有的日期工具,它又自己写了一个;纠正到第三遍,它道歉,然后犯同样的错。很多人这时会怀疑模型状态不好,或者想换个更大的模型。更常见的真相是:它的上下文已经被你们三个小时的对话、读过的文件和失败的尝试塞满了,而你写的那几条关键规则,淹没在里面。
这一课讲上下文工程(context engineering):不是怎么把提示词写漂亮,而是怎么决定模型每一步看到什么。对每天和智能体协作的人来说,这是比提示词技巧更值钱的基本功。
模型每一步到底看到了什么
先纠正一个直觉:模型并不「记得」上一轮。每次请求,工具都会把全部内容重新发送一遍:系统提示词、工具定义、项目说明文件、此前每一条消息、每一次工具调用的结果,最后才是你这一句话。Claude Code 官方文档说得很直白:模型在两次请求之间不保留任何东西。
在你敲下第一个字之前,Claude Code 已经往里装了这些东西:
| 内容 | 什么时候进来 | 特点 |
|---|---|---|
| 系统提示词与工具定义 | 会话开始 | 你改不了,但 MCP 和插件会增加它 |
| CLAUDE.md(或 AGENTS.md)与自动记忆 | 会话开始 | 每次会话都带着,体量归你管 |
| MCP 工具名、Skill 描述 | 会话开始 | 只有名字和描述,正文按需加载 |
| 读过、改过的文件 | 智能体读取时 | 大文件动辄几千到几万 token |
| 命令输出、测试日志 | 每次工具调用后 | 最容易失控的一项 |
| 消息与回复 | 每一轮 | 包括所有失败的尝试 |
会话感觉不对劲时,先运行 /context 看各类别占用和已加载的说明文件,再决定怎么处理;/memory 可打开并编辑这些文件。
为什么越长越差
「上下文窗口有一百万 token,塞满又何妨?」问题恰恰在这里:窗口能装下,不等于模型能用好。几项公开研究给了不同角度的证据:
- Lost in the Middle(Liu 等,2023,发表于 TACL):相关信息在输入开头或结尾时模型表现最好,在中间时明显下降,专为长上下文设计的模型也如此。
- Context Rot(Chroma,2025 年 7 月):测试了 18 个模型,把任务难度固定、只拉长输入。结论是输入越长,表现越不稳定,哪怕任务本身很简单;干扰项越多,下降越明显。报告还提醒:在「大海捞针」基准上拿满分,不代表长上下文真的可靠。
- Anthropic 的解释框架:他们把上下文看成有限的「注意力预算」,每多一个 token 就消耗一点,因此要追求「能把事情做对的最小高信号 token 集合」。
这些研究多数测的是检索和问答,不是写代码,不能直接照搬结论。但它们与工程体感一致,也与 Claude Code 官方最佳实践开篇的判断一致:大多数建议都源于同一个约束,上下文很快填满,而且越满表现越差。
我自己的判断是,工程上要警惕的不是「遗忘」,而是稀释与污染:
- 稀释:你的关键规则只有三行,周围是三万 token 的日志,规则的相对权重就被冲淡了。
- 污染:失败的尝试、被推翻的假设留在历史里,后续步骤仍会受它们影响。官方的建议很具体:同一个问题纠正超过两次,就
/clear,带着学到的东西写个更好的开场提示重来。干净会话加更好的提示,通常胜过堆满纠错的长会话。
四种手段,各管什么
管理上下文的手段,归纳下来就四种。它们不是互相替代,而是针对不同情形。
| 手段 | 做法 | 适合 | 代价 |
|---|---|---|---|
| 压缩(compaction) | 把历史总结成摘要,用摘要接着干 | 同一个任务连续推进,历史里有大量需要保留的决定 | 细节会丢,摘要质量取决于它「觉得」什么重要 |
| 即时检索(just-in-time) | 只保留文件路径、查询语句这类轻量引用,需要时再用 grep、读文件拉进来 | 代码库很大、事先不知道要看哪里 | 多几步探索;没有方向感时会乱读 |
| 外部记忆(notes) | 把进度、决定写进文件(PLAN.md、progress.md),需要时读回来 | 跨会话的长任务,或要经得起 /clear |
要维护;写得差就是新的噪声 |
| 子智能体隔离 | 把调研、跑测试交给独立上下文,只收一份摘要 | 需要读大量文件、产出冗长输出的子任务 | 额外的 token 开销;摘要之外的细节拿不到 |
压缩是最常用也最容易被误解的。Claude Code 会在接近上限时自动压缩,你也可以手动 /compact 重点保留 auth 相关的改动,让摘要偏向你关心的部分。压缩之后,项目根目录的 CLAUDE.md、自动记忆和计划模式写出的计划文件会从磁盘重新注入,最近读改过的文件最多重读五个,超过 5000 token 的只留路径引用。而子目录里按需加载的 CLAUDE.md、带 paths: 的规则,压缩后要等重新触发才会回来,所以必须长期生效的规则,放根目录说明文件里。可以在 CLAUDE.md 里写一句压缩指引,比如「压缩时务必保留已修改文件清单和测试命令」,让摘要别丢这两样。
即时检索是 Claude Code 自己的默认思路:CLAUDE.md 预先加载,其余靠 glob 和 grep 现用现找。Anthropic 的文章把它概括为混合策略:一部分关键信息预加载,其余让智能体自己探索。对你的启示是:文件命名和目录结构本身就是检索线索,第 6 课会专门讲。
外部记忆解决的是「会话会结束」。Anthropic 在长时任务的 harness 文章里给过一个很实用的模板:第一次会话生成初始化脚本、进度文件和一份功能清单(JSON,每项有 passes 标记),之后每次会话只做一项、提交一次、更新进度。新会话靠读这几个文件进入状态,不依赖前面的对话。他们特意选 JSON 而不是 Markdown,因为模型更不容易随手改写它。
子智能体是保护主上下文的利器。读二十个文件弄清鉴权流程,让子智能体去做,主会话只收一段总结。需要记住几点:子智能体从干净的上下文开始,看不到你的对话;它会读取 CLAUDE.md(内置的 Explore 和 Plan 除外),但不会带上主会话的自动记忆,所以关键约束要在委派的提示里重述一遍。别神化它:Anthropic 自己的多智能体研究系统文章提到,多智能体消耗的 token 约是普通对话的 15 倍,并且不适合「所有智能体需要同一份上下文」或「彼此依赖很强」的任务,文中还特别提到大多数编码任务可并行的子任务比研究类任务少。子智能体适合隔离「读得多、产出少」的活,不是并行写代码的万能药。
怎么选:一张速查
- 会话已经长了,但还在做同一件事 →
/compact,带上重点说明。 - 要换一件不相关的事 →
/clear,别带着旧上下文。 - 刚走了死路 →
/rewind回到之前的节点,别原地反复纠错。 - 只想问个小问题 →
/btw,回答不进入对话历史。 - 要读一大堆文件才能回答 → 子智能体。
- 任务要跨天、跨会话 → 写进文件(计划、进度、决定),下次读回来。
项目说明文件:写什么,不写什么
CLAUDE.md 是你能直接影响上下文的一份文件,因为它每次会话都在,每一行都在和任务竞争注意力。官方的判断标准很好用:对每一行问「删掉它,智能体会犯错吗?」不会,就删。
| 该写 | 不该写 |
|---|---|
| 智能体猜不到的命令(怎么跑单个测试、怎么起依赖服务) | 读代码就能看出来的东西 |
| 和默认习惯不同的代码风格 | 语言的通用惯例 |
| 分支、提交、PR 的约定 | 整个 API 文档(给链接就行) |
| 项目特有的架构决定与「为什么」 | 逐个文件的介绍 |
| 环境的坑(必需的环境变量、不能碰的目录) | 「写干净的代码」这类空话 |
篇幅上,官方建议每个 CLAUDE.md 控制在 200 行以内。用 @路径 导入别的文件能帮你组织,但导入不会减少上下文占用,被导入的内容随它一起加载。真正按需加载的办法有两个:子目录里的 CLAUDE.md(读到该目录文件时才加载),或带 paths: 的规则文件。偶尔才用的流程,如发布检查,更适合做成 Skill。
还有一个常被忽略的事实:说明文件是建议,不是强制。官方文档明确说它是上下文而非配置,需要「无论如何都要发生」的事,用 Hook。「每次编辑后跑格式化」写进 CLAUDE.md,智能体大概率会做;写进 Hook,一定会做。
一份够用的示例,不到三十行:
# 项目:shop-api(Node 22 / TypeScript / Postgres)
## 命令
- 校验(改完一批代码后必须跑):`npm run verify`(格式、类型、lint、单测)
- 只跑一个测试:`npm test -- src/orders/refund.test.ts`
- 本地依赖:`docker compose up -d db`,然后复制 `.env.example` 为 `.env`
## 约定
- 数据库访问只能经过 `src/db/queries/`,路由里不要写 SQL
- 已合并的迁移文件不可修改,只能新增迁移
- 金额一律用整数分,不要用浮点
- 提交信息格式:`模块: 动词开头的说明`
## 不要碰
- `src/generated/` 是代码生成产物,改动请运行 `npm run codegen`
## 压缩时
- 保留已修改文件清单和最近一次失败的测试输出
关于 AGENTS.md:它是多款工具共同认可的说明文件名。Codex 会读取 ~/.codex/AGENTS.md,再从仓库根目录一路向下合并到当前目录,每个目录最多取一个文件,合并后默认上限 32 KiB(可由 project_doc_max_bytes 调整)。Claude Code 当前版本在目录里只有 AGENTS.md、没有任何 CLAUDE.md 时会直接读它(需要 v2.1.277 及以上);两者都存在时只读 CLAUDE.md,除非 CLAUDE.md 里用 @AGENTS.md 导入它。所以多工具团队的稳妥做法是:内容写在 AGENTS.md,CLAUDE.md 只放一行导入加上 Claude 专属的少量内容。
最后一个原则:说明文件要像代码一样维护。智能体总无视某条规则,多半是文件太长、规则被淹没;总问文件里已回答过的问题,多半是写得含糊。出问题先回头检查文件,别再加一句「务必」。
请审计本仓库根目录的说明文件(CLAUDE.md,如果没有就看 AGENTS.md),目标是让它更短、更有效。
逐行判断,并把结果整理成表格(行号、原文摘要、结论、理由):
- 能通过读代码、读 package.json 或配置文件直接得出的内容,标记为「可删」;
- 互相矛盾或已经过时的规则,标记为「冲突」,并指出矛盾的另一处;
- 只在个别目录或个别任务才用到的内容,标记为「应下沉」,建议放到子目录 CLAUDE.md、带 paths 的规则或 Skill;
- 属于必须每次都执行的动作(格式化、禁止修改某些文件),标记为「应改 Hook」;
- 其余的标记为「保留」。
先只输出表格和你的建议,不要修改任何文件。我确认后你再动手。
一个会话已经进行了三小时。你就同一个问题纠正了智能体三次,它仍然在重复犯错。最合适的做法是?
下面哪个子任务最适合交给子智能体,而不是在主会话里直接做?
动手清单
0/5延伸阅读:CLAUDE.md 与记忆、上下文管理、子智能体与并行。下一课讲怎么在动手之前先把任务想清楚:规格驱动与计划先行。
参考来源
- Effective context engineering for AI agents(Anthropic 工程博客)
- Context Rot: How Increasing Input Tokens Impacts LLM Performance(Chroma 技术报告)
- Lost in the Middle: How Language Models Use Long Contexts(Liu 等,TACL)
- Explore the context window(Claude Code 官方文档)
- Best practices for Claude Code(Claude Code 官方文档)
- How Claude remembers your project(Claude Code 官方文档)
- Create custom subagents(Claude Code 官方文档)
- Effective harnesses for long-running agents(Anthropic)
- How we built our multi-agent research system(Anthropic)
- How Claude Code uses prompt caching(Claude Code 官方文档)
- AGENTS.md 指南(OpenAI Codex 文档)
工具更新很快,若与官方文档不一致,以官方为准。
学完了吗?
标记完成会记录在本机浏览器,计入你的学习进度和连续天数。