第 11 章:压缩与修剪
长会话必须减少上下文,但“少发一些旧消息”会破坏工具调用配对、项目状态和用户目标。Grok Build 使用两类互补机制:
- pruning:局部缩短旧工具结果;
- compaction:用结构化摘要全量替换当前 conversation。
11.1 三个不同问题
| 问题 | 机制 | 是否调用模型 |
|---|---|---|
| 某个旧工具输出太长 | soft/hard pruning | 否 |
| 整段历史接近上下文窗口 | full-replace compaction | 是 |
| 单个请求图片/bytes 太大 | image eviction/strip | 否 |
不要把它们都称为“压缩”,否则很难理解触发条件和失败恢复。
11.2 Pruning 回顾
ChatState 默认保留最近约 3 turns 的完整工具结果;对更旧的大结果保留头尾;达到更老阈值可 hard clear。它是确定性的本地变换,成本低,适合在上下文刚开始变大时执行。
Pruning 不试图概括历史意图,只减少高冗余 payload。
11.3 Grok Build 的代码压缩是全量替换
xai-grok-compaction 提供 transport-agnostic 算法;Grok Build 使用 code compaction 的 whole-session full-replace 形态。
与“保留最近 N 条、只总结更旧前缀”不同,它组装一个新的 compacted history,成为下一轮完整对话基线。
11.4 为什么全量替换
全量替换的优势:
- 可以重新建立一个协议合法、短而完整的上下文;
- 不必保留任意切点附近的 tool-call 配对;
- system/user-info/rules 可刷新;
- provider overhead 与 token 账本可重新 seed;
- reducer 有明确 checkpoint。
代价:
- 摘要失败会影响整个历史视图;
- 必须严格验证组装结果;
- rewind 不能再按当前 User item 数量计算;
- 需要 durable checkpoint 支撑恢复。
11.5 触发来源
SessionActor 在多个时机考虑 compaction:
- 采样前:精确 token + 新增 byte 估算已达阈值;
- 模型切换:新模型 context window 更小;
- 工具完成后:大结果使下一请求预计溢出;
- 后端返回 context error:估算未命中真实限制;
- 用户手动 compact。
多触发点是必要的,因为 token 估算永远不可能完整预测 provider 的 framing 与动态 tool schema。
11.6 Token 决策
check_auto_compact_needed 结合:
- 上次模型返回的精确总 token;
- 自那以后新增 item 的 byte/token 估算;
- 当前 model context window;
- configured threshold;
- provider overhead。
effective_estimate =
last_provider_total
+ estimated_delta_since_response
+ relevant_overhead_adjustment2
3
4
它避免每个工具进度都完整 tokenize 历史,同时在采样后重新锚定精确值。
11.7 压缩流水线
可概括为四步:
1. Build prompt
选择需要总结的 conversation,附上结构化要求与当前工程上下文。
2. Sample
调用 sampler,应用认证、重试与终态分类。压缩模型调用本身也可能限流、超时或返回无效内容。
3. Clean/validate
- 去除无效 framing;
- 识别失败/拒绝响应;
- 限制大小;
- 检查摘要是否有实质内容;
- 确保新 history 满足 message/tool invariants。
4. Assemble
按固定顺序组装 CompactedHistoryParts 并全量替换。
11.8 新 History 的顺序
核心顺序大致是:
- compact system prompt;
- 刷新的 user-info prefix;
- 可选 AGENTS/project instruction reminder;
- 最近 user query;
- 近期关键 messages;
- compaction summary;
- 可选 system reminder。
顺序有语义:
- system 行为必须在头部;
- 环境要早于 summary;
- 最近问题帮助模型恢复当前工作焦点;
- summary 提供被折叠的历史;
- 尾部 reminder 告诉模型压缩后如何继续。
11.9 Summary 应保存什么
代码 Agent 的摘要不能只写“用户让我们修 bug”。它至少应尽力保存:
- 用户目标、约束和未解决问题;
- 已做工作与设计决策;
- 修改过/读取过的关键文件;
- 测试、命令及其结果;
- tool side effects;
- 当前计划/todo;
- 错误尝试及为何失败;
- 下一步;
- 需要继续遵守的规则。
真正文件内容仍应通过工具读取,摘要只保存导航与状态,不应伪造整个 workspace snapshot。
11.10 Tool Call 结构修复
压缩切换或异常历史可能留下:
- tool result 没有对应 assistant call;
- assistant call 没有 result;
- 重复 call id;
- provider 不接受的交错顺序。
组装后会 sanitise/validate orphan tool results,并在必要时走 fallback。压缩后的 conversation 必须先满足协议合法性,再谈语义完整。
11.11 持久化顺序
全量替换是跨内存与磁盘的状态转换。安全顺序需要保证:
- compacted history 已验证;
- persistence 写 checkpoint/新基线;
- ChatState replace;
- token/overhead 重新 seed;
- 客户端收到 compaction 生命周期更新;
- 主 loop 继续。
如果内存先替换而 checkpoint 失败,崩溃后会恢复到不同历史;如果 checkpoint 先写但 actor 不收敛,也要有明确错误恢复。相关 helper 与 persistence ack 正是在处理这种边界。
11.12 Reset 的运行时状态
压缩后需要重置或刷新:
- memory injected 标记;
- plan/AGENTS/skills reminder 状态;
- token estimates;
- last compaction metadata;
- 某些 auto-compaction suppression;
- 当前 system/user prefix。
原因是新的 conversation 已经改变“哪些信息仍然存在”。若沿用旧的“已提醒”标志,关键规则可能永远不再注入。
11.13 自动压缩抑制
失败后若下一 loop 立即重复同一压缩,会形成:
need compact -> compact failed -> loop -> need compact -> ...系统维护 suppression,区分:
- 当前上下文/模型下的确定性失败;
- 认证/额度类账户状态;
- 手动触发;
- 模型切换。
模型切换可以清理 size/general suppression,因为新窗口或后端可能成功;账户类抑制更 sticky。手动 compact 不应永久设置自动抑制。
11.14 run_compact_only
自动压缩可作为当前 turn 中的 compact-only step:
- 发出自动压缩开始事件;
- 完成或报告失败;
- 不把压缩当作用户最终回答;
- 外层 loop 用新 history 继续真正模型调用。
这样 UI 能显示“正在压缩”,而对话语义仍属于原 prompt。
11.15 压缩与 Rewind
压缩后当前 conversation 已失去一一对应的旧 user turns。Rewind 若跨越 compaction:
- 不能按内存 item 数倒退;
- 必须从 updates/checkpoint 重放;
- 文件 rewind 使用独立 prompt checkpoints;
- conversation-only 与 files-only 仍可分开预览/提交。
这也是恢复面设计正确与否的试金石。
11.16 压缩与 Memory
Compaction 总结当前 session;Memory 面向跨 session 的长期知识。二者可交互,但不能互相替代:
| Compaction | Memory |
|---|---|
| 保持当前任务连续 | 帮助未来任务复用知识 |
| 随上下文窗口触发 | 按检索或 Dream 策略触发 |
| 结果进入当前 conversation | 结果存 Markdown/索引 |
| 更重视未完成工作 | 更重视稳定事实与经验 |
压缩后可重新注入 Memory,避免旧注入内容已被 summary 丢失。
11.17 失败模型
| 失败 | 恢复 |
|---|---|
| 摘要模型网络失败 | retry/classify,必要时 suppression |
| 无效/空摘要 | clean/validate 拒绝 |
| context 已过大无法压缩 | 更保守 input selection/fallback |
| checkpoint 写失败 | 不静默切换内存基线 |
| 组装产生 orphan tool | sanitize/validate |
| 连续确定性失败 | suppression 防循环 |
| 用户手动重试 | 单独路径,不污染自动策略 |
11.18 源码阅读卡
| 目的 | 文件 |
|---|---|
| Compaction crate 总览 | xai-grok-compaction/src/lib.rs |
| Code compaction 主流程 | code_compaction/compact.rs |
| Prompt 与 sample | code_compaction/prompt.rs、sample.rs |
| History assemble | code_compaction/assemble.rs |
| Session 集成 | session/compaction.rs |
| Full-replace helper | helpers/full_replace_compaction.rs |
| ChatState 估算与 pruning | chat-state/compaction_utils.rs |
11.19 本章结论
Pruning 与 compaction 的本质都是“模型视图可以有损”,但 durable history 仍需可解释:
Pruning 局部减少冗余;full-replace compaction 重建短而合法的 conversation;checkpoint 让 reducer 知道新基线;suppression 保证失败不会变成无限循环。
下一章把时间尺度再拉长:Grok Build 如何把多个 session 的经验整理为可检索的 Markdown 记忆。