02 隐藏检查点的滚动摘要 本节摘要:触发压缩后,旧对话被替换成什么?不是简单截断,而是一个隐藏检查点(hidden checkpoint)——由「结构化滚动摘要 + token 受限的近期上下文」组成。本节讲清检查点的结构:它长什么样、为什么是「滚动」的、近期上下文如何 token 受限地保留。理解了检查点,你才懂 Agent 压缩后「记得什么、忘了什么」。 一、检查点不是「截断」 先破除一个误解:压缩不是「砍掉前面一半」。简单截断的问题: 砍掉的可能含关键信息(如任务目标、关键决策)。 砍掉后模型「失忆」,可能重复问已问过的事。 OpenCode 的做法是生成一个隐藏检查点——它不是粗暴截断,而是「智能摘要 + 选择性保留」。
本节摘要:触发压缩后,旧对话被替换成什么?不是简单截断,而是一个隐藏检查点(hidden checkpoint)——由「结构化滚动摘要 + token 受限的近期上下文」组成。本节讲清检查点的结构:它长什么样、为什么是「滚动」的、近期上下文如何 token 受限地保留。理解了检查点,你才懂 Agent 压缩后「记得什么、忘了什么」。
先破除一个误解:压缩不是「砍掉前面一半」。简单截断的问题:
OpenCode 的做法是生成一个隐藏检查点——它不是粗暴截断,而是「智能摘要 + 选择性保留」。检查点对用户不可见(所以叫「隐藏」),它替换了被压缩的旧对话,成为模型的新历史起点。
检查点由两部分组成:
| 部分 | 作用 |
|---|---|
| 结构化滚动摘要 | 把旧对话总结成一段结构化文本(目标、关键细节、工作状态、下一步、相关文件) |
| token 受限的近期上下文 | 保留最近几回合的完整对话(不压缩),保证近期连续性 |
旧对话(几十回合) │ ▼ 压缩 隐藏检查点 = { 摘要: "目标:重构X模块。已决定:用策略A。已完成:步骤1、2。下一步:步骤3。相关文件:a.ts, b.ts", 近期上下文: [最近 N 回合的完整消息] } │ ▼ 替换旧对话 模型的新历史 = [检查点摘要 + 近期上下文 + 后续新对话]
摘要不是「自由文本总结」,而是结构化的——按固定板块组织:
这种结构化让摘要既有「宏观」(目标、状态),又有「可操作」(下一步、文件),模型看了能立刻接上工作。
「滚动」指的是:多次压缩时,摘要本身也会被摘要。第一次压缩生成摘要 A,会话继续又爆了,第二次压缩会把「摘要 A + 后续对话」一起摘要成摘要 B......如此滚动。
第1次压缩:旧对话1 ──► 摘要A 会话继续,又爆 第2次压缩:摘要A + 旧对话2 ──► 摘要B(更高层) 会话继续,又爆 第3次压缩:摘要B + 旧对话3 ──► 摘要C ...
这种「摘要的摘要」让超长对话也能被层层浓缩。每次摘要会根据是否有前次摘要用不同提示:「创建新摘要」或「更新已有摘要」。
除了摘要,还要保留最近几回合的完整对话。为什么?因为摘要丢失了细节,而「最近在做的事」需要细节——比如刚执行的命令、刚改的文件、模型的最新推理。这些用摘要说不清,必须原样保留。
近期上下文是「token 受限」的——它有一个预算(默认大致 min(8000, 可用额度的 25%)),在这个预算内尽量多保留:
保留预算 = min(8000, max(2000, 可用额度 × 0.25)) │ ▼ 在预算内,从最近回合往回保留 保留:最近 N 回合(默认 tail_turns = 2) │ ▼ 如果 N 回合超预算,在回合内切分 保留:预算内的部分
💡 为什么按回合保留:对话是「回合」为单位的(一次模型调用 + 工具处理)。按回合保留保证「完整的问答对」不被切断——切到一半的回合会让模型困惑。
除了对话本身,工具输出还会被额外修剪。工具输出往往很大(命令结果、文件内容),旧的输出大概率不再需要。所以有个额外机制:
这保证了「大工具输出」不会随历史堆积把上下文吃光。
压缩完成后,模型看到的新历史大致是:
[系统上下文] [检查点:摘要 + 近期上下文] [压缩后的新对话]
从模型视角,它「记得」目标、关键决策、最近在做什么(摘要 + 近期),但「忘了」中间过程的细节。这种「有损但够用」的记忆,让长对话能持续——代价是细节丢失(可用记忆库补,OpenWork 教程第 7 章)。
检查点讲清了,下一节讲「跨压缩边界的约束」——为什么厂商原生消息不能被切割。