Skip to main content

Claude Code 为什么不会撑爆上下文?三层历史视图讲清楚压缩与恢复

· 6 min read

Claude Code 连续工作几个小时不爆 context,不是因为「记性好」,是因为 它同时维护三个版本的历史,每次只把最该看的那一份喂给模型。

  1. 核心观点:成熟的 Agent 不止有「聊天记录」,而是把 UI 历史 / 接口视图 / 磁盘会话链 分成三层
  2. UI 历史:用户在终端看到的精简版,只保留关键节点。
  3. 接口视图:实际发给模型的那一份,叠加了 Prompt Cache 与压缩投影。
  4. 磁盘会话链:完整事实记录,永远不修改,恢复靠「投影」而不是「改写」。
  5. Resume 不是回放:恢复会话 = 在磁盘链上选投影策略重新生成接口视图。
  6. 压缩的本质:从无损丢消息升级为「按当前任务相关性重新切片」。

一个常见的误解:「上下文管理 = 删除旧消息」

很多人以为 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 历史。

它是做这样的事:

  1. 读取磁盘会话链(完整事件流)
  2. 按当前任务相关性,选择一种「投影策略」:
    • 完整回放前 K 轮
    • 加载压缩 summary + 最近 N 轮
    • 仅加载 summary + 当前 query
  3. 生成新的接口视图,发给模型
  4. 更新 UI 历史,给用户看精简版

Resume 后的第一轮 query,模型已经「知道」之前发生过什么——但它看到的是 根据当前任务重新切片过的版本,不是原始数据流。

这就是为什么同一个会话 /resume 多次、间隔几天再开,模型依然能保持上下文连续性:因为事实层没动过,变的只是投影角度。

三层视图的工程价值

把 Agent 上下文拆成三层视图,最大的好处是 关注点分离

  • 磁盘会话链:工程团队优化存储、压缩、索引策略
  • 接口视图:模型团队优化 Prompt Cache、token 利用率、压缩算法
  • UI 历史:设计团队优化人机交互、信息密度、可读性

三层之间通过「投影」解耦,任一层升级都不影响另外两层。这是 Claude Code 能扛住「连续工作几个小时不爆」的根本原因。

启示:写自己的 Agent 时怎么借鉴

如果你在自建 Agent,复用这套三层架构:

  1. 不要把 UI 历史直接喂给模型——人类可读 ≠ 模型需要看
  2. 压缩时保留「决策而不只是事件」——因为 X 选了 Y,因为 Y 失败了回退到 Z
  3. 磁盘只追加事件流,不做原地修改——重放、分叉、审计全靠它
  4. Resume 是投影函数,不是一次性回放——按当前任务相关性重新切片

把这四点做到位,context overflow 基本不再是工程问题。

References

  1. Claude Code 为什么不会撑爆上下文?深入解析 Agent 上下文压缩与会话恢复机制 —— 唐国梁Tommy, 哔哩哔哩, 2026-08-02