这篇讲上下文工程的 策略层:什么该注入、什么该延后、什么该压缩、什么该重建。下一篇会进入 结构层,拆解模型最终看到的具体数据结构。
很多人理解上下文时,会片面地理解为:system prompt 跟 user prompt。
但如果你仔细看 Claude Code,会发现它的上下文工程远比“一段提示词”复杂。它管理的是一个 上下文系统:system prompt、user context、system context、messages、attachments、memory、CLAUDE.md、skill surfacing、工具描述、compact 恢复,全都属于上下文的一部分。
这就是典型的 context engineering。
系列回顾:
Claude Code 的上下文是一组结构,不只是一段文本
在 query.ts 的参数里,你能直接看到这种拆分:systemPrompt、userContext、systemContext、messages、toolUseContext 都是独立字段。Claude Code 在进入模型前,已经把上下文拆成了多个层次,而不是把所有东西粗暴拼成一个大字符串。
这里还有一个很容易被忽略的点:toolUseContext 虽然和前面几项一起出现在参数里,但它并不只是另一块“给模型看的文本”。更准确地说,它像是整个 query loop 的运行时上下文容器,里面放着 tools、commands、model、MCP clients、readFileState、app state 读写接口,以及当前会话要继续往前跑所需的各种状态。
所以如果说 systemPrompt、userContext、systemContext 更像“送进模型前缀的内容层”,那么 toolUseContext 更像“驱动上下文工程运转的运行时层”。很多 attachment 注入、tool 调用、compact 重建和状态恢复,最后都要落到它身上。
一图看懂最终模型上下文的主要来源

这张图最重要的结论是:最终模型上下文来自多个来源,而不是一段单独的 prompt;即使都属于长期信息,memory 和 CLAUDE.md 也可能来自不同机制。
fetchSystemPromptParts() 暴露了核心设计
在 src/utils/queryContext.ts 中,fetchSystemPromptParts() 非常关键。
// src/utils/queryContext.ts
exportasyncfunctionfetchSystemPromptParts({
tools, mainLoopModel, additionalWorkingDirectories,
mcpClients, customSystemPrompt,
}: {
tools: Tools
mainLoopModel: string
additionalWorkingDirectories: string[]
mcpClients: MCPServerConnection[]
customSystemPrompt: string | undefined
}): Promise<{
defaultSystemPrompt: string[]
userContext: { [k: string]: string }
systemContext: { [k: string]: string }
}> {
默认路径下,它会返回三块基础内容:defaultSystemPrompt、userContext、systemContext。其中 defaultSystemPrompt 通过 getSystemPrompt() 生成;如果传入了 customSystemPrompt,这条路径会绕过 getSystemPrompt(),同时把 systemContext 置空。
这说明 Claude Code 对“进入模型前的固定前缀”做了明确拆分,而且这些内容也不是固定不变的。继续往后看 src/utils/systemPrompt.ts 和 src/QueryEngine.ts,还能看到后续组装:不同入口会再按条件覆盖、追加 prompt,叠加 coordinator userContext,并在某些情况下补上一段和 memory 机制有关的额外提示。
所以最终给模型的 system prompt 并不是一段静态文本,而是一组会继续被拼装和调整的结构。
messages 也不是静态历史,而是会在 query loop 里持续变形
如果只看类型定义,很容易把 messages 理解成“已有对话历史”。但在实际执行里,它在进入模型前还会继续被处理。src/query.ts 里能看到一条很长的处理链:先取 compact 边界之后的消息,再依次经过 tool result budget、snip、microcompact、context collapse、autocompact 等步骤。
换句话说,messages 不是一个原封不动被送进模型的数组,而是一个会随着预算、压缩策略和运行状态不断被投影、裁剪、替换、重建的动态上下文视图。这样再回头看 compact,就更容易理解它为什么不是一个孤立功能,而是这条处理链里最显眼的一环。
context engineering 不只关心“加什么”,也关心“别加什么”
上下文工程里最难的,从来都是判断什么该注入、什么该延后、什么该压缩,以及什么该在长会话中重新表面化。Claude Code 在这方面做了不少工程化处理。
attachments:把外部信息结构化地挂进会话
src/utils/attachments.ts 是理解 context engineering 的关键文件之一。
它处理的内容非常杂:文件附件、memory 注入、skill listing / skill discovery、plan 和 task 相关附件、MCP / tool search delta,以及图片和文档等多模态内容,基本都从这里进入会话。
看一下 Attachment 的类型定义就能感受到这个“杂”到底是什么量级:
// src/utils/attachments.ts
exporttype Attachment =
| FileAttachment // 用户 @ 引用的文件
| CompactFileReferenceAttachment // compact 后的文件引用
| PDFReferenceAttachment // PDF 文件
| AlreadyReadFileAttachment // 已读文件
| { type: 'edited_text_file', ... }
| { type: 'selected_lines_in_ide', ... }
| { type: 'todo_reminder', ... }
| { type: 'task_reminder', ... }
| { type: 'nested_memory', ... }
| { type: 'relevant_memories', ... }
| { type: 'dynamic_skill', ... }
| { type: 'skill_listing', ... }
| { type: 'skill_discovery', ... }
| { type: 'queued_command', ... }
| { type: 'diagnostics', ... }
| { type: 'plan_mode', ... }
| { type: 'auto_mode', ... }
| { type: 'mcp_resource', ... }
| AgentMentionAttachment
| HookAttachment
// ... 20+ 变体
文件、记忆、技能、任务、诊断、MCP、hooks——几乎所有“需要在模型看到之前先结构化挂进会话”的东西,都走 attachment 这条路。

Claude Code 的很多“额外能力”会在进入模型前就通过 attachment 机制显式注入,而不是等到工具执行时再临时补上。
tool descriptions 也是上下文的一部分
图里单独画出 tool descriptions,不是因为它只是“附带说明”,而是因为工具描述本身就会占用上下文预算,而且会直接影响模型怎么规划下一步动作。
从 src/constants/prompts.ts 往下看可以发现,Claude Code 不只是把工具注册给 runtime,它还会把一整套“怎么使用这些工具”的说明拼进 system prompt。这里面包括工具能力边界、优先使用哪些专用工具、什么时候不要用 Bash、如何并行调用工具,以及一些和当前模式相关的工具使用规则。
所以工具在 Claude Code 里有两层含义:一层是 runtime 里真实可调用的能力,另一层是模型在当前上下文里“看见的能力描述”。模型能不能正确选择工具、知不知道该优先用哪一类工具,很多时候并不取决于 runtime 里有没有这个工具,而取决于这段工具描述有没有被清楚地放进 prompt。
memory 是运行时进入上下文的记忆层
这里说的 memory,很多时候指的是运行时注入的记忆信息:比如 relevant_memories、nested_memory,以及某些入口下额外补上的 memory 提示。它们不是固定写死在 prompt 里的,而是会随着当前任务、当前 turn、当前恢复流程动态进入上下文。
所以 memory 更像一层会被按需调出的记忆机制。它服务的不是“长期规则怎么保存”,而是“当前这一步,哪些过往信息值得重新让模型看见”。
CLAUDE.md 是持久存在的指令层
CLAUDE.md 则更像一套分层指令文件系统。src/utils/claudemd.ts 会按优先级加载 managed memory、user memory、project memory、local memory,以及 .claude/rules/*.md 这一类规则文件。它们更接近长期存在的环境指令,作用类似项目级或用户级的持续约束。
所以 CLAUDE.md 解决的重点不是“临时回忆什么”,而是“有哪些长期规则会一直影响这个会话”。
两者虽然不是同一套机制,但都会影响模型当前看到的上下文。前者偏运行时记忆层,后者偏持久指令层;Claude Code 的上下文工程,恰恰就在于把这两类信息分别管理,再在合适的时机一起送进模型。
skill surfacing 也是上下文工程的一部分
这里的 skill surfacing,可以理解为“让模型在当前任务里看见哪些 skill,以及看见多少”。
很多人会把 skill 理解成能力系统,把 context 理解成 prompt 系统,但在 Claude Code 里,这两者其实是连在一起的。
因为模型不是天然知道所有 skill。一个 skill 要想被有效使用,前提是它的说明先进入当前上下文,被模型看见。
至于哪些 skill 应该被放进来、是放完整描述还是放简化说明、要不要在 compact 之后重新注入,这些问题本质上都在消耗上下文预算。
所以 skill surfacing 不是单纯的能力执行问题,而是“如何把相关 skill 动态暴露给模型”的上下文工程问题。
compact:长会话里的上下文重塑
长会话是 Claude Code 里最能体现工程实力的地方。
因为上下文窗口不是无限的,系统必须在快超长时做重塑。

在 src/services/compact/compact.ts 里,可以看到 Claude Code 在 compact 时做的不只是“总结消息”。
// src/services/compact/compact.ts
exportasyncfunctioncompactConversation(
messages: Message[],
context: ToolUseContext,
cacheSafeParams: CacheSafeParams,
suppressFollowUpQuestions: boolean,
customInstructions?: string,
isAutoCompact: boolean = false,
recompactionInfo?: RecompactionInfo,
): Promise<CompactionResult> {
它接收完整的 ToolUseContext 和 CacheSafeParams,说明 compact 过程会感知缓存边界、当前能力面、以及 session effects。它还会去掉不必要的 media 块,过滤会被重新注入的 attachment,控制文件恢复与 skill 恢复预算,并保留必要的 session effects。
这不只是简单 summary,更像一次 上下文重塑。
来看 compact 的产出结构:
// src/services/compact/compact.ts
exportinterface CompactionResult {
boundaryMarker: SystemMessage // compact 边界标记
summaryMessages: UserMessage[] // 压缩后的摘要
attachments: AttachmentMessage[] // 重新注入的附件
hookResults: HookResultMessage[] // hook 结果保留
messagesToKeep?: Message[] // 必须保留的原始消息
preCompactTokenCount?: number
postCompactTokenCount?: number
}
post-compact 的消息重建顺序也很明确:
// src/services/compact/compact.ts
exportfunctionbuildPostCompactMessages(result: CompactionResult): Message[] {
return [
result.boundaryMarker, // 先放边界
...result.summaryMessages, // 再放摘要
...(result.messagesToKeep ?? []), // 保留的原始消息
...result.attachments, // 重新注入的附件
...result.hookResults, // hook 结果
]
}
可以看到,compact 不只是“缩短消息”,它还要重建边界、重新注入附件、保留 hook 状态——是一次完整的上下文重建。
为什么 compact 不只是附加功能
因为没有 compact,长任务跑不久就会撞上下文上限,skill / memory / tool 的暴露面也会越来越失真,历史消息还会不断淹没当前任务重点。有了 compact,系统才能把“长对话”转成“可继续执行的状态”。
context engineering 在解决什么问题
Claude Code 不是一味把更多东西塞给模型,它更关心怎么把重要信息结构化,把不重要的信息压缩,把必须长期存在的机制做成可重建状态,再把和当前任务最相关的能力持续表面化。这也是 context engineering 最见功力的地方。
源码锚点
建议重点看:
src/query.tssrc/utils/queryContext.tssrc/utils/attachments.tssrc/constants/prompts.tssrc/services/compact/compact.ts
结语
Claude Code 厉害的地方,不只是“把模型提示得更好”,还在于它把上下文管理做成了一套工程系统。
理解这一点之后,你再看 prompt、skill、tool,就会发现它们会共同竞争同一个稀缺资源:模型上下文窗口。

夜雨聆风