07 Context Compact

Context Compact --- 上下文总会满,要有办法腾地方

参考资料:https://github.com/shareAI-lab/learn-claude-code

核心思想:便宜的先跑,贵的后跑 --- 四层压缩策略,从零成本操作到按需调用LLM,再到紧急兜底。

问题背景

Agent在大型项目中工作时,会不断累积工具调用结果、文件内容、命令输出到messages列表中:

  • 读一个1000行文件 → ~4000 token
  • 读30个文件 + 跑20条命令 → 大量token累积
  • 上下文窗口有限 → API返回prompt_too_long

不压缩,Agent根本无法在大项目里持续工作。

四层压缩管线(执行顺序)

实际执行顺序是:budget → snip → micro → auto

层级 名称 触发方式 API成本 信息损失 作用
L3 tool_result_budget 每轮自动 0 中(落盘) 大工具结果落盘,留预览
L1 snip_compact 每轮自动 0 中(裁对话) 裁掉无关的旧对话中间部分
L2 micro_compact 每轮自动 0 低(占位) 旧工具结果替换为简短占位符
L4 compact_history 超阈值触发 1次API 高(摘要) LLM全量摘要压缩

L1: snip_compact --- 裁掉无关的旧对话

Agent跑了80轮对话,最早的"帮我创建hello.py"和当前工作几乎无关。

  • 消息数超过50条 → 保留头部3条 + 尾部47条
  • 特殊保护:不能把assistant(tool_use)和后面的user(tool_result)拆开
python 复制代码
def snip_compact(messages, max_messages=50):
    if len(messages) <= max_messages:
        return messages
    head_end, tail_start = 3, len(messages) - (max_messages - 3)
    # 保护 tool_use ↔ tool_result 完整性
    if _message_has_tool_use(messages[head_end - 1]):
        while head_end < len(messages) and _is_tool_result_message(messages[head_end]):
            head_end += 1
    # ... 插入占位符

L2: micro_compact --- 旧工具结果占位

Agent连续读了10个文件,第1-7次的完整内容早就不需要了。

  • 只保留最近3条 tool_result的完整内容
  • 更旧的替换为一行占位符:"[Earlier tool result compacted. Re-run if needed.]"
python 复制代码
KEEP_RECENT_TOOL_RESULTS = 3

def micro_compact(messages):
    tool_results = collect_tool_result_blocks(messages)
    if len(tool_results) <= KEEP_RECENT_TOOL_RESULTS:
        return messages
    for _, _, block in tool_results[:-KEEP_RECENT_TOOL_RESULTS]:
        if len(block.get("content", "")) > 120:
            block["content"] = "[Earlier tool result compacted. Re-run if needed.]"
    return messages

L3: tool_result_budget --- 大结果落盘

模型一次读了5个大文件,单条tool_result加起来500KB。

  • 统计最后一条user消息里所有tool_result总大小
  • 超过200KB → 按大小排序,从最大的开始落盘到.task_outputs/tool-results/
  • 上下文只留<persisted-output>标记 + 前2000字符预览
python 复制代码
def tool_result_budget(messages, max_bytes=200_000):
    # 统计最后一条user消息中所有tool_result
    # 超过阈值 → 落盘最大的,替换为预览

L4: compact_history --- LLM全量摘要

前三层跑完后token仍然超阈值 → 触发LLM摘要:

  1. 保存transcript :完整对话写入.transcripts/(JSONL格式)
  2. LLM生成摘要:保留当前目标、重要发现、已改文件、剩余工作、用户约束
  3. 替换消息列表:所有旧消息 → 一条摘要消息
python 复制代码
def compact_history(messages):
    transcript_path = write_transcript(messages)  # 先保存完整对话
    summary = summarize_history(messages)          # LLM 生成摘要
    return [{"role": "user",
             "content": f"[Compacted]\n\n{summary}"}]

熔断器:连续失败3次后停止重试,防止死循环浪费API调用。

应急: reactive_compact

当API返回prompt_too_long(413错误)时触发:

  • compact_history更激进
  • 从尾部回退,但仍要避免留下孤立的tool_result
  • 重试上限:默认1次
python 复制代码
def reactive_compact(messages):
    transcript = write_transcript(messages)
    summary = summarize_history(messages)
    tail_start = max(0, len(messages) - 5)
    # 保护 tool_result 完整性
    if _is_tool_result_message(messages[tail_start]) and _message_has_tool_use(messages[tail_start - 1]):
        tail_start -= 1
    return [{"role": "user",
             "content": f"[Reactive compact]\n\n{summary}"}, *messages[tail_start:]]

执行顺序(为什么不能换)

CC源码query.ts中的真实顺序:

  1. applyToolResultBudget(L3)--- 先处理大结果,确保完整内容落盘
  2. snipCompact(L1)--- 裁中间消息
  3. microcompact(L2)--- 旧结果占位
  4. contextCollapse(上下⽂折叠,由 Feature Flag控制的实验性可选功能):将已完成的⼯具调⽤结果折叠为⼀⾏摘要。⼀个返回了3000 ⾏⽂件内容的 read_file 调⽤,如果后续已经基于这个⽂件完成了编辑,就折叠为 已读取并编辑 config.yaml
  5. autoCompact(L4)--- LLM全量摘要

顺序不能换的原因 :L3(budget)必须在L2(micro)前面,因为micro_compact会把旧的tool_result替换成一行占位符,budget必须在那之前把完整内容落盘。

在源码中,Context Collapse 作为独⽴模块存放在src/services/contextCollapse/ ⽬录下(包含 index.ts 、 operations.ts 、 persist.ts 三个⽂件)。从 autoCompact.ts 中的引⽤关系可以看到,当 Context Collapse 功能启⽤时,它会取代⾃动压缩成为主要的上下⽂管理策略------在90% 上下⽂占⽤时开始提交折叠,95% 时阻⽌新的⼦ Agent ⽣成。这意味着 Context Collapse 的"精确⼿术⼑"优先于 AutoCompact 的"全⾯摘要",只有当 Collapse ⽆法处理时,才退回到传统的 LLM 摘要压缩。

⚖️ 核心区别对比

特性 Context Collapse (上下文折叠) MicroCompact (微压缩)
核心策略 折叠与投影:将早期对话"折叠"成摘要投影 清理与去重:移除冗余、过时的"垃圾"信息
操作对象 有价值的完整对话轮次 冗余的重复消息、旧工具结果
触发时机 上下文压力达到高水位(90%/95%)时 每一次 API 调用前都会执行
信息可逆性 可恢复。原始数据保留,只是创建了"投影" 不可逆。被识别为冗余的信息会被直接移除
主要目的 系统性管理上下文窗口,为重要信息腾出空间 通过低成本方式清除"垃圾",优化上下文质量

总结一下,MicroCompact 是流水线中靠前的、非常廉价的清理步骤;而 Context Collapse 则是更靠后、更具战略性的管理手段。当 Context Collapse 启用时,它会取代 AutoCompact 成为主要的上下文管理策略,体现了系统"先做便宜的事,再做昂贵的事"的设计哲学。

整体流程

python 复制代码
def agent_loop(messages):
    reactive_retries = 0
    while True:
        # 三个预处理器(0 API 调用)
        messages[:] = tool_result_budget(messages)    # L3: 大结果落盘
        messages[:] = snip_compact(messages)          # L1: 裁中间
        messages[:] = micro_compact(messages)         # L2: 旧结果占位

        # 还不够?LLM摘要(1 API 调用)
        if estimate_token_count(messages) > THRESHOLD:
            messages[:] = compact_history(messages)

        try:
            response = client.messages.create(...)
        except PromptTooLongError:
            if reactive_retries < MAX_REACTIVE_RETRIES:
                messages[:] = reactive_compact(messages)  # 应急
                reactive_retries += 1
                continue
            raise
        # ... 工具执行 ...

核心设计原则

  1. 便宜的先跑,贵的后跑:三层零API操作先执行,仍不够才调用LLM摘要
  2. 分层降级防御:从无损到有损再到紧急丢弃
  3. 保护消息完整性 :不能拆开tool_usetool_result配对
  4. 有损压缩不可逆:压缩时先保存transcript备份,但Agent不会主动翻旧档案

局限性

  1. 有损压缩不可逆:摘要质量取决于LLM判断,重要细节可能被丢弃
  2. 需要独立记忆系统补偿:压缩管的是"当前会话台面整洁度",跨会话长期知识由Memory系统负责