# 上下文压缩 · 满了怎么办

> 上下文窗口会满。grok 的压缩不是「留个尾巴」，而是把整盘对话总结成一份九段式结构化摘要、再重建一份全新历史。这一章看它的触发、摘要提示、清洗、重组，以及那道防「压不动」的输入梯子。

- 作者：David（道雾轩）
- 专栏：Grok Build 源码解读（https://daiw.org/manual/grok-build.md）
- 最后更新：2026-09-15
- 原文：https://daiw.org/manual/grok-build/compaction
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 上下文压缩 · 满了怎么办

再长的上下文窗口也会满。满了还硬塞，要么报错要么烧钱。grok 的应对是**压缩**——策略颇为激进，值得细看。

## 触发：用量过 85%

策略在 `xai-grok-agent/src/compaction.rs` 的 `CompactionPolicy`:

- `auto_compact_threshold_percent` 默认 **85**——估算用量(总 token / 上下文窗口)达到 85% 就自动压(这是代码默认值，环境变量、用户配置或远端按模型下发的设置都能覆盖)；
- `wall_clock_budget_secs` 默认 300——单次压缩的墙钟预算，超了就掐掉重试(防推理跑飞)；
- `memory_flush_enabled`、`two_pass_enabled`、`compact_model`——见下。

[主循环](https://daiw.org/manual/grok-build/agent-loop)在每圈采样前调 `check_auto_compact_needed()`，工具执行后再调一次 `check_preflight_overflow()` 兜底(两者都在 `xai-grok-shell/src/session/compaction.rs`)。

## 策略：全量替换，不留尾巴

多数 agent 压缩是「留最近 N 条 + 摘要前面」。grok 走的是 **full-replace**(`xai-grok-compaction/src/code_compaction/`)：**把整盘对话总结掉，重建一份全新历史**。流程：

```text
构建提示 → 采样(重试+分类) → 清洗摘要 → 重组历史
```

## 九段式结构化摘要

摘要提示(`templates/full_replace_summary_prompt.txt`)不是「随便总结一下」，而是要模型产出**九个固定段落**：

1. 主要请求与意图 · 2. 关键技术概念 · 3. 文件与代码片段(最近的编辑贴全) · 4. 错误与修复 · 5. 问题求解 · 6. 全部用户消息(明确排除「总结指令」这条本身) · 7. 待办任务 · 8. 当前工作 · 9. 可选下一步(带原话引用防漂移)。

还有条硬规矩：**把之前的压缩摘要也带进来**，保证跨多次压缩不丢信息。模型要先在私下推理里想清楚、不单独输出分析草稿，最终只交一个 `<summary>…</summary>` 块。

<Callout type="info">
  眼熟吗？这套九段式结构，和很多长会话 agent(包括你正在用的这类工具)「续上一段对话」时的摘要格式几乎同构——它是把「一盘长对话压成不丢关键信息的短摘要」这件事工程化的成熟答案。
</Callout>

## 清洗与重组

模型的输出不能直接用，`summary.rs` 要清洗：剥掉开头的 `<analysis>` 草稿(提示词虽然不让写，还是得防)、把外层 `<summary>` 转成 `Summary:` 前缀、给回显的控制标记后插零宽空格(防摘要「教」下一轮再吐一个块)。还有个 `is_degenerate_summary` 检查：清洗后不足 **500** 字符就当成瞬时失败重试(源码注释说，线上见过最短的健康摘要也有约 3,200 字符)。

重组(`assemble.rs`)把历史拼回一个规范形状：

```text
[System, UserMeta(前缀), ProjectInstructions(AGENTS.md), User(最后的 query), 最近尾巴…, UserMeta(摘要+回捞指针), SystemReminder]
```

注意 AGENTS.md 是**原文重新注入**的(不交给摘要器转述)，最近的尾巴放在摘要前——既有「全局记忆」又有「近期工作」。

## 回捞指针：压掉的历史去哪了

摘要末尾那段「回捞指针」指向哪，由压缩模式决定(`features.compaction_mode` 或环境变量 `GROK_COMPACTION_MODE`)：默认的 `segments` 会把被压掉的历史按段写成会话目录下的 `compaction/segment_*.md`，另附一份 `INDEX.md` 目录(markdown 渲染在新拆出的 `xai-compaction-transcript` crate)，并在摘要里提示模型：需要细节就用 `read_file`、`grep` 回查，别改这些文件；`transcript` 模式改为指向原始的 `updates.jsonl`；`summary` 模式则不留指针。

## 防「压不动」的输入梯子

一个魔鬼细节：如果对话已经大到**连总结请求本身都超上下文**怎么办？grok 有一道**输入梯子**(`InputStage`，在 `xai-grok-shell/src/session/compaction.rs`)：`Verbatim`(逐字)→ `VerbatimFitted`(裁到预算)→ `Lossy`(有损)。压缩请求因上下文溢出失败时，host 不是放弃，而是**降一级重建更小的输入**再试——这就是防止会话进入「压不动的死状态」的机关。

另外两招：**压缩前先 memory flush**(`memory_flush_enabled`，把该记的持久知识先存下再丢历史，见[记忆](https://daiw.org/manual/grok-build/memory))；**两遍预压缩**(`two_pass_enabled`，用量接近阈值时先在后台把历史前缀总结好，真压缩时只补最近尾巴)。

## 和《从 LLM 到 Coding Agent》对照

对照 [context-compaction](https://daiw.org/manual/llm-to-agent/context-compaction) 与 [auto-compaction](https://daiw.org/manual/llm-to-agent/auto-compaction)：最小实现讲清了「为什么要压、怎么触发」。grok 的增量是把它做成了一条带**结构化摘要、退化检测、控制符清洗、输入梯子、记忆联动、两遍预压**的完整产线——每一环都在回答「怎么压得又不丢信息、又不会自己卡死」。

---

上一篇：[system prompt 怎么组装](https://daiw.org/manual/grok-build/system-prompt) · 下一篇：[记忆 · 跨会话记住东西](https://daiw.org/manual/grok-build/memory)
