本文聚焦于 Claude Code 压缩过程中 如何保留关键信息,不涉及压缩的触发、调度和降级机制(压缩整体架构见《上下文压缩三层策略》)。

源码位置:https://github.com/claude-code-best/claude-code src/services/compact/src/services/SessionMemory/src/bootstrap/state.ts


目录

  1. 概览:信息保留的两条路径
  2. 路径一:AI 摘要的结构化保留
  3. 路径二:Session Memory 的结构化记录
  4. 压缩后重新注入
  5. 保留窗口:不压缩最近消息
  6. 工具对完整性保护
  7. 跨压缩状态保留
  8. Hook 机制:用户自定义保留逻辑
  9. 保留信息的最终消息结构

1. 概览:信息保留的两条路径

上下文压缩不是简单的截断——系统通过 两条互补路径 确保压缩后仍有足够的上下文继续工作:

原始对话(几十万 tokens)
        │
        ├── 路径一:调用 AI 生成 9 部分结构化摘要
        │           ↓
        │   摘要捕获:意图、决策、代码、错误、待办...
        │           ↓
        │   压缩后重新注入:最近文件、技能、Plan、CLAUDE.md
        │
        └── 路径二:后台 Session Memory 持续提取 10 部分结构化记录
                    ↓
            直接作为摘要使用(免 API 调用)
                    ↓
            压缩后保留 10K-40K 的最近消息窗口

2. 路径一:AI 摘要的结构化保留

文件src/services/compact/prompt.ts

传统压缩路径会向 AI 发送精心设计的 Prompt,要求生成包含 9 个特定部分的摘要。这不是"自由摘要",而是结构化的信息提取

2.1 分两步生成:分析 → 摘要

Prompt 要求模型先用 <analysis> 标签梳理思路,再输出 <summary> 正式摘要。<analysis> 部分在格式化时被丢弃——它只用于提升摘要质量:

模型输出:
<analysis>         ← 草稿,格式化时丢弃
  [按时间顺序梳理每条消息,提取关键信息]
</analysis>

<summary>          ← 保留为最终摘要
  1. ...
  2. ...
</summary>

源码 formatCompactSummary() 负责最终只保留 <summary> 内容。

2.2 9 部分摘要模板

序号 部分 Prompt 要求
1 Primary Request and Intent 捕获用户的所有显式请求和意图,注意细节
2 Key Technical Concepts 列出所有涉及的技术概念、框架、技术栈
3 Files and Code Sections 列举文件及其重要性,包含完整代码片段和函数签名
4 Errors and fixes 所有错误及修复方法,特别注意用户的纠正反馈
5 Problem Solving 已解决的问题和正在进行的排查
6 All user messages 列出所有非工具结果的用户消息(理解用户意图变化的关键)
7 Pending Tasks 用户明确要求但尚未完成的任务
8 Current Work 精确描述正在做的工作,包含文件名和最新代码
9 Optional Next Step 下一步建议,必须引用最近对话的原文,防止任务理解偏移

Prompt 原文中的关键措辞(第 6、9 两部分尤为重要):

6. All user messages: 
   List ALL user messages that are not tool results.
   These are critical for understanding the users' feedback and changing intent.

9. Optional Next Step:
   If there is a next step, include direct quotes from the most recent 
   conversation showing exactly what task you were working on and where 
   you left off. This should be verbatim to ensure there's no drift in 
   task interpretation.

2.3 自定义保留指令

用户可以通过 /compact 命令附带自定义指令,告诉 AI 在摘要中重点关注特定信息:

/compact 聚焦在对 auth 模块的修改,记住我改过的每一个函数签名

这些指令会被注入到摘要 Prompt 中:

Additional Instructions:
聚焦在对 auth 模块的修改,记住我改过的每一个函数签名

同时支持 PreCompact Hook 注入指令(见第 8 节)。

2.4 发送摘要时的附加上下文

摘要生成后,作为用户消息发送给后续对话时,会附加:

// prompt.ts — getCompactUserSummaryMessage()
This session is being continued from a previous conversation that ran out 
of context. The summary below covers the earlier portion of the conversation.

[格式化后的 9 部分摘要]

If you need specific details from before compaction (like exact code snippets, 
error messages, or content you generated), read the full transcript at: 
/path/to/transcript.jsonl   ← 完整对话记录路径

Recent messages are preserved verbatim.  ← 标记有保留的最近消息

这样即使摘要丢失了某些细节,模型可以通过 Read 工具回溯完整 transcript。


3. 路径二:Session Memory 的结构化记录

文件src/services/SessionMemory/prompts.ts

Session Memory 是一个独立于压缩的后台系统,持续从对话中提取信息并写入结构化文件 ~/.claude/session-memory/memory.md

3.1 10 部分结构化模板

# Session Title
_简短但有信息密度的 5-10 词描述性标题_

# Current State
_当前正在做什么?尚未完成的待办。立即需要执行的后续步骤。_

# Task specification
_用户要求构建什么?任何设计决策或解释性上下文_

# Files and Functions
_关键文件有哪些?简要说明每个文件包含什么、为什么重要_

# Workflow
_常用命令及执行顺序,输出不直观时如何解读_

# Errors & Corrections
_遇到的错误及修复方法。用户纠正了什么?哪些方法失败、不应重试?_

# Codebase and System Documentation
_重要系统组件是什么?它们如何工作和协作?_

# Learnings
_什么有效?什么无效?应避免什么?(不要重复其他部分内容)_

# Key results
_如果用户要求了具体输出(答案、表格、文档等),在此重复完整结果_

# Worklog
_逐步骤记录:尝试了什么、完成了什么?每步用极简摘要_

3.2 后台更新机制

对话进行中
  │
  ├── 上下文达到 10K tokens → 后台 agent 首次提取
  │
  ├── 上下文再增长 5K tokens → 再次触发更新
  │  (或累积 3 次工具调用后触发)
  │
  └── 每次更新:agent 用 Edit 工具增量修改 memory.md

3.3 内容约束

更新 Prompt 中有明确的内容指令

  • 保持结构完整性:绝不修改或删除 section header 和 italic 描述行
  • 信息密度优先:写入具体的文件路径、函数名、错误信息、完整输出
  • 不包含 CLAUDE.md 已有信息:避免重复项目级文档内容
  • 每节上限 ~2000 tokens:超出时缩略非关键细节
  • 优先保留 Current State 和 Errors & Corrections:这两部分对连续性最重要

3.4 压缩时作为摘要直接使用

SM 压缩路径不需要调用 AI 生成摘要——直接将 memory.md 内容作为对话摘要:

传统路径:  消息 → 调用 AI → 生成 9 部分摘要 → 替换消息

SM 路径:   消息 → 读取 memory.md → 直接作为摘要 → 替换消息
                      ↑
                后台 agent 已提取好

3.5 截断保护

如果 memory.md 过大,truncateSessionMemoryForCompact() 按节截断(每节 ≤ 2000 tokens),确保不会占用整个重新注入预算。


4. 压缩后重新注入

文件src/services/compact/compact.tscompactConversation() 后半段)

压缩生成摘要后,系统会从摘要之外主动恢复关键上下文,总预算 50,000 tokens

4.1 重新注入清单

类别 预算/限制 来源 目的
最近读取的文件 最多 5 个,每个 ≤ 5K readFileState 缓存 恢复模型正在操作的文件内容
已调用的技能 每个 ≤ 5K,总预算 25K invokedSkills 状态 技能指令通常是 ~20K 的文件头最关键
Plan 文件 无单独限制 Plan mode 状态 保持 plan 模式上下文不丢失
CLAUDE.md 无单独限制 processSessionStartHooks 重新注入项目指引
MCP 指令 重新公告 mcpClients delta 恢复 MCP 工具描述和指令
Agent 列表 重新公告 agent 注册表 恢复可用子 agent
异步 Agent 状态 状态快照 运行中的异步 agent 恢复未完成的并行任务
Deferred Tools 重新公告 tools delta 延迟加载的工具描述

4.2 文件附件恢复

// compact.ts — createPostCompactFileAttachments()
const recentFiles = Object.entries(readFileState)
  // 排除不应恢复的路径(如 Session Memory 文件自身)
  .filter(file => !shouldExcludeFromPostCompactRestore(...))
  // 排除保留消息中已读取的文件(避免重复)
  .filter(file => !preservedReadPaths.has(...))
  // 按时间戳降序 → 取最新的 5 个
  .sort((a, b) => b.timestamp - a.timestamp)
  .slice(0, POST_COMPACT_MAX_FILES_TO_RESTORE)  // 5

// 每个文件截断到 POST_COMPACT_MAX_TOKENS_PER_FILE (5K)
results.filter(r => {
  if (usedTokens + tokens > POST_COMPACT_TOKEN_BUDGET) return false  // 50K
  usedTokens += tokens
  return true
})

4.3 技能附件恢复

// compact.ts — createSkillAttachmentIfNeeded()
const skills = Array.from(invokedSkills.values())
  // 最近调用的优先(预算紧张时丢弃较早的)
  .sort((a, b) => b.invokedAt - a.invokedAt)
  // 每个技能截断到 5K tokens(头部通常包含关键指令)
  .map(skill => truncateToTokens(skill.content, 5000))
  // 总预算 25K tokens
  .filter(skill => {
    if (usedTokens + tokens > 25000) return false
    usedTokens += tokens
    return true
  })

4.4 刻意不恢复的内容

内容 原因
skill_listing(技能列表) ~4K tokens,纯 cache_creation 开销;模型已有 SkillTool schema
sentSkillNames 被故意不重置,避免重新触发技能发现

5. 保留窗口:不压缩最近消息

文件src/services/compact/sessionMemoryCompact.ts

压缩不是"全量总结"——最近的消息窗口会被完整保留

5.1 保留窗口算法

function calculateMessagesToKeepIndex(messages, lastSummarizedIndex) {
  let startIndex = lastSummarizedIndex + 1
  // 从 lastSummarizedIndex 向前扩展
  for (let i = startIndex - 1; i >= floor; i--) {
    totalTokens += estimateMessageTokens([msg])
    if (hasTextBlocks(msg)) textBlockMessageCount++
    startIndex = i
    // 命中上限 → 停止
    if (totalTokens >= config.maxTokens) break            // 40K
    // 同时满足两个下限 → 停止(足够了)
    if (totalTokens  >= config.minTokens &&               // 10K
        textBlockMessageCount >= config.minTextBlockMessages) break  // 5 条
  }
  return adjustIndexToPreserveAPIInvariants(messages, startIndex)
}

三重约束

约束 含义
下限 minTokens 10,000 至少保留 10K token 的上下文深度
下限 minTextBlockMessages 5 至少保留 5 条有文本的消息(保持对话连贯性)
上限 maxTokens 40,000 不超过 40K(避免刚压缩完就又要压缩)

必须同时满足两个下限命中上限才停止扩展。


6. 工具对完整性保护

文件src/services/compact/sessionMemoryCompact.ts — adjustIndexToPreserveAPIInvariants()

这是压缩系统的核心正确性保证——确保压缩后不会产生 API 格式错误。

6.1 问题

Anthropic API 要求每个 tool_result 都有对应的 tool_use 与之配对。如果压缩恰好切在 tool_result 消息处,API 会收到一个没有对应 tool_use 的 tool_result → 报错

6.2 两步修复

Step 1: 向前扫描被保留消息中的所有 tool_result
        → 找到它们引用的 tool_use
        → 如果 tool_use 在被删除的消息中 → startIndex 前移,把 tool_use 也保留

Step 2: 向前扫描被保留的 assistant 消息
        → 查找与其共享 message.id 的 thinking/tool_use 块
        → 如果这些块在被删除的消息中 → startIndex 前移,一起保留

流式传输会将一个 assistant 消息拆分为多条存储记录(thinking、tool_use 等各有独立 uuid 但共享 message.id),这增加了边界情况的复杂度,两步修复确保所有碎片都被正确处理。


7. 跨压缩状态保留

文件src/bootstrap/state.ts

压缩后系统不会清空所有状态——部分关键状态会跨压缩保持

7.1 保留的状态

状态 保留原因
invokedSkills 已调用的技能内容按 agent 隔离保存,压缩后重新注入
sentSkillNames 刻意不重置——避免重新注入 ~4K 的 skill_listing
cacheEditingHeaderLatched 缓存编辑 header,防止中途切换 bust cache
cachedClaudeMdContent CLAUDE.md 缓存,用于 auto-mode 分类器

7.2 压缩标记

markPostCompaction()    → 设置 pendingPostCompaction = true
consumePostCompaction() → 读取并重置该标记

标记用于让分析系统(如缓存中断检测)能区分"压缩引起的缓存 miss"和"TTL 过期引起的缓存 miss"。

7.3 压缩后清理列表

以下状态在压缩后会被清除postCompactCleanup.ts),因为它们已不再有效:

✅ 清除:
  - MicroCompact 状态              - Context Collapse 状态
  - getUserContext 缓存             - Memory 文件缓存
  - 系统 Prompt 分段               - 分类器审批
  - 推测性检查                      - Beta 追踪状态
  - Session Messages 缓存

❌ 不清除:
  - 技能名称 sentSkillNames        - 已调用技能 invokedSkills

8. Hook 机制:用户自定义保留逻辑

文件src/services/compact/compact.ts — executePreCompactHooks / executePostCompactHooks

用户在 CLAUDE.md 或 Hook 配置中可以定义压缩前后的自定义指令。

8.1 PreCompact Hook

在压缩摘要生成前执行,用户可以指定:

## Compact Instructions
When summarizing the conversation, focus on TypeScript code changes 
and also remember the mistakes you made and how you fixed them.

这些指令会合并到摘要 Prompt 中,引导 AI 在摘要中重点关注用户关心的信息。

8.2 PostCompact Hook

在压缩完成后执行,用户可以验证关键信息是否被保留,或执行自定义恢复逻辑。

8.3 SessionStart Hook

SM 压缩使用 processSessionStartHooks('compact') 重新加载 CLAUDE.md 等启动上下文,确保压缩后项目指引不丢失。


9. 保留信息的最终消息结构

压缩后,消息数组重组为以下顺序:

[0] CompactBoundary 标记
      └── preservedSegment: { headUuid, anchorUuid, tailUuid }
           记录哪些消息被保留

[1] 摘要消息(SummaryMessages)
      ├── 路径一: AI 生成的 9 部分结构化摘要
      └── 路径二: Session Memory 10 部分结构内容

[2] 保留的消息窗口(MessagesToKeep)
      ├── 10K-40K tokens 的最近消息
      └── 通过 API 不变性调整,确保 tool_use/tool_result 配对完整

[3] 重新注入的附件(Attachments)
      ├── 最近读取的文件(≤ 5 个,每个 ≤ 5K tokens)
      ├── 已调用技能(≤ 25K tokens 总预算)
      ├── Plan 文件 & Plan Mode 指令
      ├── CLAUDE.md(通过 SessionStart Hook)
      ├── MCP 工具指令 & Agent 列表
      └── 异步 Agent 状态

[4] Hook 结果(HookResults)
      ├── SessionStart Hook 结果
      └── PostCompact Hook 结果

这样设计保证了:

  • 意图不丢失:9 部分摘要 + 用户消息列表覆盖用户需求变化
  • 代码上下文不丢失:文件附件 + 技能附件恢复关键代码
  • 连续性不中断:最近消息保留 + 工具对完整性 + 下一步引用原文
  • 可回溯:transcript 路径嵌入摘要,缺失细节可回读完整记录

 

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐