# Agent Note: 简化会话日志表示

Status: implemented

[English](2026-07-12-simplify-session-log-representation.md) | 中文

## 问题

会话日志维护着两种表示，其机制复杂度超出了消费方的实际需求：一个伪链表 surface 和自定义的请求头增量。

`SurfaceManager` 同时在数组、seq map 和可变 `prev`/`next` 链接中存储相同顺序。生产代码从不读取任一链接：compact 的工具配对 balance 根据按 surface 顺序缓存的每个切点 balance 作答。替换已经使用 `indexOf`，因此链接并未使其主导操作成为常数时间。使用线性替换查找的 seq 数组具有相同的渐近替换成本，却只有一种表示需要验证。

请求头子系统实现了一套自定义的系统/工具增量编解码器和传输决策层，尽管其契约声明增量只是编码优化，而非可重建性要求。在每个 agent loop（智能体循环）实例边界保留初始/恢复的完整快照，然后在该实例的组装头发生变化时写入一条规范的完整 `request/header`，即可保留回放能力，同时删除 `SystemDelta`、`ToolsDelta`、往返回退逻辑以及持久化的 `request/header-delta` 变体。编解码器专属的词汇随编解码器一起消失，并非因为其各分支本身无效。

实现保留追加与替换操作的 `sourceEventSeqs`、崩溃修复的溯源信息，以及所有 `SessionStartSource` 变体，因为这些字段承担审计/拦截职责，当前没有读取方并不能推翻这一点。

## 决策

`SurfaceManager.nodes` 是由事件序号组成的 `readonly number[]`；公共 `SurfaceNode` 形状、node 链接和 seq-to-node map 均已移除。内部替换 generation 信号保留。session-query 使用的完整 `foldSurface()` 读取会返回相同的数字数组表示和替换元数据，而无需让增量 manager 保留历史。工具配对 balance 和压缩（compaction）使用事件序号与 surface 位置；由 compact 拥有的每个切点的 balance cache 不依赖 node 链接。

请求头只使用规范的完整快照。初始与恢复锚点即使没有变化也仍是完整快照；实例内变化会追加另一个完整 `request/header`，reason 为 `change`。delta 事件、codec 类型、diff/apply 辅助函数，以及仅供 codec 使用的 `fallback` reason 均已移除。请求重建选择最新快照。

`SESSION_FORMAT_VERSION` 仍固定为 `0`，因此 seed、追加和持久化加载验证会显式拒绝旧 v0 `request/header-delta` 事件，以及携带已删除 `fallback` reason 的完整快照。不存在兼容性 fold 或迁移。JSONL 与 SQLite 测试固定了这一失败即报错的边界；ACP（Agent Client Protocol）快照 harness 则把合法的会话中途变更表示为完整固定请求头和完整可读提示词。

## 曾考虑的替代方案

**保留链表节点和紧凑增量以备未来扩展。** 链接可能有助于未来的游标 API，增量在大型工具 schema 仅有少量变化时可以缩减日志。但没有已发布的游标使用这些链接，而完整快照以磁盘空间为代价，显著简化了正确性保障。如果头部体积确实成为问题，可以基于真实 trace 设计压缩方案或经过度量的规范增量方案。

## 验证

单元测试覆盖并锁定有序 surface 的追加/替换行为、工具配对、压缩、完整请求头 fold/记录、请求重建和开发不变量。Seed 验证以及 JSONL、SQLite 加载测试会在回放前拒绝旧事件。无密钥 ACP 套件以新形状覆盖记录、刷新、回放、变更后请求头的固定，以及沙箱模式切换 fixture（测试前置数据）。

## 后果

完整请求头会增加日志体积，线性替换查找在极大 surface 上也可能较慢。由于先前实现调用 `indexOf`，替换原本就是线性的；benchmark 推迟到真实 trace 表明更简单的数组成为瓶颈时再进行。格式版本仍为 `0`，因此显式拒绝旧事件是预发布格式边界的永久组成部分。作为交换，surface 顺序和请求头状态现在各自只有一种表示，删除了链接维护、map、codec 分支、往返 fallback 和针对 delta 的快照规范化。
