02 基准、快照与纪元三件套 本节摘要:上一节讲了上下文源怎么组合成 System Context,但组合完之后,怎么让它「稳定」——稳定到可以作为模型缓存的前缀、稳定到变化能被检测?OpenCode 的答案是三件套:基准(Baseline)、快照(Snapshot)、纪元(Epoch)。基准是精确文本(作为模型缓存前缀,所以必须稳定),快照是模型不可见的 JSON(用来 diff 出「什么变了」),纪元是一段不可变的有效期(压缩或会话移动时结束)。本节讲清这三件套各自的角色,以及它们如何协作让 System Context「既稳定又可演化」。 一、稳定性问题:为什么需要三件套 先看问题。第 6 章讲过 Prompt Caching——模型会缓存稳定的前缀,重复读取成本极低(约 0.1×)。
本节摘要:上一节讲了上下文源怎么组合成 System Context,但组合完之后,怎么让它「稳定」——稳定到可以作为模型缓存的前缀、稳定到变化能被检测?OpenCode 的答案是三件套:基准(Baseline)、快照(Snapshot)、纪元(Epoch)。基准是精确文本(作为模型缓存前缀,所以必须稳定),快照是模型不可见的 JSON(用来 diff 出「什么变了」),纪元是一段不可变的有效期(压缩或会话移动时结束)。本节讲清这三件套各自的角色,以及它们如何协作让 System Context「既稳定又可演化」。
先看问题。第 6 章讲过 Prompt Caching——模型会缓存稳定的前缀,重复读取成本极低(约 0.1×)。System Context 天然是「前缀」(每次回合都在最前面),所以让它稳定能省大量 token。
但 System Context 又必须能演化(对话进行中环境会变)。这就矛盾了:
OpenCode 用三件套化解这个矛盾:在一段「纪元」内保持基准稳定(缓存命中),变化时通过 diff 检测并开新纪元(重建缓存)。
基准是 System Context 被渲染成的精确文本——它就是模型实际看到的初始指令文本。关键特性是:在一段纪元内,基准保持不变。
纪元 N 开始 ──► 基准 = 某段精确文本 │ │ (整个纪元 N 期间,基准不变,模型缓存命中) │ 纪元 N 结束 / 纪元 N+1 开始 ──► 新基准(重新渲染)
为什么必须精确稳定?因为它直接作为模型缓存的前缀——差一个字符,缓存就失效。基准的存在,让「缓存命中」成为可能:只要还在同一纪元,模型反复读同一份基准,享受 0.1× 的低读取成本。
⚠️ 别把基准当「会变的字符串」:在纪元内它是冻结的。想变?得开新纪元(见后文)。这个「冻结」特性是缓存命中前提。
如果基准是冻结的,那怎么知道「环境变了」?靠快照。快照是 System Context 的模型不可见 JSON 表示——模型看不到它,但 OpenCode 用它来 diff:
上一回合的快照(JSON) │ ▼ 对比 这一回合的快照(JSON) │ ▼ diff 哪些上下文源变了?(key 级别)
快照和基准的分工:
| 基准 | 快照 | |
|---|---|---|
| 形式 | 精确文本 | JSON |
| 模型可见? | 是(模型看到的初始指令) | 否(模型看不到) |
| 作用 | 作为缓存前缀 | 用来 diff 检测变化 |
| 稳定性 | 纪元内冻结 | 每回合都更新(用于对比) |
💡 为什么快照是 JSON 而基准是文本:JSON 适合结构化 diff(key 级别精确比较「哪个源变了」),文本适合喂给模型。各取所长。模型看不到快照,所以快照怎么变都不影响缓存(缓存只看基准文本)。
纪元是三件套的「组织者」——它定义一段基准有效的不可变期。在一个纪元内:
纪元在以下情况结束,开启新纪元:
| 触发条件 | 说明 |
|---|---|
| 某个上下文源变化且需替换基准 | 环境变了,旧基准不再适用 |
| 上下文压缩(第 9 章) | 压缩重写了历史,前缀变了 |
| 会话移动(换 Location,第 7 章) | 新 Location 的环境信息不同 |
纪元 1(基准A) │ 某源变化 / 压缩 / 移动 ▼ 纪元 2(基准B) ← 新基准,模型重新建立缓存 │ ▼ ...
把三件套放一起看一次完整协作:
回合开始 │ ├─ 取当前各上下文源的最新事实 ├─ 生成新快照(JSON) ├─ 与上一回合快照 diff │ ├─ 无变化 ──► 继续用当前基准(缓存命中) │ └─ 有变化 ──► 触发协调(第 04 节) │ ├─ 可替换 ──► 新基准,开新纪元 │ └─ 需中途消息 ──► 第 03 节 │ ▼ 把(可能的新)基准作为前缀喂给模型 模型调用(缓存命中或重建)
这套协作实现了「既稳定又可演化」:稳定靠纪元内基准冻结,演化靠快照 diff + 协调 + 新纪元。
新手容易把「纪元」和「回合」搞混。区别:
会话时间轴: 回合1 回合2 回合3 │ 回合4 回合5 │ 回合6 ... │ │ 纪元1结束 纪元2结束 (基准A 跨回合1-3) (基准B 跨回合4-5)
一个纪元里多个回合共享同一基准——这正是缓存命中的来源:多个回合反复读同一份基准。
基准稳定能覆盖「缓慢变化」,但有个场景它处理不了——对话进行中某个源突然变化,模型必须马上知道。这就是下一节的「对话中途系统消息」。