上下文管理与注入

📑 目录

在大型语言模型(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:#fff3e0

1.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 MemoryCLAUDE.md.claude/CLAUDE.md.claude/rules/*.md当前项目较高
Local MemoryCLAUDE.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 是上下文系统的入口文件,承担两项核心职责:

  1. 提供 getSystemContext():获取 Git 状态、调试注入等系统级信息
  2. 提供 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 工具懒加载
会话超过阈值历史消息 summaryautocompact

四、上下文压缩策略

当会话历史不断增长,上下文窗口逼近极限时,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:#ffebee

4.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 有两种路径:

  1. 时间触发:如果距离上次助手回复的时间超过阈值,服务器缓存已过期,直接清理旧工具结果
  2. 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 的执行顺序是:

  1. Session Memory Compaction:将旧消息移至 session memory,由 REPL 工具管理
  2. Legacy Compact:生成对话摘要,用 summary 替换被压缩的消息历史
  3. 更新 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 窗口内最大化有效信息密度:

  1. 分层隔离:System、User、Session、Dynamic 四层各司其职,避免信息混杂
  2. 智能注入CLAUDE.md 的层级覆盖和路径匹配,确保模型只看到相关的指令
  3. 延迟加载:ToolSearch 和 Deferred Tools 将固定成本转化为可变成本
  4. 多级压缩:Microcompact → Session Memory → Legacy Compact 的渐进式释放策略
  5. 熔断保护:连续自动压缩失败 3 次后停止尝试,避免无意义的 API 浪费
  6. 缓存意识:所有设计都充分考虑 Anthropic API 的 prompt caching 特性,在压缩时尽量保持缓存前缀不被破坏

对于构建自己的 AI Agent 系统的开发者而言,Claude Code 的上下文管理提供了宝贵的参考范式:上下文不是越多越好,而是在正确的时间将正确的信息呈现给模型。Token 预算管理应当贯穿架构设计的每一层,从工具定义到消息历史,从静态提示到动态注入,每个字节都应该有其存在的理由。