Appearance
4.9 上下文工程(Context Engineering)
🕐 内容截至 2026-07|涉及版本:
compact-2026-01-12beta
上一节讲了 Harness 的零件,其中最核心、最值钱的一块,单独拎出来讲:上下文工程——精心经营"到底往模型的上下文窗口里塞什么"。一句话,Harness 的高手,八成功夫在这里。
回到那张"办公桌"
还记得 0.2 节的比喻吗?模型的上下文窗口就像一张办公桌桌面:面积固定,桌上摆的东西就是它当下能"看见"的全部。
🪑 核心比喻:模型是个能力超强、但只能看桌面、而且记性只到桌面的专家。桌子就这么大。你往桌上摆什么、怎么摆、什么时候清理,直接决定他干得好不好。这就是上下文工程。
桌子有两个残酷的事实(前面章节都提过,这里串起来):
所以上下文工程的目标不是"把桌子塞满",而是:在每一步,桌上只放此刻最该看的东西。 下面是四个核心手法。
手法一:On-demand Injection(按需注入)
人话:不要一开始就把所有指令、所有工具说明、所有可能用到的资料一股脑塞满上下文;而是按当前这一步真正需要什么,临时把对应的东西放上桌,用完撤走。
🪑 比喻:你不会把整个档案室搬上办公桌。你是"现在要处理 A 案子,就把 A 的卷宗拿上来;处理完收走,再拿 B 的"。
具体怎么按需注入:
- 工具:不是把 50 个工具的说明全程挂着,而是当前任务相关的工具才注入(呼应 4.2 MCP 的按需工具发现)
- 指令:行为规则在相关事件发生时才注入。比如"用户要删数据了",这时才把"删除操作的安全规则"塞进去,而不是从头到尾一直占着桌面
- 资料/记忆:用 RAG 在需要时检索相关片段塞进去(2.1),而不是把整个知识库怼进上下文
好处:省 token(=省钱省延迟),更重要的是减少干扰——桌面越干净,模型对当前任务越专注。
💡 你在 Claude Code 里看到的
system-reminder之类"适时冒出来的提示",就是按需注入:在合适的时机,把当下该遵守的规则推到模型眼前。
手法二:Compaction / Compression(压缩)
人话:桌面快满时,把旧的、啰嗦的内容摘要成一小张便签,原件收走,腾出空间继续干。
🪑 比喻:桌上堆了一上午的草稿和往来记录,桌子要满了。你把它们归纳成一页纪要——"上午确定了 X、否决了 Y、待办 Z",然后把那堆草稿收进抽屉。桌面清爽了,关键信息还在。
这就是 1.1 节的 Context Compression / Compaction。代价是细节会丢——压缩时被归纳掉的内容,模型就"看不见"了,这也是长对话里 Agent 开始忘事的根源(2.4 上下文污染)。
用好压缩的要点:
- 真正重要、不能丢的信息,别只靠对话历史 → 放进 System Prompt 或写进文件/计划清单(它们不容易被压掉)
- 压缩摘要要保留"决策和结论",可以丢过程细节
- 与其等一个超长对话被反复压缩,不如把大任务拆成多个干净的小对话
新趋势:让服务端替你压缩(Server-side Compaction)
上面说的压缩,传统上是客户端自己做的——你的 Harness 在本地把旧消息摘要掉,再重新拼一份对话历史发给 API。这有个隐蔽的坑:工具调用是成对的(模型发出 tool_use,工具返回 tool_result),客户端压缩时如果只压掉了其中一半,剩下的 tool_result 就会变成"孤儿引用"(orphaned reference)——它指向的 tool_use 已经不存在了。Anthropic 的 API 会直接拒绝这种请求,整个会话当场崩溃,这在长时间、重工具调用的会话里是真实多发的故障。
2026 年 1 月,Anthropic 推出了服务端压缩:请求时加 beta header compact-2026-01-12 和 context_management 参数(指定 compact_20260112 编辑类型),之后由 API 服务端在上下文接近上限时自动摘要旧内容。因为压缩发生在服务端、对消息结构有完整掌控,它能保证 tool_use/tool_result 永远成对处理,从根上消除了孤儿引用崩溃。
javascript
const response = await client.beta.messages.create({
model: "claude-opus-4-6",
max_tokens: 4096,
betas: ["compact-2026-01-12"], // 开启服务端压缩
context_management: {
edits: [{ type: "compact_20260112" }],
},
messages,
})
// 注意:把完整响应(含压缩块)原样追加回历史,后续轮次照常调用即可💡 你在 Claude Code 里遇到的 auto-compact 就是这套思路的产品化:上下文用量接近上限(约 75%~85%,随版本有调整)时自动触发压缩,而不是等撑爆报错。自己写 Agent 时,优先用服务端压缩;只能自己压缩时,务必把
tool_use/tool_result当作不可分割的整体来处理。
自己压缩时,该切在哪:组是原子的
上面说"把 tool_use/tool_result 当作整体",但具体怎么落成代码?关键是换一个切分单位——别按消息切,按交互组切。
一个交互组从一条 user 消息开始,包含它引发的所有工具调用、工具结果,直到下一条 user 消息为止:
[组1] user: 帮我改配置
assistant: (call_1 read)
tool: (result_1 文件内容)
assistant: (call_2 edit)
tool: (result_2 已修改)
assistant: 改好了
[组2] user: 再跑下测试
...组是原子的,边界只能落在组与组之间。 孤儿引用的成因这下就清楚了——它不是什么玄学,就是切进了组内部:截断点落在 call_1 和 result_1 之间,留下的 result_1 指向一个已经不存在的调用,API 直接 400。只要坚持"边界只在组之间",这类崩溃从根上不可能发生。
有了组,压缩就是三步:分组 → 算预算(system prompt + 预留输出 + 安全边际先扣掉)→ 从最新的组往回选,选出一段连续的后缀,装不下就停。
注意最后一步是"连续后缀",不是"挑几个塞得下的小组"。跳着挑会制造时间断层——模型看到组 1、组 5、组 6,会以为组 5 紧接着组 1 发生。宁可少留几组,也要留一段连贯的近期历史。
还有一条容易想反的:压缩不是删历史。正确做法是往会话文件里追加一条"我做了一次压缩"的记录(摘要内容 + 从哪条开始是原文),原始消息一条不删。这样审计、回退、以及给压缩算法本身写测试才成立——4.25 会把这套算法连同它背后的不变量完整展开。
手法三:Isolation(隔离)
人话:不同的子任务,用各自独立的上下文去做,互不污染;做完只把"结论"带回主线程,而不是把一路的草稿都倒进主桌面。
🪑 比喻:一个大项目,你不会把五件事的资料全堆在同一张桌子上同时做(必乱)。你给每件事一张独立的桌子(或交给不同的人),各干各的;最后每张桌子只交回一份结论,主桌面始终清爽。
体现在哪:
- Subagent(子代理):主 Agent 把子任务派给子代理,子代理在自己独立的上下文里折腾(哪怕它内部翻了 50 轮、读了一堆文件),最后只把精炼结果回传主 Agent——主 Agent 的桌面不会被子任务的过程垃圾淹没(4.6)
- Worktree:让 Agent 在隔离的 Git 工作目录里改代码,不污染你的主工作区(4.4)
💡 隔离的精髓:把"过程的混乱"关在小黑屋里,只让"干净的结果"出来。 这是长时间、多步骤 Agent 能保持清醒不跑偏的关键招。
手法四:用文件系统当外部记忆(Agent Workspace)
前三个手法都在"省着用桌面"。但桌面再会收拾,也总有装不下的时候——任务跑了几十步、读了十几个文件、生成了一份长报告。这时还有第四招:别全往桌上堆,把东西写进文件柜,需要哪份再抽哪份。 这个"文件柜+草稿纸",就是 Agent Workspace(智能体工作区)。
🪑 比喻:办公桌再大也放不下整个项目。真实的工作方式是——桌上只摆当前在看的几页,其余资料、写到一半的草稿、查到的数据,都收在旁边的文件柜里。要用哪份,起身抽出来放上桌;用完放回去。文件柜的容量,远大于桌面。
对 Agent 来说,这个"文件柜"就是它能读写的那个目录。这一招的价值在于:把上下文窗口(桌面)和长期信息(文件柜)分开——上下文只放"此刻在处理的",其余一切落地成文件。
具体怎么用:
- 中间产物落盘:让 Agent 把阶段性成果写进文件(如
notes.md、plan.md、抓来的数据data.json),而不是一直顶在对话里占着桌面。后面要用时再读对应文件。 - 长内容先存后取:抓了一个超长网页 / 读了一份大文档,不要整篇塞进上下文——先存成文件,只把"摘要 + 文件路径"留在桌面,真要看细节时再按需读回(这其实就是「按需注入」+「文件柜」的组合)。
- 计划清单当锚点:把任务拆解写进一个
TODO/计划文件,做一项更一项。哪怕对话被压缩、细节丢了,这个文件还在,Agent 重新读一遍就知道"我在干嘛、干到哪了"——文件不会被 Compaction 压掉,这正是上一节反复强调"重要信息别只靠对话历史"的落地办法。
💡 为什么文件比"硬塞进上下文"强:上下文是临时的、有限的、会被压缩的;文件是持久的、近乎无限的、随时可精确取回。所以一个成熟 Agent 的记忆,往往一半在上下文(短期、此刻在看的),一半在 workspace 的文件里(长期、随用随取)。你在 Claude Code 里看到它动不动就写个临时文件、记个清单,就是在用这一招。
Workspace 的另一层作用——隔离的物理载体:手法三的「隔离」要落地,靠的也常是 workspace。给每个 Subagent 一个独立目录、用 Worktree 让 Agent 在隔离副本上改代码,本质都是用独立的 workspace 把过程的混乱关起来。所以这四招不是孤立的,是层层咬合的。
⚠️ 常见误解:以为"模型上下文越大,就不需要文件了"。恰恰相反——上下文再大也是临时桌面,会满、会被压缩、会"中间迷失"。把长期信息放进文件柜,是为了让宝贵的桌面只服务于"此刻"。大模型时代,会用文件系统的 Agent,比硬撑大上下文的 Agent 更稳。
串起来:上下文工程就是"经营桌面"
按需注入:只把此刻要用的东西放上桌,用完撤走 → 桌面别一开始就堆满
压缩 :旧内容摘成便签,原件收走 → 桌面满了及时清
隔离 :子任务在别的桌子干,只回传结论 → 别把过程垃圾倒进主桌
文件柜 :长期信息写进 workspace 文件,随用随取 → 别什么都顶在桌面上
↓
让模型在每一步,都只盯着"此刻最该看的东西"理解了这个,你就理解了为什么"上下文窗口很大"不等于"可以乱塞"——桌子大,也要会收拾。 会收拾桌面的 Harness,才能驾驭长任务。
💡 案例:把这四招全做成显式机制的 Harness。开源的 Pi(pi.dev)把本节手法几乎逐条做成了可配置的机制:
AGENTS.md/SYSTEM.md做分层注入的项目指令、可替换的 Compaction 策略、扩展可以在每轮前动态注入/过滤消息(RAG 和长期记忆都挂这个钩子)。想研究"上下文工程怎么从理念变成机制",它是很好的样本,剖析见 4.17。
🛠️ 实战练习:观察一次"桌面经营"
在 Claude Code 里跑一个较长的多步任务(比如让它重构一个小模块、跑测试、修复),观察:
- 长对话进行中,它有没有在某个点自动压缩早期内容?压缩后它对早期细节是不是变模糊了?
- 它派 Subagent 时,主线程拿回的是"一份结论"还是"一大堆过程"?
- 任务里它是不是没有把所有工具/资料全程挂着,而是用到才取?
期望结果:你会直观感受到"上下文是稀缺资源、要主动经营",并学会一个实操习惯——重要信息别埋在长对话里,及时落到文件/计划清单,以免被压缩掉。
进阶思考:回看你自己用 AI 跑长任务时遇到的"它怎么把前面说的忘了",对照这三个手法,判断是该用按需注入、压缩策略,还是拆任务+隔离来解决。
📌 关键结论
- 上下文窗口 = 模型的办公桌桌面,固定且宝贵;上下文工程就是"经营这张桌面"
- 目标不是塞满,而是每一步只放此刻最该看的东西(大桌子也要会收拾)
- 四大手法:按需注入(用时才放)、压缩(旧内容摘成便签)、隔离(子任务另开桌,只回传结论)、文件柜(长期信息写进 Workspace 文件,随用随取)
- 重要信息别只靠对话历史,放进 System Prompt / 文件 / 计划清单,避免被压缩丢掉——文件不会被压缩掉
- 压缩优先用服务端能力(如 Anthropic
compact-2026-01-12):客户端自己压缩容易把tool_use/tool_result拆散造成孤儿引用崩溃,服务端压缩保证成对处理 - 上下文是临时桌面,Workspace 文件是持久文件柜;成熟 Agent 的记忆一半在上下文、一半在文件里
- 这是 Harness 里最值钱的功夫,也是长任务 Agent 不跑偏的关键