在大型语言模型(LLM)驱动的 AI Agent 系统中,上下文窗口(Context Window)是最稀缺的核心资源之一。Claude Code 作为一款生产级的 AI 编程助手,其上下文管理系统的设计直接决定了长会话的稳定性、响应质量以及用户体验。本文将深入解析 Claude Code 的上下文分层架构、CLAUDE.md 注入机制、动态上下文管理策略,以及其在 Token 预算约束下的智能压缩方案。
一、上下文分层架构
Claude Code 的上下文系统并非简单的消息堆叠,而是一个精心设计的分层架构。每一层承担着不同的职责,共同构成模型所能看到的完整世界。
flowchart TB
subgraph System["System Context"]
S1["Git Status
分支、提交、变更"]
S2["Cache Breaker
缓存失效标记"]
end
subgraph User["User Context"]
U1["CLAUDE.md
项目/用户指令"]
U2["Current Date
当前日期"]
end
subgraph Session["Session History"]
H1["User Messages
用户输入"]
H2["Assistant Messages
助手回复"]
H3["Tool Results
工具执行结果"]
H4["Attachments
附件内容"]
end
subgraph Dynamic["Dynamic Context"]
D1["Tool Definitions
工具 Schema"]
D2["MCP Tools
外部服务工具"]
D3["Skills / Agents
技能与代理定义"]
end
System --> |"Prepend"| API["API Request"]
User --> |"Prepend"| API
Session --> |"Append"| API
Dynamic --> |"Inject"| API
style System fill:#e1f5fe
style User fill:#f3e5f5
style Session fill:#e8f5e9
style Dynamic fill:#fff3e01.1 系统上下文(System Context)
系统上下文由 context.ts 中的 getSystemContext() 函数生成,采用 memoize 缓存机制,在会话期间只计算一次。其核心内容包括:
- Git 状态快照:当前分支、主分支、未暂存变更、最近 5 次提交记录、Git 用户名。这是会话开始时的静态快照,不会在对话过程中实时更新。
- 缓存破坏标记(Cache Breaker):内部调试用的 ephemeral 状态,用于强制使 prompt cache 失效。
// src/context.ts, L78-L118
export const getSystemContext = memoize(
async (): Promise<{ [k: string]: string }> => {
const gitStatus =
isEnvTruthy(process.env.CLAUDE_CODE_REMOTE) ||
!shouldIncludeGitInstructions()
? null
: await getGitStatus()
const injection = feature('BREAK_CACHE_COMMAND')
? getSystemPromptInjection()
: null
return {
...(gitStatus && { gitStatus }),
...(feature('BREAK_CACHE_COMMAND') && injection
? { cacheBreaker: `[CACHE_BREAKER: ${injection}]` }
: {}),
}
},
)Git 状态获取被限制在 MAX_STATUS_CHARS = 2000 字符以内,超出部分会被截断并提示用户通过 BashTool 手动运行 git status 获取完整信息。这种截断策略体现了上下文管理的首要原则:在信息完整性和 Token 经济之间取得平衡。
1.2 用户上下文(User Context)
用户上下文由 getUserContext() 生成,同样采用 memoize 缓存。其核心是 CLAUDE.md 文件系统,外加当前日期信息:
// src/context.ts, L124-L154
export const getUserContext = memoize(
async (): Promise<{ [k: string]: string }> => {
const shouldDisableClaudeMd =
isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_CLAUDE_MDS) ||
(isBareMode() && getAdditionalDirectoriesForClaudeMd().length === 0)
const claudeMd = shouldDisableClaudeMd
? null
: getClaudeMds(filterInjectedMemoryFiles(await getMemoryFiles()))
setCachedClaudeMdContent(claudeMd || null)
return {
...(claudeMd && { claudeMd }),
currentDate: `Today's date is ${getLocalISODate()}.`,
}
},
)注意到 setCachedClaudeMdContent 的调用——这是为自动模式分类器(yoloClassifier)提供的缓存,避免循环依赖。
1.3 会话上下文(Session History)
会话上下文即用户与模型的完整对话历史,包括:
- 用户的文本输入和附件
- 助手的文本回复和思考块
- 工具调用(tool_use)和工具结果(tool_result)
- 系统生成的 compact boundary 标记
这是上下文窗口中占比最大、增长最快的部分,也是压缩策略的主要作用对象。
1.4 动态上下文(Dynamic Context)
动态上下文指每次 API 调用时根据当前状态动态注入的内容:
- 工具定义(Tool Definitions):当前可用的所有工具的 JSON Schema
- MCP 工具:通过 Model Context Protocol 连接的外部服务工具
- Skills / Agents:用户自定义的 Slash Command 和 Agent 定义
- Deferred 工具:通过 ToolSearch 机制按需加载的工具,未使用时不在上下文中占用 Token
二、CLAUDE.md 机制
CLAUDE.md 是 Claude Code 最具特色的上下文注入机制之一。它允许用户通过 Markdown 文件向模型注入项目特定的指令、编码规范和领域知识。
2.1 四层加载优先级
根据 utils/claudemd.ts 的注释,CLAUDE.md 文件按以下顺序加载:
| 层级 | 来源 | 作用范围 | 优先级 |
|---|---|---|---|
| Managed Memory | /etc/claude-code/CLAUDE.md | 所有用户(全局) | 最低 |
| User Memory | ~/.claude/CLAUDE.md | 当前用户(全局) | 较低 |
| Project Memory | CLAUDE.md、.claude/CLAUDE.md、.claude/rules/*.md | 当前项目 | 较高 |
| Local Memory | CLAUDE.local.md | 当前项目(私有) | 最高 |
关键设计:文件按反向优先级顺序加载,即越靠近当前工作目录的文件越晚加载。这意味着后加载的文件会覆盖或优先于先加载的文件,模型会对其给予更高关注度。
2.2 自动发现与遍历
Project Memory 和 Local Memory 的发现机制采用从当前目录向上遍历至根目录的策略。在每一层目录中,系统会查找:
CLAUDE.md.claude/CLAUDE.md.claude/rules/目录下的所有.md文件
// src/utils/claudemd.ts, L1-L20(注释部分)
/**
* File discovery:
* - User memory is loaded from the user's home directory
* - Project and Local files are discovered by traversing from the
* current directory up to root
* - Files closer to the current directory have higher priority
* (loaded later)
* - CLAUDE.md, .claude/CLAUDE.md, and all .md files in
* .claude/rules/ are checked in each directory for Project memory
*/这种设计使得大型单体仓库(monorepo)可以在根目录定义通用规范,在子包目录定义特定的规则,实现分层、局部的指令覆盖。
2.3 内容解析与预处理
加载后的内存文件会经过一系列预处理:
Frontmatter 解析:提取 YAML frontmatter 中的 paths 字段,支持 glob 模式匹配文件路径。只有匹配当前操作路径的规则才会被注入。
HTML 注释剥离:使用 marked lexer 识别并移除块级 HTML 注释(<!-- ... -->),但保留代码块内的注释。这允许开发者在 CLAUDE.md 中编写作者备注,而不增加 Token 消耗。
// src/utils/claudemd.ts, L215-L245
export function stripHtmlComments(content: string): {
content: string
stripped: boolean
} {
if (!content.includes('<!--')) {
return { content, stripped: false }
}
return stripHtmlCommentsFromTokens(
new Lexer({ gfm: false }).lex(content)
)
}@include 指令:支持通过 @path 语法包含其他文件,解析相对路径、绝对路径和家目录路径(~/)。循环引用会被检测和阻止。
内容截断:自动内存入口文件(如 MEMORY.md)会被截断到合理长度,避免单个文件占用过多 Token。
2.4 对模型决策的影响
所有内存文件内容会被包裹在统一的提示词前缀中:
Codebase and user instructions are shown below.
Be sure to adhere to these instructions.
IMPORTANT: These instructions OVERRIDE any default behavior
and you MUST follow them exactly as written.这一措辞极其强硬——它明确告诉模型,CLAUDE.md 中的指令优先于系统默认行为。这是通过 prompt engineering 实现行为约束的经典案例。
三、动态上下文注入
Claude Code 的上下文注入并非静态的"一次性加载",而是根据会话状态、工具使用历史和 Token 压力进行动态调整。
3.1 context.ts 的职责
src/context.ts 是上下文系统的入口文件,承担两项核心职责:
- 提供
getSystemContext():获取 Git 状态、调试注入等系统级信息 - 提供
getUserContext():获取 CLAUDE.md 内容和日期信息
两者都使用 lodash/memoize 缓存,但提供了显式的缓存清理接口:
// src/context.ts, L23-L31
export function setSystemPromptInjection(value: string | null): void {
systemPromptInjection = value
// Clear context caches immediately when injection changes
getUserContext.cache.clear?.()
getSystemContext.cache.clear?.()
}当系统提示注入发生变化时,两个缓存会被立即清除,确保下一次 API 调用获取最新的上下文。
3.2 utils/context.ts 的辅助功能
src/utils/context.ts 是上下文系统的"基础设施层",负责模型上下文窗口的容量管理和 Token 计算:
上下文窗口大小计算:支持从 200K 到 1M Token 的动态窗口大小。通过模型名称检测(如 [1m] 后缀、claude-sonnet-4 等)、Beta Header、环境变量覆盖等多层机制确定实际可用的窗口大小。
// src/utils/context.ts, L40-L80
export function getContextWindowForModel(
model: string,
betas?: string[],
): number {
// Ant 内部用户可通过环境变量覆盖
if (
process.env.USER_TYPE === 'ant' &&
process.env.CLAUDE_CODE_MAX_CONTEXT_TOKENS
) {
const override = parseInt(
process.env.CLAUDE_CODE_MAX_CONTEXT_TOKENS, 10
)
if (!isNaN(override) && override > 0) {
return override
}
}
// [1m] 后缀 —— 客户端显式选择 1M 上下文
if (has1mContext(model)) {
return 1_000_000
}
// ... 更多检测逻辑
}上下文使用率计算:将 API 返回的 Token 使用数据(input_tokens、cache_creation_input_tokens、cache_read_input_tokens)汇总,计算已用百分比。
3.3 analyzeContext.ts 的分析逻辑
src/utils/analyzeContext.ts 是上下文系统中最复杂的分析模块。它将上下文窗口的占用情况细分为多个类别,并生成可视化的 Grid 数据:
// src/utils/analyzeContext.ts, L55-L75
interface ContextCategory {
name: string
tokens: number
color: keyof Theme
/** When true, these tokens are deferred and don't count
* toward context usage */
isDeferred?: boolean
}
export interface ContextData {
readonly categories: ContextCategory[]
readonly totalTokens: number
readonly maxTokens: number
readonly percentage: number
readonly gridRows: GridSquare[][]
readonly model: string
readonly memoryFiles: MemoryFile[]
readonly mcpTools: McpTool[]
readonly agents: Agent[]
readonly skills?: SkillInfo
readonly isAutoCompactEnabled: boolean
// ...
}分析逻辑包括:
系统提示词分节统计:将系统提示词按 Markdown 标题切分为多个 section,分别统计每个 section 的 Token 数。这帮助开发者和用户理解系统提示词的构成。
工具 Token 统计:区分 always-loaded 工具和 deferred 工具。Deferred 工具(通过 ToolSearch 机制)只有在被实际调用后才会计入上下文占用,未使用时仅作为"潜在容量"被追踪。
内存文件统计:逐个统计 CLAUDE.md 文件的 Token 占用,支持按路径、类型展示。
消息内容拆解:将历史消息拆解为 tool_call、tool_result、attachment、assistant_message、user_message 五大类,分别统计 Token 占用。
3.4 何时注入什么上下文
Claude Code 的上下文注入遵循按需加载、延迟加载的原则:
| 场景 | 注入内容 | 机制 |
|---|---|---|
| 每次 API 调用 | System Context + User Context | 固定前缀 |
| 每次 API 调用 | 当前可用工具定义 | 全部或 deferred |
| 检测到 MCP 服务 | MCP 工具 Schema | 动态添加 |
| 用户使用 / 技能 | Skill 详细内容 | 按需加载 |
| ToolSearch 触发 | 特定 deferred 工具 | 懒加载 |
| 会话超过阈值 | 历史消息 summary | autocompact |
四、上下文压缩策略
当会话历史不断增长,上下文窗口逼近极限时,Claude Code 采用多层压缩策略,从微观到宏观逐步释放 Token 空间。
flowchart TD
A["Token 使用增长"] --> B{"超过 microcompact 阈值?"}
B -->|"是"| C["Microcompact
清理旧工具结果"]
B -->|"否"| D["正常继续"]
C --> E{"超过 autocompact 阈值?"}
D --> E
E -->|"是"| F["Session Memory Compact
会话记忆压缩"]
E -->|"否"| G["正常继续"]
F --> H{"仍超过阈值?"}
H -->|"是"| I["Legacy Compact
历史消息摘要"]
H -->|"否"| G
I --> J["生成摘要替换历史"]
J --> K["更新 Compact Boundary"]
K --> G
G --> L["API 调用"]
style C fill:#fff3e0
style F fill:#e8f5e9
style I fill:#ffebee4.1 Token 预算管理
Claude Code 的 Token 预算管理非常精细。以默认 200K 上下文窗口为例:
// src/services/compact/autoCompact.ts, L20-L30
// Returns the context window size minus the max output tokens
export function getEffectiveContextWindowSize(model: string): number {
const reservedTokensForSummary = Math.min(
getMaxOutputTokensForModel(model),
MAX_OUTPUT_TOKENS_FOR_SUMMARY, // 20,000
)
let contextWindow = getContextWindowForModel(model, getSdkBetas())
// ...
return contextWindow - reservedTokensForSummary
}有效上下文窗口 = 总窗口 - 为摘要保留的输出 Token。这意味着 200K 窗口的实际可用上限约为 180K。
在此基础上,系统设置多层缓冲区:
// src/services/compact/autoCompact.ts, L33-L37
export const AUTOCOMPACT_BUFFER_TOKENS = 13_000
export const WARNING_THRESHOLD_BUFFER_TOKENS = 20_000
export const ERROR_THRESHOLD_BUFFER_TOKENS = 20_000
export const MANUAL_COMPACT_BUFFER_TOKENS = 3_000- AutoCompact 阈值:
effectiveWindow - 13_000。超过此阈值时,系统自动触发压缩。 - Warning 阈值:
autoCompactThreshold - 20_000。向用户显示警告。 - Blocking 阈值:
effectiveWindow - 3_000。达到此阈值时,阻塞新操作直到压缩完成。
4.2 上下文截断(Snipping)
Snipping 是一种比 compact 更轻量的历史消息清理机制。它移除会话中冗余的搜索/读取操作记录,但保留其功能影响。
src/utils/collapseReadSearch.ts 定义了哪些操作可以被"折叠"(collapse):
// src/utils/collapseReadSearch.ts, L40-L65
export function getToolSearchOrReadInfo(
toolName: string,
toolInput: unknown,
tools: Tools,
): SearchOrReadResult {
// REPL 工具被静默吸收
if (toolName === REPL_TOOL_NAME) {
return {
isCollapsible: true,
isSearch: false,
isRead: false,
isList: false,
isREPL: true,
isMemoryWrite: false,
isAbsorbedSilently: true,
}
}
// Memory 文件写入可被折叠
if (isMemoryWriteOrEdit(toolName, toolInput)) {
return { isCollapsible: true, /* ... */ }
}
// Snip 和 ToolSearch 元操作被静默吸收
if (
(feature('HISTORY_SNIP') && toolName === SNIP_TOOL_NAME) ||
(isFullscreenEnvEnabled() && toolName === TOOL_SEARCH_TOOL_NAME)
) {
return { isCollapsible: true, isAbsorbedSilently: true }
}
// ...
}连续的搜索/读取操作会被分组折叠,在 UI 上显示为"Read 5 files"或"Searched 3 times",而不是展示每一次调用的完整内容。
4.3 智能压缩:Microcompact 与 Autocompact
Claude Code 实现了两种不同粒度的自动压缩机制:
Microcompact(微观压缩):
src/services/compact/microCompact.ts 实现了对工具结果的精细化清理。它识别可压缩的工具(Read、Bash、Grep、Glob、WebSearch 等),将其过时的结果内容替换为占位符或完全移除。
// src/services/compact/microCompact.ts, L40-L55
const COMPACTABLE_TOOLS = new Set<string>([
FILE_READ_TOOL_NAME,
...SHELL_TOOL_NAMES,
GREP_TOOL_NAME,
GLOB_TOOL_NAME,
WEB_SEARCH_TOOL_NAME,
WEB_FETCH_TOOL_NAME,
FILE_EDIT_TOOL_NAME,
FILE_WRITE_TOOL_NAME,
])Microcompact 有两种路径:
- 时间触发:如果距离上次助手回复的时间超过阈值,服务器缓存已过期,直接清理旧工具结果
- Cache Editing(Ant 内部):通过 API 的 cache_edits 机制精确删除特定工具结果,而不破坏缓存前缀
Autocompact(自动压缩):
当 Token 使用超过 AUTOCOMPACT_BUFFER_TOKENS 阈值时,系统启动自动压缩流程:
// src/services/compact/autoCompact.ts, L115-L145
export async function autoCompactIfNeeded(
messages: Message[],
toolUseContext: ToolUseContext,
// ...
): Promise<{ wasCompacted: boolean; compactionResult?: CompactionResult }> {
// 熔断机制:连续失败 3 次后停止尝试
if (
tracking?.consecutiveFailures !== undefined &&
tracking.consecutiveFailures >= MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES
) {
return { wasCompacted: false }
}
const shouldCompact = await shouldAutoCompact(messages, model, querySource)
if (!shouldCompact) {
return { wasCompacted: false }
}
// 先尝试 Session Memory 压缩
const sessionMemoryResult = await trySessionMemoryCompaction(
messages, toolUseContext.agentId, threshold
)
if (sessionMemoryResult) {
// ...
return { wasCompacted: true, compactionResult: sessionMemoryResult }
}
// 回退到 Legacy Compact
const compactionResult = await compactConversation(
messages, toolUseContext, cacheSafeParams, true, undefined, true
)
// ...
}Autocompact 的执行顺序是:
- Session Memory Compaction:将旧消息移至 session memory,由 REPL 工具管理
- Legacy Compact:生成对话摘要,用 summary 替换被压缩的消息历史
- 更新 Compact Boundary:在消息流中插入系统 boundary 标记,区分"已压缩"和"未压缩"区域
4.4 重要性排序与 Deferred Loading
除了压缩历史,Claude Code 还通过**延迟加载(Deferred Loading)**减少静态上下文的 Token 占用。
ToolSearch 机制:当内置工具或 MCP 工具数量较多时,系统会将不常用的工具标记为 deferred。这些工具的 Schema 不会出现在每次 API 调用的上下文中。当模型需要调用某个 deferred 工具时,先通过 ToolSearchTool 搜索并加载该工具的 Schema。
// src/utils/analyzeContext.ts, L350-L390
const alwaysLoadedTools = builtInTools.filter(t => !isDeferredTool(t))
const deferredBuiltinTools = builtInTools.filter(t => isDeferredTool(t))
// 只统计已加载的 deferred 工具
const loadedDeferredTokens = /* ... */
return {
builtInToolTokens: alwaysLoadedTokens + loadedDeferredTokens,
deferredBuiltinTokens: totalDeferredTokens - loadedDeferredTokens,
}这种设计的精妙之处在于:工具定义的 Token 占用从"固定成本"变成了"可变成本"。对于拥有大量 MCP 工具的项目,Deferred Loading 可以节省数万 Token 的上下文空间。
五、上下文失效与更新
上下文管理不仅关乎"注入",还关乎"何时刷新"。Claude Code 在多个层面实现了上下文失效检测和更新机制。
5.1 文件变更检测
CLAUDE.md 文件的变更是通过文件状态缓存(fileStateCache)跟踪的。当文件被修改后,其缓存条目失效,下一次 getUserContext() 调用(在缓存被清除后)会重新读取磁盘上的最新内容。
// src/context.ts, L23-L31
export function setSystemPromptInjection(value: string | null): void {
systemPromptInjection = value
getUserContext.cache.clear?.()
getSystemContext.cache.clear?.()
}系统提示注入的变化会强制清除所有上下文缓存,这是一种全量刷新策略。
5.2 Git 状态变化
Git 状态在 getSystemContext() 中被描述为:
"This is the git status at the start of the conversation. Note that this status is a snapshot in time, and will not update during the conversation."
这是一个刻意的设计选择——Git 状态是会话级的静态快照,而非实时流。避免在每次 API 调用时重复执行 git status,既节省 Token(状态文本可能很长),也减少外部命令开销。
如果用户需要最新的 Git 状态,必须通过 BashTool 显式运行 git status。
5.3 上下文刷新机制
Claude Code 的上下文刷新遵循懒加载 + 显式清除的策略:
| 刷新类型 | 触发条件 | 影响范围 |
|---|---|---|
| 系统注入变化 | setSystemPromptInjection() | System + User Context |
| 模型切换 | 用户更改主循环模型 | Context Window 大小、Tool 定义 |
| 会话重置 | /clear 命令 | 全部 Session History |
| 压缩完成 | Compact / Microcompact | 消息历史被替换或清理 |
| 工具加载 | ToolSearch 调用 | Deferred 工具 Schema 注入 |
| MCP 变化 | 服务连接/断开 | MCP Tool 定义增删 |
六、总结
Claude Code 的上下文管理系统是一个分层、动态、自适应的工程杰作。它通过以下策略在有限的 Token 窗口内最大化有效信息密度:
- 分层隔离:System、User、Session、Dynamic 四层各司其职,避免信息混杂
- 智能注入:CLAUDE.md 的层级覆盖和路径匹配,确保模型只看到相关的指令
- 延迟加载:ToolSearch 和 Deferred Tools 将固定成本转化为可变成本
- 多级压缩:Microcompact → Session Memory → Legacy Compact 的渐进式释放策略
- 熔断保护:连续自动压缩失败 3 次后停止尝试,避免无意义的 API 浪费
- 缓存意识:所有设计都充分考虑 Anthropic API 的 prompt caching 特性,在压缩时尽量保持缓存前缀不被破坏
对于构建自己的 AI Agent 系统的开发者而言,Claude Code 的上下文管理提供了宝贵的参考范式:上下文不是越多越好,而是在正确的时间将正确的信息呈现给模型。Token 预算管理应当贯穿架构设计的每一层,从工具定义到消息历史,从静态提示到动态注入,每个字节都应该有其存在的理由。