# Agent Note: 稳定快照刷新中的易变值

Status: implemented

[English](2026-07-27-stable-snapshot-refresh-volatiles.md) | 中文

## 问题

ACP（Agent Client Protocol）快照比较会归一化生成的 UUID、cwd 别名、spill locator、嵌入的事件时间和省略字节数，但刷新写回会持久化本次生成的原始值。因此，即使比较约定将两份日志视为相等，一次行为未发生变化的刷新仍会用新的随机值或宿主特有的路径写法改写 fixture（测试前置数据）。

消息身份所需的结构前提比记录对齐更弱：无关的日志事件可能破坏记录对齐，但继承而来的消息去除身份后的值在父级和子级日志之间仍保持不变。录制模式在替换现有 fixture 时也会从新生成的消息 UUID 开始。

## 决策

在录制或刷新写入会话 fixture 前，共享快照支持层会将可写入 fixture 的日志交给一个负责结构化处理消息 ID 的组件。该组件通过会话包的权威 surface 类型谓词识别 surface 载体，并识别 `agent/inbox/spliced` 中与这些载体关联的已排队消息副本；随后移除每条完整消息的顶层 `id` 并计算指纹，同时记录所有父级/子级日志中每条 ID 与指纹之间的关联边。仅当该 ID 与指纹在本次生成图和现有图中的度均为 1 时，才会复用现有 UUID，随后仅改写这些载体中通过验证的消息 `id` 字段。具有相同 ID、重复出现的继承消息仍算作一个候选项；新增、发生变化、内容重复、格式错误和存在冲突的消息则保留本次生成的 ID。ACP、JSON-RPC 和 Web 录制器会在擦除 header 并对 cwd 进行 token 化后执行这一步，因此消息身份取决于 fixture 中的写法，而非宿主机原始路径。

刷新写回以 `normalizeSessionLog` 作为已对齐叶值的易变值判定依据。系统使用本次运行的 id、cwd 及全部 cwd 别名归一化原始收集记录，并使用 fixture 头部上下文归一化 fixture 记录；字面量替换仅限于本次运行生成的会话 ID、cwd 值和 spill 路径。现有记录完成对齐后，系统基于这些归一化记录，递归比较本次生成记录与现有记录的叶节点：归一化后等价的叶节点保留现有原始值，归一化后不同的叶节点则保留本次生成的语义值。surface 或 inbox 载体中的完整消息 ID 不参与这一路径，以免按位置复用与结构复用各自独立分配同一个已提交 UUID。

复用前必须确保完整逻辑记录布局对齐，现有的打包分片与插入标题等价情形除外。归一化后等价但发生变化的字符串在整份日志范围内形成双射：一个本次生成的字符串只映射到一个现有字符串，反向亦然，因此跨记录重复出现的 ID 仍保持关联。出现无法解释的记录不匹配或映射冲突时，该日志会停用归一化字符串复用。

对象字段按键对齐。只有所有对应数组长度相同时，才对齐其元素；否则以本次生成的数组为准。字符串始终作为不可拆分的叶节点。现有的打包分片计时对齐与插入标题处理仍保持独立，因为它们对齐的是逻辑事件，而非单条记录内的值。

## 考虑过的替代方案

**在快照部署中使用确定性的 UUID 和 spill 文件名。** 替换生产环境使用的随机性会削弱测试所要验证的安全属性，或者要求存储与审批实现引入仅用于测试的行为。

**提交归一化后的 fixture。** token 化的会话日志将不再是原始回放输入，并会引发与写回缺陷无关的大范围 fixture 迁移。

**当整条记录的归一化形式未变时保留整条记录。** 这种做法更简单，但同一记录中的另一个字段发生语义变化时，也会改写其中的随机字段。按叶节点保留可使这些决策彼此独立。

## 后果

录制和刷新不再仅仅因为另一个事件改变了周边记录布局，就改写未变化且唯一的消息 UUID，无论该录制由 ACP、JSON-RPC 还是 Web 负责。重复刷新也会保留规范化器归类为易变值的已对齐 fixture 值；以后加入规范化器的新易变值类别也会自动继承该写回行为。结构有歧义时仍采取保守策略：记录无法匹配、字符串映射冲突、数组尺寸发生变化、字符串同时包含语义变化与易变变化、消息格式错误，或消息图中的 ID 或指纹不唯一时，均使用本次生成的值，避免冒险复用未对齐的数据。

聚焦的单元测试固定了会话包权威谓词识别的所有 surface 消息形态、持久 inbox/surface 关联、场景范围内的父级/子级消息关联、带 cwd 的可写入 fixture 消息匹配、无关事件插入、格式错误消息隔离、消息图在 ID 与指纹两条轴上的歧义、由单一处理方负责的写回、递归处理对象与数组的行为、映射冲突、本次运行的 cwd 别名、易变字符串以及本次生成的语义字段。无密钥刷新测试证明，审批 UUID、cwd 别名、spill 路径和事件读取中的易变值不会改变已提交 fixture 的任何字节。
