主题
Claude Code 上下文构建系统深度解析
一、概述
上下文构建是 Claude Code 最核心、最精密的子系统之一。在每次 API 调用前,Claude Code 需要将系统提示词、记忆文件、Git 状态、环境信息、工具定义、逐轮附件等多层上下文组装成一个完整的请求。每一层都有自己的优先级、缓存策略和注入路径。
上下文构建的核心理念可以概括为一句话:
上下文是一个状态化的记忆加载器,而非路由中枢。
它的职责不是"决定做什么",而是"在 LLM 被调用之前,确保 LLM 看到了正确的、完整的、经过优先级排序的上下文信息"。
核心源文件
| 文件 | 行数 | 职责 |
|---|---|---|
context.ts | ~190 | 记忆化的 Git 状态 + 用户上下文入口 |
claudemd.ts | ~1,480 | CLAUDE.md 文件发现、解析、@include 解析、glob 条件规则 |
prompts.ts | ~915 | 系统提示词组装——静态段、动态注册表、边界标记 |
systemPrompt.ts | ~124 | 系统提示词优先级链: override > coordinator > agent > custom > default |
attachments.ts | ~3,998 | 逐轮附件计算——30+ 类型、提醒调度 |
queryContext.ts | ~180 | 共享辅助函数,组装缓存键前缀 |
analyzeContext.ts | ~1,383 | /context 命令——token 统计、分类拆解、网格可视化 |
query.ts | ~大量 | 查询引擎,组合系统上下文 + 基础提示词 → API 调用 |
二、三层上下文架构
Claude Code 通过三个截然不同的层次组装上下文,每层的生命周期和缓存策略完全不同:
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: System Prompt(身份层) │
│ │
│ 生命周期: 全局静态 + 会话动态 │
│ 缓存策略: 静态段 scope='global'(跨用户共享) │
│ 动态段 scope='session'(会话级记忆化) │
│ │
│ 包含: 身份定义、系统规则、任务准则、工具使用指南、 │
│ 语气风格、输出效率、环境信息、语言偏好、MCP 指令 │
├─────────────────────────────────────────────────────────────┤
│ Layer 2: User/System Context(知识层) │
│ │
│ 生命周期: 会话级(lodash/memoize,计算一次) │
│ 缓存策略: 首次计算后缓存,整个会话期间不再重新计算 │
│ │
│ 包含: CLAUDE.md 层级文件、Git 状态快照、当前日期 │
├─────────────────────────────────────────────────────────────┤
│ Layer 3: Attachments(感知层) │
│ │
│ 生命周期: 逐轮(Per-turn) │
│ 缓存策略: 每轮重新计算,1 秒超时 │
│ │
│ 包含: 文件内容、IDE 选区、嵌套记忆、任务提醒、Hook 结果、 │
│ Skill 发现、Swarm 通信、预算追踪、工具变更增量 │
└─────────────────────────────────────────────────────────────┘组装时序
当 QueryEngine.ask() 触发时,上下文按以下顺序组装:
1. fetchSystemPromptParts()
├── getSystemPrompt() ← 并行
├── getUserContext() ← 并行(记忆化)
└── getSystemContext() ← 并行(记忆化)
2. buildEffectiveSystemPrompt()
└── 应用优先级链: override > coordinator > agent > custom > default
3. getAttachments()
└── 并行计算 30+ 附件类型(1 秒超时)
4. normalizeMessagesForAPI()
└── 将 messages + attachments 转换为 Anthropic API 格式
5. microcompactMessages()(可选)
└── 压缩旧工具结果(FRC / Fine-grained Result Compaction)
6. API 调用
└── system[]: prompt parts
messages[]: normalized messages三、Layer 1:系统提示词(The Identity)
3.1 提示词组装流程
getSystemPrompt() 在 prompts.ts(第 444 行)中构建一个有序段落数组——不是单一字符串,而是一组在 API 层拼接的段落列表。
静态段(全局可缓存)
这些段在所有用户和会话间完全相同,使用 scope: 'global' 实现跨组织提示词缓存:
| 序号 | 段名 | 函数 | 内容 |
|---|---|---|---|
| 1 | Identity | getSimpleIntroSection() | "You are an interactive agent..." —— 身份定义 |
| 2 | System Rules | getSimpleSystemSection() | 工具权限、系统提醒、Hooks 规则 |
| 3 | Doing Tasks | getSimpleDoingTasksSection() | 代码风格规则、安全警告、KISS 原则 |
| 4 | Actions | getActionsSection() | 可逆性分析、影响范围评估 |
| 5 | Using Tools | getUsingYourToolsSection() | "Use FileRead instead of cat"、并行工具调用 |
| 6 | Tone & Style | getSimpleToneAndStyleSection() | 不使用 emoji、file:line 引用格式 |
| 7 | Output Efficiency | getOutputEfficiencySection() | 工具调用间 ≤25 词(仅 Anthropic 内部) |
动态边界标记(DYNAMIC_BOUNDARY)
typescript
export const SYSTEM_PROMPT_DYNAMIC_BOUNDARY =
'__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'这个标记将系统提示词一分为二:
- 标记之前的所有段可以使用
scope: 'global',实现跨组织共享的 Prompt Cache - 标记之后的段是会话特定的,每个用户/会话都可能不同
将任何段从一侧移到另一侧都会改变缓存行为——代码中有明确的警告注释。
动态段(会话级)
标记之后的段通过注册表系统动态解析:
| 序号 | 段名 | 内容 |
|---|---|---|
| 1 | Session Guidance | Fork Agent 指令、Skill 发现、验证 Agent 契约 |
| 2 | Memory | loadMemoryPrompt(): CLAUDE.md 文件内容 |
| 3 | Environment | 模型名称、CWD、平台、Shell、Git 状态、知识截止日期 |
| 4 | Language | "Always respond in {language}" |
| 5 | MCP Instructions | MCP 服务器提供的指令(或增量附件) |
| 6 | Scratchpad | 会话级临时目录路径 |
| 7 | Token Budget | "+500k" 预算指令(当启用时) |
可视化:完整系统提示词结构
┌────────────────────────────────────────────────────────┐
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ scope: 'global' (跨组织缓存) │ │
│ │ │ │
│ │ ① Identity "You are an interactive agent..." │ │
│ │ ② System Rules 工具权限、系统提醒、Hooks │ │
│ │ ③ Doing Tasks 代码风格、安全、KISS │ │
│ │ ④ Actions 可逆性分析、影响范围 │ │
│ │ ⑤ Using Tools FileRead > cat, 并行调用 │ │
│ │ ⑥ Tone & Style 无 emoji, file:line 引用 │ │
│ │ ⑦ Output Eff. 工具间 ≤25 词 │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ═══════ DYNAMIC_BOUNDARY ═══════════════════════════ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ scope: 'session' (会话级) │ │
│ │ │ │
│ │ ⑧ Session Guidance Fork/Skill/验证指令 │ │
│ │ ⑨ Memory CLAUDE.md 层级内容 │ │
│ │ ⑩ Environment 模型/CWD/平台/Shell/Git │ │
│ │ ⑪ Language "Always respond in 中文" │ │
│ │ ⑫ MCP Instructions MCP 服务器指令 │ │
│ │ ⑬ Scratchpad 临时目录路径 │ │
│ │ ⑭ Token Budget +500k 预算 │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────┘3.2 系统提示词优先级链
typescript
// systemPrompt.ts: buildEffectiveSystemPrompt()
if (overrideSystemPrompt) → [override] // Loop 模式完全替换
else if (coordinatorMode) → [coordinator prompt] // 多 Agent 协调器
else if (agentDefinition) → [agent prompt] // 自定义 Agent 替换默认
else if (customSystemPrompt) → [custom] // --system-prompt flag
else → [default sections] // 正常操作
// appendSystemPrompt 始终追加到末尾(除了 override 模式)这是一个替换链,不是合并——只有一个"基础"提示词胜出。
3.3 动态段注册表系统
动态系统提示词段通过注册表管理,支持静态和计算型内容:
typescript
type SectionRegistry = Map<string, {
label: string
getSectionContent: () => string | null // 每次调用时计算
isDynamic: boolean // 在 DYNAMIC_BOUNDARY 之后?
scope: 'global' | 'session' // 缓存范围
}>
// 缓存与失效
export function getSection(key: string): string | null {
const cached = STATE.systemPromptSectionCache.get(key)
if (cached !== undefined) return cached
const content = registry.get(key)?.getSectionContent() ?? null
STATE.systemPromptSectionCache.set(key, content)
return content
}
// 缓存在以下时机清除:
// - /memory 对话框变更
// - Settings 同步
// - Worktree 进入/退出
// - 显式调用 resetSystemPromptSectionCache()四、Layer 2:用户与系统上下文(The Knowledge)
4.1 两个独立的上下文函数
context.ts(约 190 行)导出两个核心的记忆化函数:
typescript
// 系统上下文:Git 状态 + 环境信息
export const getSystemContext = memoize(async () => {
// 在 CCR(Claude Code Remote)或禁用 Git 指令时跳过
const gitStatus = isEnvTruthy(process.env.CLAUDE_CODE_REMOTE) ||
!shouldIncludeGitInstructions()
? null
: await getGitStatus()
// 缓存破坏注入(仅 Anthropic 内部调试用)
const injection = feature('BREAK_CACHE_COMMAND')
? getSystemPromptInjection()
: null
return {
...(gitStatus && { gitStatus }),
...(injection && { cacheBreaker: `[CACHE_BREAKER: ${injection}]` }),
}
})
// 用户上下文:CLAUDE.md + 当前日期
export const getUserContext = memoize(async () => {
const shouldDisableClaudeMd =
isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_CLAUDE_MDS) ||
(isBareMode() && getAdditionalDirectoriesForClaudeMd().length === 0)
const claudeMd = shouldDisableClaudeMd
? null
: getClaudeMds(filterInjectedMemoryFiles(await getMemoryFiles()))
// 缓存给 auto-mode 分类器使用(避免循环依赖)
setCachedClaudeMdContent(claudeMd || null)
return {
...(claudeMd && { claudeMd }),
currentDate: `Today's date is ${getLocalISODate()}.`,
}
})4.2 关键设计决策:System Prompt vs User Message
┌─────────────────────────────────────────────────┐
│ API 请求结构 │
│ │
│ system: [ │
│ { text: "静态提示词段...", cache: 'global' }, │
│ { text: "动态提示词段 + systemContext..." } │
│ ] │
│ │
│ messages: [ │
│ { role: 'user', content: userContext }, ←── CLAUDE.md 在这里!
│ { role: 'user', content: 用户输入 }, │
│ ...对话历史... │
│ ] │
└─────────────────────────────────────────────────┘这是一个极其重要的架构决策:
- systemContext(Git 状态等)通过
appendSystemContext()追加到系统提示词中 - userContext(CLAUDE.md + 日期)通过
prependUserContext()前置到消息数组中
也就是说,CLAUDE.md 的内容作为 user 消息传递,而不是系统提示词。这个选择有深远影响:
| 位置 | 模型遵从性 | 原因 |
|---|---|---|
| System Prompt | 高(接近确定性) | 模型将其视为核心指令 |
| User Message | 中(概率性) | 模型将其视为对话上下文 |
这创造了一个刻意的分离:
- 引导层(CLAUDE.md,概率性遵从)—— 提供指导建议
- 执行层(权限规则,确定性执行)—— deny-first 的硬性约束
4.3 systemContext 的注入路径
typescript
// query.ts 中的组装逻辑
const effectiveSystemPrompt = asSystemPrompt(
appendSystemContext(systemPrompt, systemContext)
)()
// systemContext 被追加到系统提示词末尾
// 内容示例:
// "Git status: On branch main, 3 files changed, 12 insertions..."4.4 userContext 的注入路径
typescript
// query.ts 中的组装逻辑
const messagesForQuery = prependUserContext(messages, userContext)
// userContext 作为第一条 user 消息插入
// 内容示例:
// "Codebase and user instructions are shown below...
// Contents of ~/.claude/CLAUDE.md: ...
// Contents of /project/CLAUDE.md: ...
// Today's date is 2026-04-25."五、CLAUDE.md 记忆系统(claudemd.ts)
5.1 概述
CLAUDE.md 是 Claude Code 最精密的上下文子系统(claudemd.ts,约 1,480 行)。它从多个来源发现、解析、合并指令文件,严格按优先级排序。
核心设计原则:
存储的上下文必须对用户可检查、可编辑。CLAUDE.md 文件是纯文本 Markdown,而非结构化配置或不透明的数据库条目。
这意味着:
- 用户可以阅读、编辑、版本控制和删除 Agent 看到的任何指令
- 不使用 Embedding 或向量相似性索引
- 用 LLM 扫描记忆文件标题来选择最多 5 个相关文件
5.2 六级记忆层级
优先级(低 → 高,后加载者优先级更高):
┌────────────────────────────────────────────────────────────────┐
│ Level 1: Managed(组织策略) │
│ 路径: /etc/claude-code/CLAUDE.md (Linux) │
│ 用途: 企业/组织级策略,由管理员部署 │
│ 示例: "所有代码必须符合 SOC2 合规要求" │
├────────────────────────────────────────────────────────────────┤
│ Level 2: User(用户全局) │
│ 路径: ~/.claude/CLAUDE.md │
│ 用途: 个人偏好,跨项目通用 │
│ 示例: "Always respond in 中文", "使用 Vim 风格的键位" │
├────────────────────────────────────────────────────────────────┤
│ Level 3: Project(项目级) │
│ 路径: CLAUDE.md, .claude/CLAUDE.md, .claude/rules/*.md │
│ 用途: 项目规范,提交到 Git 仓库,团队共享 │
│ 示例: "使用 TypeScript strict 模式", "测试覆盖率 > 80%" │
├────────────────────────────────────────────────────────────────┤
│ Level 4: Local(本地私有) │
│ 路径: CLAUDE.local.md │
│ 用途: 个人的项目特定规则(添加到 .gitignore) │
│ 示例: "使用 localhost:3001 作为开发 API 端点" │
├────────────────────────────────────────────────────────────────┤
│ Level 5: AutoMem(自动记忆) │
│ 路径: ~/.claude/memory/MEMORY.md │
│ 用途: Agent 在对话中自动写入的记忆 │
│ 示例: "用户偏好简洁回复", "该项目使用 pnpm 而非 npm" │
├────────────────────────────────────────────────────────────────┤
│ Level 6: TeamMem(团队记忆) │
│ 路径: 组织同步的共享记忆 │
│ 用途: 团队级知识共享(Feature Flag 控制) │
│ 示例: "公司 API 网关地址: api.internal.corp" │
└────────────────────────────────────────────────────────────────┘关键规则:后加载的文件具有更高优先级——模型会更关注它们。
5.3 目录遍历算法(Directory Walk)
对于 Project 和 Local 文件,系统执行从 CWD 到文件系统根目录的向上遍历:
typescript
// 从 CWD 向上遍历到根目录
let currentDir = originalCwd
while (currentDir !== parse(currentDir).root) {
dirs.push(currentDir)
currentDir = dirname(currentDir)
}
// 反转:从根目录向下处理到 CWD
// 这样 CWD 附近的文件后加载,优先级更高
for (const dir of dirs.reverse()) {
// 在每个目录中检查:
// 1. CLAUDE.md — 项目指令
// 2. .claude/CLAUDE.md — 备选项目指令
// 3. .claude/rules/*.md — 规则文件(无条件 + 条件)
// 4. CLAUDE.local.md — 本地私有指令
}可视化示例
假设 CWD = /home/user/project/src/api:
加载顺序(先加载 = 低优先级):
/ (root)
└── CLAUDE.md ← 最先加载,最低优先级
/home
└── CLAUDE.md
/home/user
└── ~/.claude/CLAUDE.md ← User 级
/home/user/project
├── CLAUDE.md ← 项目根
├── .claude/CLAUDE.md
├── .claude/rules/coding.md
├── .claude/rules/api-rules.md ← 条件规则 (paths: src/api/**)
└── CLAUDE.local.md
/home/user/project/src
└── CLAUDE.md
/home/user/project/src/api
└── CLAUDE.md ← 最后加载,最高优先级 ✓5.4 条件规则(Glob-Gated Rules)
.claude/rules/ 目录下的规则文件可以通过 YAML frontmatter 限制其生效范围:
yaml
---
paths:
- src/api/**
- tests/api/**
---
# API 开发规则
- 所有 API 端点必须有请求验证
- 使用 Zod schema 验证输入参数
- 错误响应必须包含错误代码和消息工作机制:
- 系统使用
picomatch进行 glob 匹配 - 使用
ignore进行路径过滤 - 无条件规则在启动时立即加载
- 条件规则作为嵌套记忆附件,仅当工具操作匹配路径时注入
延迟加载:对于 CWD 以下的嵌套目录,即使是无条件规则也只在 Agent 读取该目录中的文件时才加载。这意味着模型的指令集可以在对话过程中随着探索代码库而动态演化。
5.5 @include 指令
CLAUDE.md 文件支持递归包含:
markdown
# 项目规范
@./coding-standards.md
@./api-conventions.md
@~/global-rules.md
@/etc/company-policy.md解析流程:
原始文件
│
▼
① 使用 marked 进行词法分析(禁用 GFM,防止 ~/path 变成删除线)
│
▼
② 遍历文本 token,提取 @path 模式
│
▼
③ 解析为绝对路径(处理符号链接)
│
▼
④ 递归处理,最大深度 MAX_INCLUDE_DEPTH = 5
│
▼
⑤ 跟踪 processedPaths 集合,防止循环引用
│
▼
⑥ 不存在的文件被静默忽略语法变体:
| 语法 | 说明 |
|---|---|
@path | 相对于当前文件目录 |
@./relative | 显式相对路径 |
@~/home | 用户主目录路径 |
@/absolute | 绝对路径 |
约束:@include 仅在叶文本节点中生效,不会在代码块内被解析。
5.6 内容处理管道
每个记忆文件经过以下处理管道:
原始文件内容
│
▼
① parseFrontmatter()
提取 paths 等元数据,剥离 YAML 块
│
▼
② stripHtmlComments()
移除 <!-- 块注释 -->(保留行内注释)
│
▼
③ truncateEntrypointContent()
截断 AutoMem/TeamMem 文件(防止过大)
│
▼
④ 跟踪 contentDiffersFromDisk
标记内容是否经过转换
│
▼
⑤ 二进制保护
白名单检查 100+ 文本扩展名 (TEXT_FILE_EXTENSIONS)
防止将图片、PDF 等加载到上下文5.7 渲染为上下文
typescript
export const getClaudeMds = (memoryFiles: MemoryFileInfo[]): string => {
const memories: string[] = []
for (const file of memoryFiles) {
const description = /* 类型特定后缀 */
memories.push(`Contents of ${file.path}${description}:\n\n${content}`)
}
return `${MEMORY_INSTRUCTION_PROMPT}\n\n${memories.join('\n\n')}`
}注入提示词:
"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 内容的遵从度,弥补其作为 User Message(而非 System Prompt)的注意力差异。
六、Git 状态构建(getGitStatus)
6.1 实现逻辑
getGitStatus() 也使用 lodash/memoize,在会话期间只计算一次:
typescript
export const getGitStatus = memoize(async (): Promise<string | null> => {
// 测试环境跳过
if (process.env.NODE_ENV === 'test') return null
// 检查是否在 Git 仓库中
const isGit = await getIsGit()
if (!isGit) return null
// 并行获取 5 项 Git 信息
const [branch, mainBranch, status, log, userName] = await Promise.all([
getBranch(), // 当前分支名
getDefaultBranch(), // 默认分支(main/master)
execFileNoThrow(gitExe(), [ // git status --short
'--no-optional-locks',
'status', '--short'
]),
execFileNoThrow(gitExe(), [ // git log(最近提交)
'--no-optional-locks',
'log', '--oneline', '-5'
]),
getUserName(), // git config user.name
])
return formatGitStatus({ branch, mainBranch, status, log, userName })
})6.2 并行数据采集
5 项 Git 信息通过 Promise.all() 并行获取,最大化效率:
┌─── getBranch() ─── "feature/auth"
│
├─── getDefaultBranch() ─── "main"
│
Promise.all ──├─── git status --short ─── "M src/auth.ts\n A tests/auth.test.ts"
│
├─── git log -5 ─── "abc1234 feat: add login\ndef5678 fix: typo"
│
└─── getUserName() ─── "developer"6.3 Git 状态与缓存问题
关键问题:Git 状态是一个会话启动时的快照。提示词中会明确说明:
"this status will not update during the conversation"
缓存影响:Git 状态被放在系统提示词中,每次 Git 操作(commit、checkout 等)都会导致不同会话间的缓存失效:
会话级缓存结构:
system: [
{ text: "工具定义 | Claude 版本", cache: 'ephemeral' }, ← 高命中率
{ text: "系统提示词 | ~/.claude/CLAUDE.md | git status",
cache: 'ephemeral' }, ← git status 变化导致未命中
]
messages: [
{ role: 'user', content: "Skills | ./CLAUDE.md | 用户输入" }, ← 受上层未命中连带影响
]社区发现的问题:
git status在每次会话间变化,导致系统提示词缓存块每次未命中- 社区建议使用
CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1作为变通方案 - 底层问题:git status 可能触发 Git 自身的 housekeeping(gc),导致首次查询延迟数分钟
6.4 跳过 Git 状态的条件
typescript
const gitStatus =
isEnvTruthy(process.env.CLAUDE_CODE_REMOTE) || // 远程模式
!shouldIncludeGitInstructions() // 设置禁用 Git 指令
? null
: await getGitStatus()| 条件 | 效果 |
|---|---|
CLAUDE_CODE_REMOTE=1 | 远程模式下跳过(不必要的开销) |
CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1 | 环境变量显式禁用 |
includeGitInstructions=false | settings.json 配置禁用 |
| 非 Git 仓库 | 自动检测并跳过 |
七、Layer 3:逐轮附件(Per-Turn Attachments)
7.1 概述
getAttachments() 在 attachments.ts(约 3,998 行)中组装每轮变化的上下文。它使用 1 秒超时的 AbortController 防止阻塞用户输入。
typescript
const abortController = createAbortController()
const timeoutId = setTimeout(ac => ac.abort(), 1000, abortController)如果附件计算超过 1 秒,直接中止。每个附件来源都被 maybe() 辅助函数包裹,捕获错误并静默记录日志。
7.2 30+ 附件类型
Attachment 联合类型跨越 700+ 行类型定义:
| 类别 | 附件类型 | 触发条件 |
|---|---|---|
| 文件内容 | file, compact_file_reference, pdf_reference, already_read_file | 用户 @mentions 文件 |
| IDE 集成 | selected_lines_in_ide, opened_file_in_ide | IDE 发送选区/焦点 |
| 记忆 | nested_memory, relevant_memories, current_session_memory | 工具操作 CWD 之外的文件 |
| 任务管理 | todo_reminder, task_reminder, plan_mode, verify_plan_reminder | 定期(每 N 轮) |
| Hook 系统 | hook_cancelled, hook_success, hook_non_blocking_error 等 | Hook 执行结果 |
| Skill 系统 | skill_listing, skill_discovery, invoked_skills, dynamic_skill | Skill 匹配 + 调用 |
| Swarm | teammate_mailbox, team_context, teammate_shutdown_batch | 多 Agent 协调 |
| 预算 | token_usage, budget_usd, output_token_usage | Token/费用追踪 |
| 工具增量 | deferred_tools_delta, agent_listing_delta, mcp_instructions_delta | 工具集中途变更 |
7.3 基于轮次的提醒调度
多个附件类型使用轮次计数器进行调度:
typescript
// TODO 提醒配置
export const TODO_REMINDER_CONFIG = {
TURNS_SINCE_WRITE: 10, // 最后一次写入后 10 轮触发提醒
TURNS_BETWEEN_REMINDERS: 10, // 提醒间隔不少于 10 轮
}
// 计划模式附件配置
export const PLAN_MODE_ATTACHMENT_CONFIG = {
TURNS_BETWEEN_ATTACHMENTS: 5, // 每 5 轮注入一次
FULL_REMINDER_EVERY_N_ATTACHMENTS: 5, // 每 5 次为完整提醒
}工作示意:
Turn 1: 📋 TODO 写入
Turn 2-10: (无提醒)
Turn 11: 🔔 "你还有未完成的 TODO"
Turn 12-20: (无提醒)
Turn 21: 🔔 "你还有未完成的 TODO"
...7.4 自动记忆浮现(Relevant Memories)
当 AutoMem 启用时,findRelevantMemories() 基于当前上下文浮现存储的记忆:
typescript
export const RELEVANT_MEMORIES_CONFIG = {
MAX_SESSION_BYTES: 60 * 1024, // 每会话累计上限 60KB
}
const MAX_MEMORY_LINES = 200 // 每文件行数上限
const MAX_MEMORY_BYTES = 4096 // 每文件字节上限 (5 × 4KB = 20KB/轮)缓存优化:记忆浮现器在附件创建时预计算标题,避免因时间戳变化("3 天前保存" → "4 天前保存")而破坏 Prompt Cache。
7.5 工具增量(Deferred Tool Loading)
插件和 MCP 工具可以在会话中途到达。附件系统通过增量描述处理:
typescript
export type Attachment =
| { type: 'deferred_tools_delta'; tools: { added: ToolInfo[]; removed: ToolInfo[] } }
| { type: 'agent_listing_delta'; agents: AgentDelta[] }
| { type: 'mcp_instructions_delta'; server: string; instructions: string }增量描述变化了什么而非重新列出所有工具,保持注入紧凑,让模型理解"你现在有了一个新工具"而非重新处理整个工具池。
7.6 Skill 调用保存
会话中调用的 Skill 被保存在 STATE.invokedSkills 中,键为 ${agentId ?? ''}:${skillName}:
typescript
// Skill 在 compaction 后存活的机制:
// 1. attachments.ts 检查 STATE.invokedSkills
// 2. 重新注入 Skill 内容作为 'invoked_skills' 附件类型
// 3. 复合键防止跨 Agent 的 Skill 覆盖八、上下文在 API 请求中的最终形态
8.1 完整请求结构
json
{
"model": "claude-opus-4-6",
"system": [
{
"type": "text",
"text": "x-anthropic-billing-header: cc_version=2.1.104; cc_entrypoint=cli",
},
{
"type": "text",
"text": "You are Claude Code, Anthropic's official CLI... [静态段 ①-⑦]",
"cache_control": { "type": "ephemeral" }
},
{
"type": "text",
"text": "[动态段 ⑧-⑭] + [systemContext: git status, 环境信息]",
"cache_control": { "type": "ephemeral" }
}
],
"messages": [
{
"role": "user",
"content": "[userContext: CLAUDE.md 内容 + 当前日期]"
},
{
"role": "user",
"content": "[Skill 列表] + [项目 CLAUDE.md] + [用户输入]"
},
...对话历史 + 附件...
]
}8.2 三块缓存架构
API 请求的缓存分块:
┌────────────────────────────────────────┐ cache_control: ephemeral
│ Block 1: 工具定义 | Claude 版本号 │ scope: global
│ (跨用户共享,命中率最高) │ ← ~11K tokens, 几乎总是缓存命中
├────────────────────────────────────────┤ cache_control: ephemeral
│ Block 2: 系统提示词 | ~/.claude/ │ scope: session
│ CLAUDE.md | Git 状态 │ ← ~6K tokens, git 变化时未命中
├────────────────────────────────────────┤
│ Block 3: Skills | ./CLAUDE.md | │
│ 用户输入 │ ← 受 Block 2 未命中连带影响
└────────────────────────────────────────┘
理想情况(所有缓存命中):
→ cache_read: 17K, cache_write: 0 ← 节省 ~90% 费用
最坏情况(git 变化破坏缓存链):
→ cache_read: 11K, cache_write: 6K ← Block 2, 3 均未命中九、缓存策略与性能优化
9.1 三级缓存体系
| 级别 | 机制 | 范围 | 失效条件 |
|---|---|---|---|
| L1: Global Prompt Cache | Anthropic API 层 cache_control | 跨组织 | 静态段内容变化 |
| L2: Session Memoize | lodash/memoize | 单会话 | 会话结束 / 显式清除 |
| L3: Section Registry Cache | Map<string, content> | 单会话 | /memory 变更 / Settings 同步 / Worktree 变更 |
9.2 记忆化(Memoize)的设计意义
getUserContext() 和 getSystemContext() 使用 lodash/memoize,整个会话只计算一次:
| 特性 | 影响 |
|---|---|
| Git 状态是启动快照 | 会话期间 Git 操作不会更新状态 |
| CLAUDE.md 加载一次 | 编辑 CLAUDE.md 后需要 /memory 刷新或新会话 |
| 缓存在特定事件时清除 | Worktree 进入/退出、Settings 同步、/memory 对话框 |
typescript
// 缓存清除时机
export function setSystemPromptInjection(value: string | null): void {
systemPromptInjection = value
// 注入变更时立即清除两个上下文缓存
getUserContext.cache.clear?.()
getSystemContext.cache.clear?.()
}
// 在 compaction 时也会清除
resetGetMemoryFilesCache('compact')9.3 文件变更检测优化
contentDiffersFromDisk 标志实现了一个巧妙的优化:
磁盘上的原始内容 ──→ 处理管道 ──→ 注入到上下文的内容
│
▼
contentDiffersFromDisk = true/false当文件的注入内容与磁盘不同(注释剥离、frontmatter 移除、截断),原始内容被保留在旁边。这让文件状态缓存能跟踪变更,而不触发不必要的重新读取。
9.4 附件超时保护
typescript
const abortController = createAbortController()
const timeoutId = setTimeout(ac => ac.abort(), 1000, abortController)
// 每个附件来源都被 maybe() 包裹
function maybe<T>(fn: () => Promise<T>): Promise<T | null> {
try {
return await fn()
} catch (e) {
logSilently(e)
return null
}
}如果任何附件来源(文件读取、MCP 查询等)超过 1 秒,立即中止。这确保用户输入永远不会被附件计算阻塞。
9.5 Prompt Cache 优化最佳实践
基于社区经验和 Anthropic 的架构设计:
bash
# 最大化缓存命中率的启动方式
CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=1 claude "Hello"
# 效果:
# - Block 1 (工具): 缓存命中 ✓
# - Block 2 (系统提示词): 缓存命中 ✓(无 git status 变化)
# - Block 3 (Skills + CLAUDE.md): 缓存命中 ✓("Hello" 保持一致)十、上下文压缩系统(Compaction)
10.1 压缩触发条件
当上下文窗口使用率过高时,Claude Code 会触发压缩:
┌───────────────────────────────────────────────────────┐
│ 上下文使用量监控 │
│ │
│ ████████████████████████████████░░░░░ 80% │
│ ▲ │
│ │ │
│ 自动压缩触发阈值 │
│ │
│ 触发机制: │
│ 1. 反应式压缩 (Reactive): 上下文即将溢出时触发 │
│ 2. 自动压缩 (Auto-compact): 默认启用,后台周期性检查 │
│ 3. 手动压缩: /compact [instructions] │
└───────────────────────────────────────────────────────┘10.2 三阶压缩管道
上下文压缩管道:
Stage 1: Microcompact(细粒度工具结果压缩)
└── 压缩旧的工具调用结果(保留最新的)
└── 内联执行,不需要额外 API 调用
Stage 2: Token 删除(Token Deletion)
└── 删除可安全移除的 token
└── 如文件读取结果中的重复内容
Stage 3: Auto-compact(完整模型压缩)
└── 调用模型生成对话摘要
└── 替换旧对话历史为摘要
└── 保留关键信息(根据用户指令)10.3 压缩后的消息结构
typescript
// compact.ts: buildPostCompactMessages()
const compactedOutput = [
boundaryMarker, // 压缩边界标记
...summaryMessages, // 模型生成的摘要
...messagesToKeep, // 保留的近期消息
...attachments, // 重新注入的附件
...hookResults, // Hook 执行结果
]
// boundaryMarker 注解:
// headUuid, anchorUuid, tailUuid
// → 支持读取时的链式修补追加设计:压缩永远不修改或删除已写入的会话转录行,只追加新的边界和摘要事件。
十一、/context 命令:上下文可视化
analyzeContext.ts(约 1,383 行)为 /context 命令提供实时的上下文窗口拆解。
11.1 统计维度
| 统计项 | 说明 |
|---|---|
| 系统提示词 | 按段拆解的 token 数 |
| 记忆文件 | 每文件的 token 数 |
| 内置工具 | 常驻工具 vs 延迟加载工具 |
| MCP 工具 | 已加载 vs 延迟,按服务器分组 |
| Skills | frontmatter token 估算 |
| 消息 | 工具调用 vs 结果 vs 文本 |
| 自动压缩 | 预留缓冲区 |
11.2 可视化输出
/context
Context Window Usage: 72,543 / 200,000 tokens (36.3%)
████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 36%
Breakdown:
System Prompt │███░░░░░│ 12,340 (6.2%)
- Identity │█░░░░░░░│ 1,200
- Rules │██░░░░░░│ 4,560
- Tools Guide │█░░░░░░░│ 3,200
- Environment │░░░░░░░░│ 890
- Memory │█░░░░░░░│ 2,490
CLAUDE.md Files │██░░░░░░│ 8,750 (4.4%)
- ~/.claude/ │█░░░░░░░│ 2,100
- /project/ │█░░░░░░░│ 3,450
- .claude/rules/ │█░░░░░░░│ 3,200
Tool Definitions │████░░░░│ 18,920 (9.5%)
- Built-in (40) │███░░░░░│ 15,200
- MCP (12) │█░░░░░░░│ 3,720
Messages │█████░░░│ 32,533 (16.3%)
- Text │██░░░░░░│ 8,230
- Tool Calls │█░░░░░░░│ 4,120
- Tool Results │██░░░░░░│ 20,183十二、完整数据流:从用户输入到 API 调用
用户输入: "修复 src/auth.ts 中的登录 bug"
│
▼
┌────────────────────────────────────────────────────────────────┐
│ Phase 1: 上下文收集(并行) │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐ │
│ │ getSystemPrompt() │ │ getSystemContext()│ │getUserContext()│ │
│ │ │ │ │ │ │ │
│ │ 静态段: │ │ Git 状态快照: │ │ CLAUDE.md: │ │
│ │ ①-⑦ 全局段 │ │ branch: main │ │ 用户级 │ │
│ │ 动态段: │ │ status: clean │ │ 项目级 │ │
│ │ ⑧-⑭ 会话段 │ │ log: abc1234... │ │ 本地级 │ │
│ │ │ │ user: dev │ │ │ │
│ │ (memoized) │ │ (memoized) │ │ 日期: │ │
│ │ │ │ │ │ 2026-04-25 │ │
│ │ │ │ │ │ (memoized) │ │
│ └────────┬─────────┘ └────────┬─────────┘ └──────┬───────┘ │
│ │ │ │ │
└───────────┼─────────────────────┼────────────────────┼─────────┘
│ │ │
▼ ▼ ▼
┌────────────────────────────────────────────────────────────────┐
│ Phase 2: 提示词组装 │
│ │
│ systemPrompt = buildEffectiveSystemPrompt( │
│ override? → coordinator? → agent? → custom? → default │
│ ) │
│ │
│ effectiveSystemPrompt = asSystemPrompt( │
│ appendSystemContext(systemPrompt, systemContext) │
│ ) │
│ │
│ messagesForQuery = prependUserContext(messages, userContext) │
└────────────────────────────────┬───────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ Phase 3: 附件收集(1 秒超时) │
│ │
│ getAttachments() │
│ ├── 文件内容附件 │
│ ├── IDE 集成附件 │
│ ├── 嵌套记忆附件 │
│ ├── TODO/任务提醒 │
│ ├── Skill 发现/调用 │
│ ├── 工具增量 │
│ ├── Token 预算 │
│ └── ...30+ 类型 │
│ │
│ 超时处理: AbortController + setTimeout(1000ms) │
│ 错误处理: maybe() 包裹,静默失败 │
└────────────────────────────────┬───────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ Phase 4: 消息标准化 + 可选压缩 │
│ │
│ normalizeMessagesForAPI() │
│ └── 将 messages + attachments 转换为 Anthropic 格式 │
│ │
│ microcompactMessages()(可选) │
│ └── 压缩旧工具结果(FRC) │
│ │
│ 上下文窗口超限检查 │
│ └── 如需压缩 → 触发 reactiveCompact() │
└────────────────────────────────┬───────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ Phase 5: API 调用 │
│ │
│ callModel({ │
│ system: [ │
│ { text: billing_header }, │
│ { text: 静态段, cache: ephemeral }, │
│ { text: 动态段 + systemContext, cache: ephemeral }, │
│ ], │
│ messages: [ │
│ userContext, ← CLAUDE.md + 日期 │
│ 用户输入 + 附件, │
│ ...对话历史, │
│ ], │
│ tools: [...], │
│ model: "claude-opus-4-6", │
│ thinking: { budget: ... }, │
│ }) │
└────────────────────────────────────────────────────────────────┘延迟注入(Late Injection)
在主上下文窗口构建完成后,以下内容可能在轮次执行期间后期注入:
| 延迟源 | 触发条件 |
|---|---|
| 相关记忆预取 | query.ts 中的 relevant-memory prefetch |
| MCP 指令增量 | 新增或变更的 MCP 服务器指令 |
| Agent 列表增量 | 子 Agent 创建或销毁 |
| 后台任务通知 | 后台 Agent 任务完成 |
这意味着上下文窗口在组装时不是静态的——它可以在轮次执行期间增长。
十三、设计哲学与可迁移模式
13.1 核心设计原则
| 原则 | 实现 | 价值 |
|---|---|---|
| 透明性 | CLAUDE.md 是纯文本 Markdown | 用户可读、可编辑、可版本控制 |
| 分层优先级 | 6 级记忆层级 + 后加载优先 | 组织 → 用户 → 项目的清晰覆盖链 |
| 确定性 vs 概率性分离 | 权限系统 vs CLAUDE.md | 硬约束用权限,软指导用记忆 |
| 渐进式上下文 | 延迟加载 + 条件规则 | 只加载相关指令,节省 token |
| 性能优先 | 三级缓存 + 1 秒超时 + 并行获取 | 首次交互延迟最小化 |
| 失败容忍 | maybe() 包裹 + 静默降级 | 单个附件失败不影响整体 |
13.2 Guidance vs Enforcement 的双轨模型
┌─────────────────────────────────────────────────────┐
│ │
│ Guidance(引导层) Enforcement(执行层) │
│ │
│ CLAUDE.md 内容 Permission Rules │
│ ↓ ↓ │
│ 作为 User Message 注入 Deny-First 硬性检查 │
│ ↓ ↓ │
│ 模型概率性遵从 确定性执行/拒绝 │
│ ↓ ↓ │
│ "请使用 TypeScript" "禁止 rm -rf /" │
│ → 模型通常遵从但可能偏离 → 绝对不允许执行 │
│ │
└─────────────────────────────────────────────────────┘这个双轨设计意味着:
- CLAUDE.md 提供灵活的、可协商的指导
- 权限规则 提供不可绕过的安全边界
- 两者互补而非竞争
13.3 可迁移的设计模式
这些模式可以直接应用于任何 LLM Prompt 工程架构:
1. 分段缓存 + 动态边界
静态段(全局共享)→ BOUNDARY → 动态段(会话特定)
↑
缓存行为在此分界2. 记忆化状态加载器
初始化时计算一次 → 缓存整个会话 → 显式事件触发清除
避免: 每轮重新计算高成本上下文3. 附件超时保护
每个数据源 → maybe() 包裹 → 1 秒超时 → 静默降级
确保: 用户体验永远不被单个数据源阻塞4. 后加载优先级
先加载的 = 低优先级(通用规则)
后加载的 = 高优先级(具体规则)
符合 CSS-like 的级联覆盖直觉5. 延迟加载 + 按需注入
启动时: 只加载 CWD 及以上的指令文件
运行时: 当工具操作新目录 → 动态加载该目录的规则
效果: 指令集随代码库探索而演化