Skip to content

Composer 与上下文 cache

模型每一步看到的不是一个累积的对话记录——它是 ContextComposer 从 fold 后的状态当场组装出的一个 View。和 fold 一样,Composer 是一个纯函数:相同的状态组装出相同的 View。它在每次 run_one_step 中运行一次,一个 ContextPlanComposed 信封精确记录了这一步是由什么构建的——选择了哪些块、什么被压缩了、什么被丢弃了——因此每次 LLM 调用在事后都可审计。

三个段,按波动性排序

View 按各部分的变化频率被切分为三个段:

包含何时变化
stable_prefix系统 prompt + 工具定义身份或工具集变化
semi_stable已激活的 Skills、记忆索引已激活内容集变化
dynamic_suffix滚动对话 + 工具结果每一步

这种切分是为了 cache。Provider 按前缀缓存 KV 状态:只要前缀逐字节不变,就复用前一步的 cache 而不是重新编码和重新计费。因此 Composer 将所有稳定内容推到前面并保持字节稳定——排序的工具 schema 键、无时间戳、固定字段顺序——并将所有波动性写入尾部。使 fold 可复现的同一种确定性纪律(见 Fold & 快照)在这里换来了 cache 命中。

常驻内容通过内容通道(content channels)进入 semi_stable 段:一次激活被记录为一个事件,一个注册的渲染器在每次后续组装时将该内容放入段中。Skills 和记忆索引是两个内置使用者。半稳定段不受压缩影响,因此已激活的内容能在长对话中存活。

压缩是一个事件,不是一次编辑

当对话变得过长时,必须有东西被压缩。Noeta 的选择:压缩是一个被记录的事件,不是对历史的就地编辑。Policy 决定压缩;Engine 发出一个携带摘要引用的压缩事件;fold 读取它;下一次组装将被压缩的片段替换为摘要——而稳定前缀保持不变,原始消息保留在日志中。后果:

  • 可审计且可复现。 压缩在日志中,因此恢复的 Task 以相同方式压缩,你可以在事后看到确切被削减了什么。
  • 没有东西被擦除。 摘要是在组装时应用的一层,而非一次覆盖。完整历史在底层仍可 fold(见事件溯源)。

一个自旋保护为此兜底:如果压缩持续触发而记录的边界从未推进,Engine 会使 Task 失败,而不是永远循环。

相关:引擎与执行 · Provider 中立 · 事件溯源

Released under the MIT License.