好的,这是根据您的要求翻译成的简体中文版本。
以后,我创建了一个插件,可以自动包含以下内容,并透明地增强紧凑功能。
https://x.com/u1/status/2073742408555343968
Claude Code 的 Compact 实际工作原理
作为前提,Claude Code 的 compact 通过两条路径运行:
- 手动 compact (/compact):由用户显式触发。可以在任何时机执行。
- 自动 compact:当会话上下文使用量接近限制时自动运行(实践中通常在 90-95% 左右)。它不会在用户期望的确切时刻运行,而是在某个轮次边界上,当你突然意识到上下文已经被压缩时发生。
在两条路径中,内容是一样的:将之前的对话历史发送给大语言模型,以创建一个自然语言摘要,然后将摘要、最近的轮次和系统提示词重构为一个新的上下文。摘要变成了一段关于“做了什么”的叙事性描述,因此,“为什么做出那个选择”、“哪些方案被拒绝了”以及“我们当前处于哪个阶段”等决策结构会被削弱。此外,一旦压缩运行,这是一个不可逆的操作;你无法返回到压缩前的原始日志。
从钩子的角度来看,压缩可以通过 PostCompact 事件捕获。但是,PostCompact 钩子无法返回 additionalContext,因此没有路径可以在压缩后立即向 Agent 直接注入指令。要插入指令,必须通过随后的 UserPromptSubmit 事件。这个约束决定了本文中的钩子设计。
标准 Compact 的问题
Claude Code 的 /compact 会总结对话历史,并将结果作为新会话的初始上下文。虽然摘要本身是合理的,但它是一份“过去的工作日志”,而不是“接下来要做什么的指令”。因此,压缩后的 Agent 容易出现以下误解:
- 混淆了“工作指令”和“工作日志”的界限。如果摘要提到“考虑了方案 A”,Agent 可能会开始实施方案 A,而不是实际采用的方案 B。
- 混淆了验证阶段和实施阶段。像“部署前在实际机器上验证”这样的前提可能从摘要中消失,Agent 可能会在下一轮次中直接进行破坏性的部署操作。
- 重新提出已被否决的错误方案。摘要可能记录某个方案被“尝试过”,但没有记录“被拒绝的原因”。
- 丢失“会话状态”,例如计划模式、工作者委派或任务树。Agent 可能在退出计划模式后继续工作,或者忘记了在 tmux 中运行的工作者,并开始自己动手工作。
实际发生过哪些事故
回顾过去一周的会话,我观察到至少在 compact 后发生了四类模式。它们似乎都源于未能区分“工作指令”和“工作日志”:
- 先验证后部署的流程被破坏:尽管同意“在放置文件前先在机器上验证行为”,但压缩后 Agent 只看到了摘要中的完成状态,跳过了验证,并直接覆盖了目标位置。
- 重新执行被否决的捷径:由于摘要将一次失败的捷径尝试记录为“已尝试过的步骤”,Agent 再次执行它,并再次陷入相同的失败。
- 忘记了设计原则:在会话中确立的原则,如“编辑源代码仓库而不是目标目录”,从摘要中丢失,导致 Agent 进行了一些短期内有效但无法复现的直接编辑。
- 误解了任务目标:压缩前某个测试的具体目的丢失了,导致 Agent 偏离到外围调查,并开始处理一个完全不同的问题。
这些情况在同一会话中多次发生,每次 compact 后都出现不同类型的误解。我认为这是标准 compact 规范的结构性缺陷,而非孤立的事故。
我构建的方案
(1) compact-prep 技能
一个作为斜杠命令实现的技能,用户在输入 /compact 之前显式执行它。它会提取那些可能无法放入摘要的“决策结构”和“会话状态”,并以特定格式保存到固定路径 (${TMPDIR}/claude-compact-state/<session_id>.md) 的状态文件中。
保存的内容包括那些在摘要中难以捕获的信息:
- 活跃计划(计划文件路径和当前阶段)
- 任务列表摘要(进行中的任务和补充说明)
- 会话决策(已采纳的方案 / 被拒绝的方案 / 拒绝原因)
- 约束和阻碍项
- 工作者拓扑(tmux-bridge 面板 / 角色 / 分配)
- 编辑中的文件(关于未保存或未验证更改的说明)
- 恢复笔记(给压缩后的自己的一封信)
关键设计点是“机械式强制执行”。它使用硬性门控,在找不到 session_id 时阻止文件创建;使用固定标题作为强制手段来检测遗漏;并限制允许使用的工具以防止状态损坏。
(2) 两阶段钩子:PostCompact + UserPromptSubmit
即使 (1) 写入了状态文件,如果压缩后的 Agent 不读取它,那也毫无意义。由于 PostCompact 无法返回 additionalContext,我使用了一个两阶段配置:
- PostCompact 钩子写入一个包含 session_id 的标记文件(不包含指令,只记录压缩已发生)。
- 当下一个 UserPromptSubmit 钩子检测到该标记时,它通过 additionalContext 注入指令:“读取计划文件 / 读取状态文件 / 检查任务列表 / 将摘要的后续步骤视为假设 / 如果计划模式被停用,则确认重新进入”,然后删除标记。
由于 Claude Code 的钩子之间缺乏共享状态机制,文件系统上的标记是唯一的通信路径。这使得每个钩子都能保持精简,职责单一。在正常的轮次中,开销仅是一次 test -f 检查。
(3) 60% 通知(用于避免自动 compact)
前面的工具假设用户手动运行 /compact-prep → /compact。然而,自动 compact 会在毫无预警的情况下运行,绕过了这个流程。如果自动 compact 先触发,状态文件不会被保存,原始日志被压缩成摘要,从而丢失了恢复材料。
解决方法是创建一种状态,让用户可以在自动 compact 触发前手动运行预备步骤。这就是 60% 通知的目的。
为什么是 60%?
- 足够的提前量:自动 compact 在 90-95% 左右触发。60% 提供了 30% 的缓冲,确保用户在下一轮触发自动 compact 之前看到警告。
- 有时间完成工作:在 60% 时,仍有 30% 的容量来完成一个子任务,然后再保存状态。
- 机械式触发器:当你过于专注而忽略了上下文消耗时,它能起到强制作用。
- 100 万上下文假设:对于 20 万上下文,60% 太小了。我使用的是 100 万上下文(Opus 4.7),60% 大约剩下 60 万个 token——有充足的空间来完成工作并运行预备步骤。
实现方式包括:在状态行钩子中写入一个达到阈值时的警告标记,以及一个 UserPromptSubmit 钩子检测到该标记后建议运行 /compact-prep。
结果
现在,即使在进行了大约 10 次压缩后,会话也能保持稳定,没有逻辑崩溃!
- 由于压缩导致的工作丢失几乎为零。
- 被否决的想法被重新提出的情况消失了。
- 计划模式得以维持。
- 在 tmux 中运行多个工作者时,不再忘记拓扑结构。
参考:实现代码
以下是实际的技能和钩子代码。将它们复制到标准 Claude Code 目录 (~/.claude/) 即可使用。
A: compact-prep 技能主体
放置在 ~/.claude/skills/compact-prep/SKILL.md。
1---2name: compact-prep3description: |4 在 Claude Code 中执行 /compact 之前,将会话的当前状态保存到一个临时状态文件中。5 强制触发条件:/compact-prep, compact-prep, 压缩准备。6 不触发条件:压缩后的恢复、常规进度报告、计划创建。7strict_procedure: true8argument-hint: "[恢复笔记]"9allowed-tools: Read Write Bash(~/.claude/scripts/get-session-id.sh *) Bash(mkdir *) Bash(date *) Bash(pwd)10---1112# compact-prep1314在 `/compact` 之前,将会话状态中难以保留在压缩摘要中的信息保存到15`${TMPDIR}/claude-compact-state/${SESSION_ID}.md`。1617## 严格流程概要1819- 严格程度:严格流程。20- 硬性门控:如果无法获取 session_id,则停止。21- 强制手段:保存后检查固定路径和必要的标题。22- 完成确认:报告文件路径、已保存的项目以及运行 `/compact` 的指引。2324## 流程25261. 通过 `~/.claude/scripts/get-session-id.sh` 获取 session_id。272. 设置路径为 `${TMPDIR:-/tmp}/claude-compact-state/${SESSION_ID}.md`。283. 检查任务列表、活跃计划、tmux-bridge 和编辑中的文件。294. 按顺序保存标题:# Compact Prep State, ## Active Plan, ## Current Phase, ## TaskList Summary, ## Session Decisions, ## Constraints and Blockers, ## Worker Topology, ## Editing Files, ## Recovery Notes。305. 验证所有标题都存在。316. 告知用户:“准备工作已完成。请执行 `/compact`。”
B: 恢复钩子(两阶段)
B-1: PostCompact 钩子
~/.claude/hooks/compaction-recovery.sh
B-2: UserPromptSubmit 钩子(指令注入)
~/.claude/hooks/userpromptsubmit-compaction-recovery.sh
C: 60% 通知
C-1: 状态行钩子阈值分支
添加到 ~/.claude/hooks/statusline.sh。
C-2: UserPromptSubmit 钩子(提醒)
~/.claude/hooks/userpromptsubmit-compact-prep-reminder.sh
D: settings.json 注册
将钩子添加到 ~/.claude/settings.json。





