芥末
发布于 2026-05-18 / 0 阅读
0
0

Claude Code Auto-Compact 上下文压缩机制解析

AI Agent(智能体)和普通聊天机器人最大的区别之一,是它会持续使用工具、读取文件、修改代码、跑命令,并在多轮交互中维护任务状态。任务一长,上下文窗口很快就会被塞满。

Claude Code 的上下文管理不是简单删除旧消息,也不是每隔几轮粗暴做摘要,而是一套分层压缩机制。最重的一层叫 Auto-Compact:当上下文接近上限时,把历史对话重写成结构化摘要,再通过附件、缓存重载、系统提示重建等通道恢复关键状态。

理解这套机制,对设计自己的 Agent 很有帮助,因为它解决的不是“少花一点 token”这么简单的问题,而是如何让 Agent 在长任务中不丢目标、不丢进度、不重复踩坑。

上下文窗口是什么

大语言模型(Large Language Model,LLM)本身没有传统意义上的长期记忆。每次请求模型时,都需要把它当前要看的内容一起放进去,包括:

  • system prompt;
  • 工具列表和工具说明;
  • 用户消息;
  • assistant 历史回复;
  • tool use;
  • tool result;
  • 当前输入;
  • 项目记忆文件,例如 CLAUDE.md

这些内容加起来的长度不能超过模型限制,这个限制就是上下文窗口,通常用 token 计量。

flowchart LR
    A[system prompt] --> W[上下文窗口]
    B[工具描述] --> W
    C[历史对话] --> W
    D[工具调用结果] --> W
    E[当前用户输入] --> W
    W --> M[大语言模型]
    M --> R[生成回复]

可以把上下文窗口理解成模型的工作台。模型只能处理放在工作台上的材料,超过工作台大小的内容要么无法放入,要么必须被删除、压缩或改写。

为什么 Agent 更容易撑爆窗口

普通聊天通常是一问一答,每轮消息较短。Agent 不一样,它在执行任务时会不断产生额外上下文,尤其是工具调用结果。

固定开销很高

一个代码 Agent 启动后,还没真正开始干活,上下文里通常已经放入了不少内容:

内容作用token 压力
system prompt约束 Agent 行为较高
工具描述告诉模型有哪些工具、参数怎么传较高
CLAUDE.md项目规范、长期记忆、偏好配置视项目而定
权限配置限制哪些命令能执行较低到中等
MCP server 信息注入外部工具能力视工具数量而定

这些是“固定成本”。Agent 还没读取任何代码,上下文就已经占掉一部分。

工具调用会双倍记账

Agent 读取一个文件时,对话里通常会出现两类消息:

tool_use: Read("src/auth.ts")

tool_result: <src/auth.ts 的完整内容>

tool_use 要进上下文,tool_result 也要进上下文。真正占空间的是工具返回值,尤其是源码文件、日志、搜索结果、测试输出。

sequenceDiagram
    participant A as Agent
    participant T as Read 工具
    participant C as 上下文窗口

    A->>T: tool_use: Read("a.py")
    T-->>A: tool_result: 文件完整内容
    A->>C: tool_use 写入上下文
    A->>C: tool_result 写入上下文

一个中等大小的源码文件可能有几千 token,大文件可能超过一万 token。读几个文件、跑几次测试、grep 几轮结果,上下文很快就会膨胀。

长窗口不能彻底解决问题

模型厂商可以把窗口做大,从 200k token 扩到 1M、2M,确实能延缓问题,但不能从根上解决 Agent 的上下文管理。

主要原因有三点:

问题说明
成本增加输入 token 越多,请求成本越高
延迟增加上下文越长,首 token 延迟通常越明显
Lost in the Middle长上下文中间部分更容易被模型忽略

Lost in the Middle 指的是:模型对上下文开头和结尾的信息更敏感,对中间区域的信息关注较弱。即使窗口足够大,把所有历史一股脑塞进去,也不代表模型真的能稳定利用全部信息。

所以,Agent 需要主动管理信息结构,而不是单纯等待更大的上下文窗口。

常见上下文管理方案的问题

在 Claude Code 这种全量重写方案之前,很多 Agent 框架会使用三类办法:滑动窗口、定期摘要、向量召回。它们都有适用场景,但直接用于复杂 Agent 会遇到明显限制。

方案做法优点主要问题
滑动窗口删除最早的消息,只保留最近 N 轮实现简单、无额外模型调用可能删掉最初目标、约束和关键决策
定期摘要每隔 N 轮或 N token 做一次摘要比直接删除信息更安全触发机械,重要细节容易被压没
向量召回把历史切片入库,按相似度召回适合文档问答类 RAG会破坏时序,tool use 和 tool result 可能被拆散

Agent 上下文有很强的时间顺序和状态依赖。比如“先改 A,再跑测试,然后根据报错改 B”,这不是一堆可独立检索的知识片段,而是一条执行链。

向量召回更适合 RAG(Retrieval-Augmented Generation,检索增强生成)场景:从文档库找相关资料,再放入上下文辅助回答。Agent 的历史消息不只是资料,它还记录了任务状态、尝试路径、错误修复和用户中途修改的要求。只靠相似度召回,很容易漏掉低相似但高价值的信息。

Claude Code 的五层上下文压缩

Claude Code 没有把所有压力都交给 Auto-Compact,而是设计了一套从轻到重的五层机制。越底层成本越低,越上层改动越大。

flowchart BT
    L1[第 1 层:大工具结果落盘] --> L2[第 2 层:Snip 删除远古消息]
    L2 --> L3[第 3 层:Micro-Compact 时间衰减]
    L3 --> L4[第 4 层:Context Collapse 读时投影]
    L4 --> L5[第 5 层:Auto-Compact 全量摘要]
层级机制核心动作是否需要额外模型调用
第 1 层大结果落盘超大 tool result 写入磁盘,只留预览
第 2 层Snip删除较早消息,插入边界标记
第 3 层Micro-Compact清理可重新获取的旧工具结果
第 4 层Context Collapse调 API 时生成压缩视图,不改本地历史否或较低
第 5 层Auto-Compact全量重写历史,并恢复关键附件

这套分层设计的关键在于:能用便宜手段解决,就不急着做重压缩。大文件先落盘,旧工具结果先清掉,读时可以投影压缩视图,只有这些办法仍然挡不住窗口膨胀时,才触发 Auto-Compact。

Auto-Compact 的核心思想

Auto-Compact 做三件事:

  1. 接近窗口上限时触发:不是按固定轮数,而是按 token 上限预留固定缓冲。
  2. 重写整段历史:不是只压旧消息,也不是保留最近 N 轮,而是把整段对话重新生成结构化摘要。
  3. 关键状态分通道恢复:文件、任务、计划、记忆、系统配置,不全靠摘要保留。

它的设计重点不是“把文字变短”,而是把不同类型的信息放到不同通道里管理。

flowchart LR
    A[压缩前完整消息链] --> B[Micro-Compact 预处理]
    B --> C[摘要器生成结构化摘要]
    C --> D[清理缓存]
    D --> E[生成附件]
    E --> F[重组压缩后消息链]

    F --> F1[边界标记]
    F --> F2[摘要消息]
    F --> F3[文件/任务/计划附件]
    F --> F4[hook 结果]

语义信息适合摘要,例如用户目标、技术方案、已经完成的步骤、遇到的错误。状态信息不适合只靠摘要,例如最近读过的文件内容、异步子任务状态、当前计划文件。这些内容需要更精确的恢复方式。

什么时候触发 Auto-Compact

Claude Code 使用固定 token 缓冲,而不是百分比阈值。

相关实现可以抽象成这样:

export const AUTOCOMPACT_BUFFER_TOKENS = 13_000

export function getAutoCompactThreshold(model: string): number {
  const effectiveContextWindow = getEffectiveContextWindowSize(model)
  return effectiveContextWindow - AUTOCOMPACT_BUFFER_TOKENS
}

假设模型有效上下文窗口是 200k token,那么 Auto-Compact 的触发阈值大约是:

200000 - 13000 = 187000

也就是说,当当前上下文超过约 187k token,就会进入自动压缩流程。

固定缓冲比“80% 触发”更适合这种场景。摘要任务需要的输出预算并不会随着模型窗口线性增长。窗口从 200k 变成 1M,不代表摘要本身就需要多预留几十万 token。用固定缓冲能让触发行为更可预测。

手动压缩和自动压缩

Claude Code 也提供 /compact 手动入口。手动压缩和自动压缩底层会复用核心压缩能力,但行为不同。

模式触发方式是否支持自定义指令是否禁止后续提问
手动 /compact用户主动执行支持不强制
Auto-Compacttoken 接近上限不支持开启 suppressFollowUpQuestions

手动压缩时,用户可以告诉摘要器重点保留哪些信息。比如正在调一个认证 bug,可以要求压缩时重点记录认证流程、相关文件和已尝试的修复。

自动压缩通常发生在 Agent 正在执行任务的过程中。如果摘要里生成“请确认下一步做 A 还是 B”这类问题,就会打断任务流。所以自动压缩会开启 suppressFollowUpQuestions,避免摘要器额外抛出需要用户回答的问题。

熔断和递归保护

Auto-Compact 是一次额外模型调用。如果压缩连续失败还不断重试,可能导致费用异常上涨。因此实现里需要熔断机制:连续失败达到阈值后,停止继续自动压缩。

另一个必要保护是递归守卫。压缩过程本身也会调用模型生成摘要,如果这个摘要任务又触发 Auto-Compact,就可能进入循环。

简化后的判断类似这样:

if (querySource === 'session_memory' || querySource === 'compact') {
  return false
}

当请求来源是 compactsession_memory 时,直接禁止再次触发压缩。这个判断很短,但能避免压缩任务递归压缩自己。

Auto-Compact 压什么、留什么、丢什么

Auto-Compact 最反直觉的地方是:它不保留最近 N 轮原始对话,而是把历史消息整体送入摘要器,生成新的压缩后消息链。

压缩后的结构可以抽象成:

export function buildPostCompactMessages(result: CompactionResult): Message[] {
  return [
    result.boundaryMarker,
    ...result.summaryMessages,
    ...result.attachments,
    ...result.hookResults,
  ]
}

四段内容分别承担不同职责:

压缩后内容作用
boundary marker记录压缩边界、压缩方式、压缩前状态
summary messages存放结构化摘要
attachments恢复文件、计划、技能、异步任务等精确状态
hook results注入压缩过程中 hook 执行结果

原始历史消息会被替换掉。压缩后的对话不是“旧消息 + 摘要”,而是“边界 + 摘要 + 附件 + hook 结果”。

Micro-Compact 预处理

Auto-Compact 生成摘要前,会先让历史消息瘦身。工具调用结果往往最占空间,尤其是:

  • Read
  • Bash
  • Grep
  • Glob
  • WebFetch
  • WebSearch
  • Edit
  • Write

这些工具的结果很多是可重新获取的。比如文件内容可以再读,grep 结果可以再查,命令输出在必要时可以重跑。Micro-Compact 会清理这类可恢复内容,只保留元数据或占位信息。

这样做有两个好处:

  1. 摘要器不用处理过多低密度工具输出;
  2. 压缩请求本身更不容易超过上下文限制。

需要注意,Micro-Compact 不会随便清掉不可重复状态。比如异步子任务的输出、任务运行状态等内容,如果丢了就无法恢复,必须走更谨慎的通道。

文件恢复策略:5 个文件、每个 5k、总预算 50k

工具结果被清理后,Agent 仍然需要知道最近正在处理哪些文件。Claude Code 会在压缩后恢复一部分最近活跃文件。

相关常量可以概括为:

export const POST_COMPACT_MAX_TOKENS_PER_FILE = 5_000
export const POST_COMPACT_TOKEN_BUDGET = 50_000
export const POST_COMPACT_MAX_FILES_TO_RESTORE = 5

含义如下:

参数含义
POST_COMPACT_MAX_FILES_TO_RESTORE最多恢复 5 个文件
POST_COMPACT_MAX_TOKENS_PER_FILE每个文件最多注入 5k token
POST_COMPACT_TOKEN_BUDGET压缩后附件总预算控制在 50k token 内

文件选择按最近活跃度排序,最近被 Read 过的文件优先恢复。这样可以把“当前正在改的代码”重新放回模型视野里,而不是完全依赖摘要器用自然语言描述文件内容。

CLAUDE.md 为什么不进摘要

CLAUDE.md 属于项目记忆或用户上下文,它通常记录编码规范、项目约定、长期偏好等信息。这类内容不是某一次对话的临时状态,而是每轮对话都应该自动加载的长期上下文。

Claude Code 不把 CLAUDE.md 塞进压缩后的摘要里,而是清空相关用户上下文缓存,例如 getUserContext。下一轮请求开始时,缓存为空,系统会重新从磁盘加载 CLAUDE.md

flowchart LR
    A[Auto-Compact] --> B[清空 getUserContext 缓存]
    B --> C[下一轮请求开始]
    C --> D[重新读取 CLAUDE.md]
    D --> E[注入新的用户上下文]

这种做法把长期记忆和对话摘要分开管理。长期记忆不占摘要空间,也不会因为摘要器遗漏而失效。

system prompt 和异步任务怎么处理

system prompt 不参与压缩。压缩完成后,会重新构建有效 system prompt,把最新工具列表、权限设置、MCP(Model Context Protocol,模型上下文协议)服务等信息注入进去。

这样做可以避免一个问题:对话中途工具或权限发生变化,压缩后 Agent 仍然拿着旧操作手册工作。

异步任务状态则走附件通道。例如主 Agent 派出子 Agent 去跑测试、查文档或分析代码,压缩时必须保留这些任务的状态:

异步任务状态压缩后处理
正在运行作为附件恢复,主 Agent 继续等待或查询
已完成注入结果摘要或结果位置
失败注入错误状态,避免主 Agent 误判任务仍可用

这类信息不能只靠自然语言摘要,因为它关系到调度状态。

摘要 prompt 的结构

Auto-Compact 的摘要不是简单一句“请总结对话”。为了让压缩结果可接续,摘要 prompt 会对输出格式和内容清单做强约束。

禁止工具调用

摘要器的任务只是总结历史,不能继续执行工具。prompt 会明确要求只输出纯文本,不允许调用 ReadBashGrepGlobEditWrite 等任何工具。

可以抽象成这样的约束:

CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.
- Do NOT use Read, Bash, Grep, Glob, Edit, Write, or ANY other tool.
- Tool calls will be REJECTED and will waste your only turn.
- Your entire response must be plain text.

这是必要的防呆设计。因为模型在看到历史里出现文件、命令、错误时,可能会尝试继续操作;但摘要阶段一旦调用工具,就偏离了“压缩历史”的目标。

XML 输出结构

摘要输出大致分成两个块:

<analysis>
用于整理哪些信息重要的中间分析区
</analysis>

<summary>
真正进入压缩后消息链的结构化摘要
</summary>

analysis 块用于帮助模型组织信息,最终不会进入压缩后的对话。summary 块才是压缩后保留的内容。

9 个固定摘要章节

摘要必须覆盖 9 类信息:

序号章节作用
1Primary Request and Intent用户主要目标和真实意图
2Key Technical Concepts涉及的关键技术概念
3Files and Code Sections文件、函数、代码段和修改点
4Errors and fixes遇到的错误和对应修复
5Problem Solving已解决的问题和推理路径
6All user messages所有用户消息
7Pending Tasks尚未完成的任务
8Current Work当前正在做的具体工作
9Optional Next Step合理的下一步

其中第 6 项和第 8 项尤其关键。

All user messages 要求枚举所有非工具结果的用户消息。用户可能在对话中途改变需求、加入限制、废弃某个方案,这些信息一旦漏掉,Agent 后续行为就会偏离目标。

Current Work 要求描述当前工作到非常细的颗粒度。不是“正在调试登录功能”,而应该接近:

正在调试 auth.ts 中 refreshToken 函数的 token 续期逻辑;
已经确认 cookie 过期判断存在问题;
下一步准备修改 isExpired 判断并重新运行 auth.test.ts。

压缩后 Agent 接续工作的关键,就是能立刻知道“刚才做到哪一步”。

为什么摘要使用当前对话同一个模型

一种直觉做法是:摘要任务用更便宜的小模型完成。但 Claude Code 选择使用当前对话的同一个模型。

原因有两个:

原因说明
摘要质量更重要摘要丢信息会直接影响后续任务执行
Prompt Cache 可复用同模型更容易复用已有 system prompt、工具描述等缓存前缀

Prompt Cache 是大模型 API 常见能力:如果多次请求的前缀相同,服务端可以复用缓存,降低成本和延迟。摘要换成小模型未必真的省钱,因为压缩质量下降后,Agent 可能需要更多轮重新探索上下文。

压缩后的接续流程

Auto-Compact 完成后,需要把新消息链接回会话,让 Agent 不会误以为自己从零开始。

整体流程可以表示为:

flowchart TD
    A[当前消息链接近窗口上限] --> B[触发 Auto-Compact]
    B --> C[Micro-Compact 清理可恢复工具结果]
    C --> D[调用摘要器生成 summary]
    D --> E[清空文件状态和用户上下文缓存]
    E --> F[并发生成附件]
    F --> G[组装 boundary + summary + attachments + hook results]
    G --> H[替换旧消息链]
    H --> I[下一轮继续执行任务]

压缩后摘要通常会带有类似这样的语义包装:

本会话是从一次因上下文接近上限而压缩的历史会话延续而来。
以下内容概述了之前的目标、进度、文件状态、错误修复和下一步工作。

这句话告诉模型:当前不是新会话,而是接着之前的任务继续做。配合 Current WorkPending Tasks,Agent 才能从压缩点继续向前推进。

旧消息链在会话里通常会被新消息链替换掉。如果启用了 transcript 备份机制,历史可能会写入本地记录文件,供需要时回查;但从当前上下文角度看,旧消息已经不再直接参与后续推理。

这套机制适合借鉴到哪些 Agent

Claude Code 的设计可以抽象成一组通用原则,不只适用于代码 Agent。

原则适合解决的问题
大结果外置日志、文件、搜索结果太大
可恢复结果清理工具输出可重新读取或重新执行
语义信息摘要用户目标、技术方案、错误教训需要保留
精确状态附件化文件内容、任务状态、计划不能被摘要模糊化
长期记忆独立加载项目规则、用户偏好不该混进对话摘要
自动流程加熔断防止压缩失败后无限重试
自动压缩禁止提问避免长任务被压缩流程打断

如果要给自己的 Agent 实现上下文管理,可以从一个简化版开始:

flowchart LR
    A[统计 token] --> B{是否接近阈值}
    B -- 否 --> C[正常调用模型]
    B -- 是 --> D[清理可恢复工具结果]
    D --> E[生成结构化摘要]
    E --> F[恢复最近文件/任务状态]
    F --> G[重组消息链]
    G --> C

关键不是照搬参数,而是先定义清楚哪些信息必须保留:

信息类型推荐处理方式
用户目标和约束结构化摘要
中途需求变更枚举保留
当前正在修改的文件附件恢复
测试失败原因摘要中单独记录
子任务状态附件恢复
项目长期规则独立记忆系统加载
大文件内容落盘,需要时再读

用一句话概括 Claude Code 的上下文管理

Claude Code 的上下文压缩不是“删掉旧内容省 token”,而是把上下文拆成不同生命周期的信息:语义进摘要,状态走附件,长期记忆重新加载,系统能力重新构建,可恢复工具结果清理或落盘。

上下文窗口即使继续变大,这种主动管理仍然必要。窗口变大只能减少触发压缩的频率,不能消除 Lost in the Middle,也不能保证模型稳定利用所有历史细节。对复杂 Agent 来说,真正重要的是让模型始终看到结构清晰、当前有效、可继续执行的任务状态。


评论