Simplify session-log representation
简化会话日志表示
The session log maintains two representations that cost more machinery than their consumers require: a pseudo-linked surface and custom request-header deltas. `SurfaceManager` stores the same order in an array, a seq map, and mutable `prev`/`next` links. Production never reads either link: compact's tool-pairing balance answers from per-cut balances cached in surface order. Replacement already uses `indexOf`, so the
English
Problem
The session log maintains two representations that cost more machinery than their consumers require: a pseudo-linked surface and custom request-header deltas.
SurfaceManager stores the same order in an array, a seq map, and mutable prev/next links. Production never reads either link: compact's tool-pairing balance answers from per-cut balances cached in surface order. Replacement already uses indexOf, so the links do not make its dominant operation constant-time. A seq array with linear replacement lookup has the same asymptotic replacement cost and one representation to validate.
The request-header subsystem implements a custom system/tool delta codec and transmission-decision layer even though its contract says deltas are an encoding optimization, not a reconstructability requirement. Retaining the initial/resume full snapshot at each loop-instance boundary, then writing a canonical full request/header whenever that instance's assembled header changes, preserves replay while deleting SystemDelta, ToolsDelta, round-trip fallback, and the durable request/header-delta variant. Codec-only vocabulary disappears with the codec, not because its individual arms were invalid.
The implementation retains append and replacement sourceEventSeqs, the tool/call seq cited by crash-repaired results, and all SessionStartSource variants because those fields have an audit/interception role that zero current readers does not overturn.
Decision
SurfaceManager.nodes is a readonly number[] of event sequences; the public SurfaceNode shape, node links, and seq-to-node map are removed. The internal replace-generation signal remains. The complete foldSurface() read used by session-query returns the same number-array representation plus replacement metadata without making the incremental manager retain history. Tool-pairing balance and compaction use event sequences and surface positions; the compact-owned per-cut balance cache does not depend on node links.
Request headers use canonical full snapshots only. Initial and resume anchors remain full snapshots even when unchanged; an in-instance change appends another full request/header with reason change. The delta event, codec types, diff/apply helpers, and codec-only fallback reason are removed. Request reconstruction selects the latest snapshot.
SESSION_FORMAT_VERSION remains pinned at 0, so seed, append, and persistence-load validation explicitly reject old v0 request/header-delta events and full snapshots carrying the removed fallback reason. There is no compatibility fold or migration. JSONL and SQLite tests pin this fail-loud boundary, and the ACP snapshot harness represents legitimate mid-session changes as full pinned headers and full readable prompts.
Alternatives considered
Keep linked nodes and compact deltas for possible scale. Links could help a future cursor API, and deltas can reduce logs when large tool schemas change by a small amount. No shipped cursor uses the links, while full snapshots trade disk size for substantially simpler correctness. If header volume proves material, compression or a measured canonical-delta scheme can be designed around real traces.
Verification
Unit coverage pins ordered-surface append/replace behavior, tool pairing, compaction, full-header folding/logging, request reconstruction, and dev invariants. Seed validation plus JSONL and SQLite load tests reject the legacy event before replay. The keyless ACP suite exercises record, refresh, replay, changed-header pinning, and the sandbox mode-switch fixture in the new shape.
Consequences
Full headers increase log volume, and linear replacement lookup could be slower on very large surfaces. Replacements were already linear because the prior implementation called indexOf; benchmarks are deferred until real traces show the simpler array is a bottleneck. The format version remains 0, so explicit legacy-event rejection is a permanent part of the pre-release format boundary. In return, surface order and request-header state each have one representation, deleting link maintenance, maps, codec arms, round-trip fallback, and delta-aware snapshot normalization.
中文
问题
会话日志维护着两种表示,其机制复杂度超出了消费方的实际需求:一个伪链表 surface 和自定义的请求头增量。
SurfaceManager 同时在数组、seq map 和可变 prev/next 链接中存储相同顺序。生产代码从不读取任一链接:compact 的工具配对 balance 判定基于按 surface 顺序缓存的各切点 balance。替换已经使用 indexOf,因此链接并未使其主导操作成为常数时间。使用线性替换查找的 seq 数组具有相同的渐近替换成本,却只有一种表示需要验证。
请求头子系统实现了一套自定义的系统/工具增量编解码器和传输决策层,尽管其约定声明增量只是编码优化,而非可重建性要求。在每个 agent loop(智能体循环)实例边界保留初始/恢复的完整快照,然后在该实例的组装头发生变化时写入一条规范的完整 request/header,即可保留回放能力,同时删除 SystemDelta、ToolsDelta、往返回退逻辑以及持久化的 request/header-delta 变体。编解码器专属的词汇随编解码器一起消失,并非因为其各分支本身无效。
实现保留追加与替换操作的 sourceEventSeqs、崩溃修复结果引用的 tool/call seq,以及所有 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 的快照规范化。