# 上下文压缩为什么需要六套机制，而不是一套？

深入分析 Claude Code 的六级上下文压缩流水线——从轻量的工具结果裁剪到紧急的全量摘要替换——解释为何单一压缩策略无法应对真实场景的多样性。

### 🌍 行业背景：AI 编程工具的上下文管理策略

上下文窗口管理是所有 LLM 应用的核心挑战，但各家的应对策略差异很大：

- **Cursor**：采用"检索增强"策略——不试图把所有历史塞进上下文，而是用 embedding 索引整个代码库，每次对话只检索最相关的代码片段。上下文压缩相对简单，主要依赖 RAG（Retrieval-Augmented Generation）来避免窗口溢出。
- **Aider**：Repo Map 已进化到 **AST（抽象语法树）级别**，将数十万行代码库精炼为类定义、函数签名及调用依赖的高密度图谱，大幅降低了 Token 消耗。对话历史超长时做单次 AI 摘要替换——本质上只有 Claude Code 六套机制中"autocompact"这一套，但 AST 级精炼显著减少了对话摘要的压力。
- **Kimi Code**：Agent Swarm 架构下，每个子智能体被限制在极小的上下文范围内，从根本上规避了单一超长上下文的管理难题。协调器负责宏观上下文，子智能体各自维护微观上下文——这是一种"分而治之"的上下文策略，与 Claude Code 的"单一上下文多级压缩"形成鲜明对比。
- **LangChain / LlamaIndex**：作为框架层提供 `ConversationSummaryBufferMemory` 和 `ConversationTokenBufferMemory`。这些是通用方案，没有针对工具调用结果的专门处理。
- **Codex（OpenAI）**：底层已用 Rust 重写（具体比例以官方为准），在内存管理上有先天优势，但上下文管理策略更侧重于并行 Agent 各自独立维护上下文而非单一长上下文的压缩。
- **Windsurf**：Cascade Engine 实时跟踪开发者的光标位置、文件切换历史和终端输出轨迹，维持高度动态的上下文树——结合了 RAG 检索和持续状态感知。
- **Cody（Sourcegraph）**：Deep Search 联合 MCP 引擎能跨越不同 Git 仓库追溯架构设计文档，为大模型构建跨微服务的 API 调用依赖图，代表了"企业级代码图谱"路线的上下文管理方案。

Claude Code 的六级梯度压缩在业界属于较为精细的设计。多数工具采用 1-2 种机制（截断 + 摘要），Claude Code 区分了六种不同的"上下文过大"子场景并分别处理，这种分层方法虽增加了系统复杂度，但在 token 成本和信息保留之间取得了更精细的平衡。

---

## 问题

Claude Code 源码里有六种不同的上下文压缩机制：toolResultBudget、snipCompact、microcompact、contextCollapse、autocompact、reactiveCompact。前五类在每次 AI 调用前根据条件分支决策触发（并不是严格串行全跑），reactiveCompact 则是收到 413 错误后的恢复路径。为什么需要这么多套机制？一套不够用吗？

> 💡 **通俗理解**：这就像**会议记录员面对一本越来越厚的会议纪要**——第一步：把超长的附件折叠起来 → 第二步：删掉"今天没什么事"的空白页 → 第三步：去掉重复出现的内容 → 第四步：把上个月的内容写成一页摘要 → 第五步：整本纪要太厚了，做一份精华版替代 → 第六步：精华版还是太厚？紧急再压缩一次。六步从轻到重，绝大多数时候前三步就够了。

---

## 你可能以为……

直觉上，你可能认为"快满了就压缩，用 AI 生成一个摘要替换旧内容"这一套机制就够了。确实这是最直观的做法——Aider、LangChain 的 `ConversationSummaryMemory` 等主流方案本质上就是这个思路。

但实际情况比这复杂得多。

---

## 实际上是这样的

上下文压缩是一个**多维度的权衡问题**，而这六套机制分别针对不同的维度：

### 第一关：toolResultBudget（工具结果大小上限）

**针对的问题：** 某一个工具调用返回了巨型结果（比如读了一个 100KB 的文件），但 AI 很快就不再需要这个结果了。

**做法：** 按照每个工具配置的 `maxResultSizeChars` 上限截断工具结果内容，并用占位符替换。这个操作在内存中完成，不需要任何 AI 调用，成本接近零。

**为什么不用全文压缩代替：** 因为这里不是"整体上下文太大"，而是"单个结果太大"。用大炮打蚊子既浪费又破坏结果完整性——截断比压缩更精准。

---

### 第二关：snipCompact（历史片段剪裁）[feature: HISTORY_SNIP]

**针对的问题：** 对话历史中有一些"中间过程"——比如搜索了几十个文件最终找到了目标，这些搜索过程消耗了大量 token，但它们的信息已经隐含在后续操作中了，可以安全地删除。

**做法：** 用规则识别可以"剪掉"的历史片段，直接删除，不生成摘要。比全文摘要更快，因为不需要 AI 参与。

**为什么不用全文压缩代替：** 因为全文压缩是"推倒重来"——把所有历史变成一个 AI 生成的摘要，会损失细节。snip 是"精准切除"——只删掉确定无用的部分，保留其余所有原始信息。

---

### 第三关：microcompact（微型压缩）

**针对的问题：** 同一个文件被读了很多次（AI 查看、工具读取、确认修改），每次读取的内容都作为独立的 `tool_result` 保留在历史中，造成大量冗余。

**做法：** 识别重复的 `tool_result` 内容，用"文件未变化"的占位符替换较早的版本。实际上有三种变体：

1. **Time-based microcompact**：如果距离上次请求超过 60 分钟（`timeBasedMCConfig.ts:32` `gapThresholdMinutes: 60`；对应 Anthropic prompt cache 可选的 1 小时 `cache_control.ttl` 档位，标准默认 TTL 是 5 分钟，此处取较宽的 1 小时档位），主动清理旧的工具结果，因为反正缓存要重建。
2. **Cached microcompact**：通过 API 的 `cache_edits` 机制直接编辑服务端缓存中的工具结果，本地消息不变——只在 API 层面删除，不破坏缓存前缀。
3. **API-level microcompact**（`apiMicrocompact.ts`）：通过 `context_management` API 参数将清理完全委托给服务端——客户端告诉 API"当 input tokens 超过 180K 时，至少清理约 140K tokens，使剩余输入规模回落到约 40K"（SoT：`apiMicrocompact.ts:17` `DEFAULT_TARGET_INPUT_TOKENS = 40_000`；line 120 `clear_at_least = triggerThreshold - keepTarget`），无需客户端跟踪任何状态。这标志着上下文管理从"客户端协商"向"服务端原生支持"的演进。

**为什么不用全文压缩代替：** 成本差一个数量级——microcompact 的三个变体要么是纯本地内存操作（Time-based：识别+替换 tool_result），要么只是附加一次 cache 编辑或 `context_management` 参数（Cached / API-level），不走 AI 摘要；而全文压缩需要一次完整的 AI 摘要调用（成本和时间都高得多）。

---

### 第四关：contextCollapse（上下文折叠）[feature: CONTEXT_COLLAPSE]

**针对的问题：** 对话的早期阶段有大量"已经完成的探索性工作"（比如一开始的代码阅读、需求分析），这些内容对后续工作帮助不大，但全文摘要又太激进，会丢失太多信息。

**做法：** 将较旧的对话段"折叠"——在 API 上下文中用摘要替换，但在 REPL 的 UI 历史视图中仍然保留完整版本（用户仍然能滚动看到）。这是一种"视图层"和"模型层"分离的设计。

**特殊之处：** 是在 413 错误发生之前的"预防性"折叠，比 reactiveCompact 更早触发，成本也更低，因为它折叠的粒度更细、可以复用更多缓存。

---

### 第五关：autocompact（自动全文压缩）

**针对的问题：** 上下文整体接近 context window 上限，前面的机制都不够用了，需要彻底压缩。

**做法：** 用 fork agent（一个子 AI 实例）读取完整对话历史，生成一个高质量的摘要，然后用这个摘要替换所有历史。触发阈值是：`context_window - 13,000 tokens`（预留一定空间给即将到来的 AI 响应）。摘要并非自由发挥——压缩提示词要求生成 **9 个 section 的结构化摘要**：任务意图、关键技术概念、文件与代码片段、错误与修复、问题解决过程、**所有用户消息原文**（关键的意图追踪）、待办任务、当前工作、可选的下一步。输出采用双阶段结构：先生成 `<analysis>` 草稿 block 供模型整理思路，再生成 `<summary>` 正式摘要——`<analysis>` 在进入上下文前被剥除，只是提升摘要质量的"思考纸"，不占用压缩后的 token 预算。

💡 **通俗理解**：就像考试前整理笔记——先在草稿纸上梳理一遍重点（`<analysis>`），再把要点抄到正式笔记本上（`<summary>`），然后把草稿纸扔掉。

#### Compaction 提示词原文：NO_TOOLS_PREAMBLE

**来源**: `services/compact/prompt.ts` → `NO_TOOLS_PREAMBLE`（第 19-26 行）

这段文字是每次压缩提示词的**最开头**，比任何实质性指令都要先出现——专为 Sonnet 4.6+ 的 adaptive-thinking 模型设计，因为这类模型更容易在被要求"只输出文本"时仍然尝试调用工具：

```
CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.

- Do NOT use Read, Bash, Grep, Glob, Edit, Write, or ANY other tool.
- You already have all the context you need in the conversation above.
- Tool calls will be REJECTED and will waste your only turn — you will fail the task.
- Your entire response must be plain text: an <analysis> block followed by a <summary> block.
```

**设计要点**：注意这段文字语气极为强硬（"CRITICAL"、"REJECTED"、"you will fail the task"）。源码注释解释了原因：压缩用的 fork agent 为了命中 prompt cache 必须继承父请求完整的工具集，但 `maxTurns: 1` 意味着一旦模型尝试调用工具，调用被拒绝后就没有第二次机会了——那次压缩就白费了。把拒绝后果写得如此明确，是为了从一开始就打消模型的"试一试"念头。

#### Compaction 提示词原文：DETAILED_ANALYSIS_INSTRUCTION

**来源**: `services/compact/prompt.ts` → `DETAILED_ANALYSIS_INSTRUCTION_BASE`（第 31-44 行）

这是嵌入在主摘要提示词中的 `<analysis>` 草稿阶段指令：

```
Before providing your final summary, wrap your analysis in <analysis> tags to organize your thoughts 
and ensure you've covered all necessary points. In your analysis process:

1. Chronologically analyze each message and section of the conversation. For each section thoroughly identify:
   - The user's explicit requests and intents
   - Your approach to addressing the user's requests
   - Key decisions, technical concepts and code patterns
   - Specific details like:
     - file names
     - full code snippets
     - function signatures
     - file edits
   - Errors that you ran into and how you fixed them
   - Pay special attention to specific user feedback that you received, especially if the user told you 
     to do something differently.
2. Double-check for technical accuracy and completeness, addressing each required element thoroughly.
```

**设计要点**：注意 "Pay special attention to specific user feedback that you received, especially if the user told you to do something differently" 这句话。压缩时最容易丢失的不是技术细节，而是用户的**纠正行为**——比如"不，不要用这种方式"、"我之前说的那个方法不行"。这些用户纠正如果在压缩中丢失，AI 就会在后续对话中重复犯同样的错误。提示词专门强调这一点，说明这是从实际踩坑中总结的经验。

#### Compaction 提示词原文：BASE_COMPACT_PROMPT（完整版）

**来源**: `services/compact/prompt.ts` → `BASE_COMPACT_PROMPT`（第 61-143 行）

这是全文压缩（autocompact / reactiveCompact）的核心提示词。`getCompactPrompt()` 函数将它夹在 `NO_TOOLS_PREAMBLE` 和 `NO_TOOLS_TRAILER` 之间发送给 fork agent：

```
Your task is to create a detailed summary of the conversation so far, paying close attention to the 
user's explicit requests and your previous actions.
This summary should be thorough in capturing technical details, code patterns, and architectural 
decisions that would be essential for continuing development work without losing context.

[...DETAILED_ANALYSIS_INSTRUCTION_BASE...]

Your summary should include the following sections:

1. Primary Request and Intent: Capture all of the user's explicit requests and intents in detail
2. Key Technical Concepts: List all important technical concepts, technologies, and frameworks discussed.
3. Files and Code Sections: Enumerate specific files and code sections examined, modified, or created. 
   Pay special attention to the most recent messages and include full code snippets where applicable 
   and include a summary of why this file read or edit is important.
4. Errors and fixes: List all errors that you ran into, and how you fixed them. Pay special attention 
   to specific user feedback that you received, especially if the user told you to do something 
   differently.
5. Problem Solving: Document problems solved and any ongoing troubleshooting efforts.
6. All user messages: List ALL user messages that are not tool results. These are critical for 
   understanding the users' feedback and changing intent.
7. Pending Tasks: Outline any pending tasks that you have explicitly been asked to work on.
8. Current Work: Describe in detail precisely what was being worked on immediately before this summary 
   request, paying special attention to the most recent messages from both user and assistant. Include 
   file names and code snippets where applicable.
9. Optional Next Step: List the next step that you will take that is related to the most recent work 
   you were doing. IMPORTANT: ensure that this step is DIRECTLY in line with the user's most recent 
   explicit requests, and the task you were working on immediately before this summary request. If your 
   last task was concluded, then only list next steps if they are explicitly in line with the users 
   request. Do not start on tangential requests or really old requests that were already completed 
   without confirming with the user first.
   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.

[...示例结构 + 自定义指令注入口...]
```

💡 **通俗理解**：这份提示词像一张**会议纪要模板**——它不只说"帮我总结一下"，而是列出了9个具体栏目要填写，还要求在正式交稿前先打一份草稿（`<analysis>`）审查自己有没有漏掉什么。第 6 条要求 "List ALL user messages"（列出所有非工具结果的用户消息），第 9 条进一步要求"include direct quotes … This should be verbatim"——也就是**最严格的逐字原文保留**只出现在第 9 条（Next Step 的原文引用），第 6 条要求的是"全部枚举"而非"逐字原样"。这两条合起来才达成"用户意图不丢失"的目标：AI 总结的版本可能丢失"语气"和"强调"，通过原文引用锁死任务理解。

**9 个 section 的设计逻辑分析**：

| Section | 作用 | 最容易丢失的信息 |
|---------|------|----------------|
| 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 | 不遗漏 TODO | 中途插入的新任务 |
| 8. Current Work | 断点续传 | 进行中的具体操作 |
| 9. Optional Next Step | 压缩后继续工作 | 原文引用防止漂移 |

第 9 条要求"include direct quotes from the most recent conversation"——用原文引用而非 AI 总结，就是为了防止在接续工作时因措辞变化导致任务理解偏差（"task drift"）。

**代价：** 一次完整的 AI API 调用，成本高，时间长。注释里说 p99.99 的摘要输出约 17,387 tokens，所以给摘要留了 20,000 tokens 的空间。压缩完成后，系统会重新注入关键上下文：最近读取的最多 5 个文件（`POST_COMPACT_MAX_TOKENS_PER_FILE = 5_000`，即单文件上限 5K tokens，因此文件内容本身最多占 5×5K = 25K）、Skill 内容、Plan 附件、工具定义、MCP 配置等——整体恢复预算 `POST_COMPACT_TOKEN_BUDGET = 50_000`，文件之外的额度留给其他上下文项。压缩后不是一片空白，而是一个"精炼过的工作台"。

**circuit breaker：** 如果 autocompact 连续失败 3 次，就停止重试——生产数据显示有会话每天因此浪费约 25 万次 API 调用，所以加了这个熔断机制。

---

### 第六关：reactiveCompact（响应式压缩）

**针对的问题：** 尽管有前五关，有时还是会触发 API 返回 413（`prompt_too_long`）错误。这通常意味着 autocompact 的阈值没有足够的余量，或者工具结果在一次循环内突然大量增加。

**做法：** 收到 413 后，立即触发紧急全文摘要压缩，然后重试。与 autocompact 的区别是：autocompact 是"快满了主动压缩"，reactiveCompact 是"满了之后紧急压缩"。

**关键细节：** 用 `hasAttemptedReactiveCompact` 标志确保 reactiveCompact 只尝试一次——如果压缩后再次触发 413，就认为无法恢复，直接报错。这防止了"压缩→失败→再压缩→再失败"的死循环。

---

## 这个设计背后的取舍

这六套机制形成了一个**成本梯度**：

```
成本从低到高：
toolResultBudget < snipCompact ≈ microcompact < contextCollapse < autocompact ≈ reactiveCompact
```

系统总是先尝试成本最低的方法，只有在低成本方法不够用时才升级。这是一种贪心策略：在保证不崩溃的前提下，尽可能保留更多原始上下文（更低成本的压缩损失信息更少），并尽可能少花 API 调用成本。

> 📚 **课程关联 · 操作系统 / 计算机体系结构**：这个成本梯度与 OS 课程中的**多级存储体系**（memory hierarchy）高度同构。CPU 缓存体系是 L1 → L2 → L3 → 主存 → 磁盘，访问速度递减、容量递增，系统优先使用速度最快的层级。Claude Code 的压缩梯度是 toolResultBudget → snip → microcompact → contextCollapse → autocompact → reactiveCompact，成本递增、压缩强度递增，系统优先使用成本最低的机制。两者的设计哲学完全一致：**用分层策略在速度（成本）和容量（压缩力度）之间取得最优平衡。** 这也与数据库课程中的"缓冲区置换策略"（buffer replacement policy）相呼应——不同的数据热度用不同的淘汰策略处理。

**代价：** 系统复杂度显著增加。六套机制之间有微妙的交互（比如，snip 在 autocompact 之前运行，它节省的 tokens 会影响 autocompact 是否触发；reactiveCompact 和 contextCollapse 都能处理 413，需要明确规定谁先谁后）。代码里有很多专门处理这些交互的注释，说明这些边界情况在实践中确实被踩过坑。

---

## 从这里能学到什么

**当系统面对一个"连续的约束违反场景"（这里是"上下文太长"），设计单一的应对机制几乎从不是最优解。**

更好的设计是把问题分解成不同的子场景，分析每个子场景的约束和成本，然后为每个子场景设计专门的解决方案。

这里的六套机制对应了六种不同的"上下文太长"场景：
- 单结果太大 → 截断
- 冗余重复 → 去重
- 历史无用 → 精准剪裁
- 整体偏大 → 渐进折叠
- 快到极限 → 主动压缩
- 已超极限 → 应急压缩

在软件设计中，"一套万能方案"的吸引力来自于简洁，但实际上往往是把复杂度转移到了单个方案内部（要么过于激进，要么过于保守）。正确的设计是识别真正的子问题，为每个子问题选择最合适的工具。

---

## 代码落点

- `src/query.ts`，`queryLoop()`：六套机制的调用顺序（第 380-544 行范围）
- `src/services/compact/autoCompact.ts`：`getAutoCompactThreshold()`，`AUTOCOMPACT_BUFFER_TOKENS = 13,000`
- `src/services/compact/compact.ts`：全文压缩实现（runForkedAgent 调用）
- `src/query.ts`，`isWithheld413` 处理：第 1062-1183 行，reactiveCompact 和 contextCollapse 的分工
- `src/query.ts`，max_output_tokens 恢复消息：第 1224-1229 行

---

## 还可以追问的方向

- autocompact 生成摘要时，使用的是什么提示词？它如何决定保留什么信息、压缩什么信息？（→ 参见 `services/compact/prompt.ts`）
- microcompact 的"缓存编辑"是如何工作的？Anthropic API 真的支持编辑 prompt cache 吗？
- contextCollapse 的"视图层和模型层分离"的 UI 是如何实现的？用户如何看到被折叠的历史？

---

