Claude Code 为什么不会撑爆上下文?三层历史视图讲清楚压缩与恢复
Claude Code 连续工作几个小时不爆 context,不是因为「记性好」,是因为 它同时维护三个版本的历史,每次只把最该看的那一份喂给模 型。
- 核心观点:成熟的 Agent 不止有「聊天记录」,而是把 UI 历史 / 接口视图 / 磁盘会话链 分成三层。
- UI 历史:用户在终端看到的精简版,只保留关键节点。
- 接口视图:实际发给模型的那一份,叠加了 Prompt Cache 与压缩投影。
- 磁盘会话链:完整事实记录,永远不修改,恢复靠「投影」而不是「改写」。
- Resume 不是回放:恢复会话 = 在磁盘链上选投影策略重新生成接口视图。
- 压缩的本质:从无损丢消息升级为「按当前任务相关性重新切片」。
一个常见的误解:「上下文管理 = 删除旧消息」
很多人以为 Claude Code 处理 context overflow 的方式,就是「聊到一定长度就删前面」。这个理解是错的。
真实情况是:Claude Code 在任何一个时刻都同时持有三种历史,三者各司其职:
| 视图 | 谁能看到 | 作用 |
|---|---|---|
| UI 历史 | 用户在终端看到 | 人类可读的「发生了什么」简报 |
| 接口视图 | 模型 API 真正收到 | 喂给 LLM 的 token 流,决定了模型「知道什么」 |
| 磁盘会话链 | 只有 Agent 自己能访问 | 完整事实记录,永远不修改 |
这三层之间的关系,恰恰是 Claude Code「不爆 context」的关键。
每一层都从「事实层」派生,而不是互相复制粘贴。
接口视图:Prompt Cache 如何影响上下文设计
发给模型的接口视图不是「UI 历史 + 系统提示」的简单拼接。它要解决两个工程问题:
1. Token 经济性:不能让模型每次都从头读 100k token,但又要让它「记得」前面的关键事实。
2. Cache 命中率:Anthropic API 的 Prompt Cache 按前缀匹配收费,前缀越稳定,缓存命中率越高,调用越便宜。
Claude Code 的做法是 把稳定的部分(system prompt + 项目约定 + 长篇背景文档)放在前面,把易变部分(最近 N 轮对话)放在后面。这样每次新增对话时,前缀几乎不变,cache 命中。
[稳定前缀:缓存命中]
system prompt
CLAUDE.md / AGENTS.md
skills / MCP 工具定义
历史摘要(compressed summary)
[易变后缀:每轮重建]
当前用户 query
最近 N 轮消息
工具调用结果
压缩发生在哪里?发生在「历史摘要」这一段。当累计对话超过阈值,Agent 把旧的若干轮对话 压缩成一段总结,塞到稳定前缀里。这样:
- 接口视图大小可控
- 前缀尽量稳定 → Prompt Cache 高命中
- 关键事实不丢(被压缩进 summary)
为什么压缩不是「简单删除」
简单删除的硬伤是 事实丢失。比如你让 Claude Code 重构一个模块,中途改了 5 次类名,第 3 次改错了又回滚。简单删除会让模型忘掉「为什么最终选择 A 而不是 B」,下次再问就重新踩坑。
Claude Code 的压缩策略是 生成结构化 summary,而不是删条目。Summary 至少包含:
- 用户最初的目标(为什么要做这件事)
- 已尝试过的方案 + 失败原因
- 最终采用的方案 + 关键决策
- 当前进度 + 待办事项
这种 summary 本身就是「 事实」,可以无损(或近似无损)喂回模型。
压缩 ≠ 丢消息,而是把冗余的「对话过程」压缩成「决策结果」。模型下次看到 summary 就够了,不需要看到 50 轮原始对话。
磁盘会话链:为什么「永远不修改」
这是最反直觉的部分:磁盘上保存的不是「当前会话状态」,而是「会话的事件流」。
每一条用户输入、工具调用、模型响应、错误重试,都作为独立事件追加到一个不可变日志里。这种设计的优势:
| 优势 | 说明 |
|---|---|
| 可重放 | 从头按顺序 replay 事件,能精确还原任何时刻的接口视图 |
| 可分叉 | 在第 N 个事件后选择不同的投影策略,就能产生「如果当时压缩得更狠会怎样」的对照实验 |
| 可审计 | 出问题时能完整回溯 Agent 的每一步决策 |
| 无锁并发 | 不可变 = 追加 only,多个 reader 可以同时访问而不冲突 |
类比 Git:磁盘会话链就是 repo,接口视图是某个 working tree,UI 历史是 git log --oneline。三者共享同一个事实层(commit graph),各自呈现不同密度。
Resume 的真相:「投影」而非「回放」
当你输入 /resume 恢复一个旧会话时,Claude Code 不是把磁盘上的所有事件 重新发给模型(那会爆 context),也不是简单 reload UI 历史。
它是做这样的事:
- 读取磁盘会话链(完整事件流)
- 按当前任务相关性,选择一种「投影策略」:
- 完整回放前 K 轮
- 加载压缩 summary + 最近 N 轮
- 仅加载 summary + 当前 query
- 生成新的接口视图,发给模型
- 更新 UI 历史,给用户看精简版
Resume 后的第一轮 query,模型已经「知道」之前发生过什么——但它看到的是 根据当前任务重新切片过的版本,不是原始数据流。
这就是为什么同一个会话 /resume 多次、间隔几天再开,模型依然能保持上下文连续性:因为事实层没动过,变的只是投影角度。
三层视图的工程价值
把 Agent 上下文拆成三层视图,最大的好处是 关注点分离:
- 磁盘会话链:工程团队优化存储、压缩、索引策略
- 接口视图:模型团队优化 Prompt Cache、token 利用率、压缩算法
- UI 历史:设计团队优化人机交互、信息密度、可读性
三层之间通过「投影」解耦,任一层升级都不影响另外两层。这是 Claude Code 能扛住「连续工作几个小时不爆」的根本原因。
启示:写自己的 Agent 时怎么借鉴
如果你在自建 Agent,复用这套三层架构:
- 不要把 UI 历史直接喂给模型——人类可读 ≠ 模型需要看
- 压缩时保留「决策而不只是事件」——
因为 X 选了 Y,因为 Y 失败了回退到 Z - 磁盘只追加事件流,不做原地修改——重放、分叉、审计全靠它
- Resume 是投影函数,不是一次性回放——按当前任务相关性重新切片
把这四点做到位,context overflow 基本不再是工程问题。
References
- Claude Code 为什么不会撑爆上下文?深入解析 Agent 上下文压缩与会话恢复机制 —— 唐国梁Tommy, 哔哩哔哩, 2026-08-02