/compact 实现原理与使用指南

·7 min read·随笔·--
SummaryAI

/compact 功能通过智能压缩对话历史来突破上下文窗口限制,是维持长会话流畅性的关键技术。其实现包含三条按优先级尝试的压缩路径,兼顾效率与完整性;摘要生成采用结构化的九章节Prompt,确保信息不丢失;自动触发机制结合缓冲区设计,在资源耗尽前主动干预,设计颇为精巧。

一、什么是 /compact

/compact 将当前对话历史压缩为一段结构化摘要,释放上下文窗口空间,让长会话可以持续进行。

核心机制:用 LLM 对对话历史生成摘要 → 替换原始消息 → 重新注入系统上下文(CLAUDE.md 等)。

二、关键常量

常量含义
MAX_OUTPUT_TOKENS_FOR_SUMMARY20,000摘要最大输出 token
AUTOCOMPACT_BUFFER_TOKENS13,000自动压缩缓冲区
MANUAL_COMPACT_BUFFER_TOKENS3,000手动压缩缓冲区
MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES3熔断器:连续失败 3 次后停止自动压缩
POST_COMPACT_MAX_FILES_TO_RESTORE5压缩后最多恢复 5 个文件内容
POST_COMPACT_TOKEN_BUDGET50,000压缩后附件总 token 预算

三、三条压缩路径

/compact 命令不是单一实现,而是有三条路径按优先级依次尝试:

路径 1:Session Memory Compact(快速路径)

条件:Session Memory 功能开启 + 无自定义指令 + 存在有效 session memory

原理

  • 不调用 LLM 重新总结
  • 找到上次摘要点(lastSummarizedMessageId),保留最近的消息
  • 消息保留策略(默认配置):
Plain Text
minTokens = 10,000    → 至少保留 10K token 的近期消息
minTextBlockMessages = 5  → 至少保留 5 条含文本的消息
maxTokens = 40,000    → 最多保留 40K token
  • 会保护 tool_use / tool_result 配对完整性(不会拆散一对)

特点:速度最快,不耗额外 token,适合日常自动压缩。

路径 2:Reactive Compact

条件:特定功能标志开启时使用

原理:部分消息压缩,只压缩旧消息,保留最近的对话。分两个方向:

  • from:从某个切分点到末尾的消息生成摘要
  • up_to:对切分点之前的消息生成摘要

路径 3:Traditional Compact(兜底路径)

条件:前两条路径不可用或失败时使用

原理:对整个对话历史调用 LLM 生成结构化摘要。这是最完整但最慢的路径。

四、摘要生成的 Prompt 结构

Traditional compact 使用的 prompt 要求 LLM 生成 9 个章节的结构化摘要:

Plain Text
<analysis>    ← 分析草稿(会被丢弃,不进入最终摘要)
...
</analysis>
<summary>     ← 最终摘要(保留)
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                ← 待完成的任务
8. Current Work                 ← 压缩前正在进行的工作
9. Optional Next Step           ← 建议的下一步
</summary>

关键安全措施:prompt 前后都有强调 "不要调用任何工具,只输出纯文本",防止 LLM 在压缩过程中执行工具。

五、自动压缩触发机制

5.1 阈值计算

Plain Text
有效上下文窗口 = 模型上下文窗口 - 20,000(摘要输出预留)
自动压缩阈值 = 有效上下文窗口 - 13,000(缓冲区)

示例(200K 上下文模型):

Plain Text
有效窗口 = 200,000 - 20,000 = 180,000
自动压缩在 180,000 - 13,000 = 167,000 token 时触发

5.2 触发条件

每次查询后检查:

Plain Text
shouldAutoCompact() 依次检查:
  ✓ 自动压缩是否启用?(环境变量 + 用户配置)
  ✓ 当前请求来源是否允许?(排除 compact 自身等)
  ✓ 当前 token 数 ≥ 阈值?
  → 全部满足则触发

5.3 熔断器

连续 3 次自动压缩失败后,熔断器打开,停止尝试。成功一次则重置计数。

5.4 环境变量覆盖

Bash
CLAUDE_CODE_AUTO_COMPACT_WINDOW=100000   # 覆盖上下文窗口大小
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=80       # 按百分比触发(0-100)

六、压缩后发生什么

6.1 消息替换

原始对话消息被替换为一条 user 消息:

Plain Text
"This session is being continued from a previous conversation.
Here is a summary of the conversation so far:

{摘要内容}

Recent messages are preserved verbatim."

6.2 上下文重新注入

压缩后重新加载(完全存活):

  • ✅ 所有 CLAUDE.md 文件(从磁盘重新读取)
  • ✅ .claude/rules/*.md
  • ✅ @include 引用的文件
  • ✅ Auto memory(MEMORY.md)
  • ✅ 激活的 skill 内容

6.3 文件内容恢复

压缩后会尝试恢复关键文件内容:

  • 最多恢复 5 个文件
  • 总 token 预算 50,000
  • 单文件最多 5,000 token
  • Skill 文件预算 25,000 token

6.4 缓存清理

runPostCompactCleanup() 清理以下缓存:

  • 用户上下文缓存
  • Memory 文件缓存(触发重新加载)
  • 系统提示词缓存
  • 权限审批缓存
  • 会话消息缓存

6.5 Hook 触发

Plain Text
PreCompact  → 压缩前触发(matcher: "manual" 或 "auto")
PostCompact → 压缩后触发(stdin 包含摘要内容)
SessionStart → 压缩后也会触发(等同于新会话开始)

七、手动 vs 自动压缩对比

特性手动 /compact自动压缩
触发方式用户输入 /compacttoken 超过阈值时自动
自定义指令支持 /compact 保留 xxx 信息不支持
缓冲区3,000 token13,000 token
Session Memory 路径有自定义指令时跳过优先使用
熔断器连续 3 次失败后停止

八、使用指南

8.1 基本用法

Bash
# 直接压缩(无自定义指令)
/compact

# 带自定义指令(告诉 LLM 压缩时保留什么)
/compact 保留所有关于数据库迁移的讨论细节

/compact Keep all code snippets and file paths

8.2 什么时候手动 /compact

  1. 上下文快满时 — 看到黄色/红色进度条
  2. 话题转换时 — 从 A 功能切到 B 功能前先压缩
  3. Claude 开始"忘记"早期指令时 — 上下文过长导致注意力稀释
  4. 长时间调试结束后 — 大量试错信息可以压缩掉

8.3 什么时候不需要手动 /compact

  1. 对话刚开始 — 消息太少,会报 "Not enough messages to compact"
  2. 自动压缩已经在工作 — 默认开启,通常不需要手动干预
  3. 关键代码还在讨论中 — 压缩可能丢失细节,先完成当前任务再压缩

8.4 自定义指令的最佳实践

Bash
# ✅ 好的自定义指令 — 具体、有重点
/compact 保留:1) 所有修改过的文件路径 2) 数据库 schema 变更 3) 当前未完成的 TODO

# ✅ 好的自定义指令 — 保护关键上下文
/compact Preserve the API endpoint mapping and all error handling patterns we discussed

# ❌ 不好的 — 太笼统
/compact 保留所有内容

8.5 配合 Hook 使用

压缩前提醒保留重点

JSON
{
  "hooks": {
    "PreCompact": [{
      "hooks": [{
        "type": "command",
        "command": "echo '{\"systemMessage\": \"⚠️ 即将压缩对话,关键信息请确认已记录到 CLAUDE.md\"}'"
      }]
    }]
  }
}

压缩后记录摘要

JSON
{
  "hooks": {
    "PostCompact": [{
      "hooks": [{
        "type": "command",
        "command": "jq -r '.summary // empty' >> .claude/compact-log.txt"
      }]
    }]
  }
}

8.6 长会话生存策略

核心原则:CLAUDE.md 是持久文件,不要把会话临时信息往里塞,否则会快速膨胀超过 200 行上限。

信息保留应分层处理:

信息类型保留方式示例
长期架构决策写入 CLAUDE.md / rules"API 统一用 RESTful,不用 GraphQL"
会话学到的 pattern依赖 Auto Memory(自动)Claude 自行判断是否记录到 ~/.claude/projects/<project>/memory/
本次压缩需保留的上下文/compact 保留xxx 自定义指令/compact 保留当前数据库迁移的进度和方案
调试过程、试错记录不需要保留,压缩释放空间反复 debug 的堆栈信息

具体策略:

  1. 只把真正长期有效的决策写入 CLAUDE.md — 如编码规范、架构选型,而非会话临时状态
  2. 会话临时上下文用 /compact 自定义指令保留 — 只影响本次压缩摘要,不污染持久文件
  3. 信任 Auto Memory — Claude 会自动将有价值的 pattern 写入 MEMORY.md,无需手动干预
  4. 阶段性任务完成后主动 /compact — 清理无用上下文
  5. 话题切换前压缩/compact 保留当前进度,然后开始新话题

8.7 常见问题排查

问题原因解决
"Not enough messages to compact"消息太少继续对话,积累更多消息
"Conversation too long"单条消息/对话已超长按 Esc 两次回退几条消息再试
压缩后丢失关键信息摘要未覆盖到使用自定义指令指定保留内容
压缩后行为改变CLAUDE.md 冲突或模糊检查 /memory,确认指令文件加载正确
自动压缩不触发熔断器打开或功能禁用检查环境变量,手动 /compact 重置

九、核心源码文件索引

文件职责
src/commands/compact/compact.ts命令入口,路由三条路径
src/services/compact/compact.ts核心压缩逻辑(流式处理、重试、附件恢复)
src/services/compact/sessionMemoryCompact.tsSession Memory 快速压缩路径
src/services/compact/autoCompact.ts自动压缩触发、阈值计算、熔断器
src/services/compact/prompt.ts三种 prompt 变体 + 格式化函数
src/services/compact/postCompactCleanup.ts压缩后缓存清理

十、一句话总结

/compact 有三条路径(Session Memory → Reactive → Traditional),自动压缩在约 83% 上下文使用率时触发。CLAUDE.md 完整存活压缩(从磁盘重新读取),对话记忆被结构化摘要替代。长期决策写入 CLAUDE.md/rules,会话临时上下文用 /compact 保留xxx 自定义指令保留,日常 pattern 交给 Auto Memory 自动记录——不要把临时信息塞进 CLAUDE.md 导致膨胀

评论