fix(session-persistence): invalidate stale preparations

This commit is contained in:
imccyu
2026-08-06 03:45:22 +08:00
parent 5e317371e5
commit ede74b1926
25 changed files with 353 additions and 101 deletions
@@ -20,11 +20,11 @@ agent loop(智能体循环)通过同一条设置与发布流水线消费这
## 持久化准备生命周期
使用协调器的持久化实现会将一个冷源加载为准备完成的 Session。后端转移新鲜、彼此无别名的元数据和事件;Session 恢复路径直接验证并冻结这些对象图,不再复制。协调器计算中断轮次的 closer,并且只构造一次精确的未发布 Session。其不可变 header 与平衡逻辑事件日志构成读取方借用的 `SessionInspection`。
使用协调器的持久化实现会将一个冷源加载为准备完成的 Session。后端转移新鲜、彼此无别名的元数据和事件,以及标识这些精确值的来源限定 revision;Session 恢复路径直接验证并冻结这些对象图,不再复制。协调器计算中断轮次的 closer,并且只构造一次精确的未发布 Session。其不可变 header 与平衡逻辑事件日志构成读取方借用的 `SessionInspection`,revision 则保留在持久化内部。
`inspect(id, signal?)` 不修改存储。合成 closer 只存在于准备完成的内存视图中,撕裂的物理尾部保持不变。同 id 调用方共享进行中的冷读。准备完成后,该对象可以进入每个协调器自己的 LRU;第一方后端可配置容量,默认保留五个。
`inspect(id, signal?)` 不修改存储。合成 closer 只存在于准备完成的内存视图中,撕裂的物理尾部保持不变。同 id 调用方共享进行中的冷读。准备完成后,该对象可以进入每个协调器自己的 LRU;第一方后端可配置容量,默认保留五个。协调器复用保留源之前会读取该 id 的当前 revision;如果不匹配,就淘汰旧源并重新完成冷实体化。
`prepare(id, signal?)` 独占预留准备完成的 Session。它先提交撕裂尾部和中断轮次修复并建立持久游标,再返回可 dispose 的准备对象。同 id 的另一个准备请求会等待当前预留发布或释放。发布只接受精确的预留 Session,并直接附接已提交游标,无需重建历史。设置失败或取消时,未发生变化的未发布 Session 会返回 LRU;发生变更或完成附接后,系统会消费该预留。
`prepare(id, signal?)` 独占预留准备完成的 Session。它先确认保留的 revision,再提交撕裂尾部和中断轮次修复、建立持久游标,最后返回可 dispose 的准备对象。过期源会被丢弃并重新读取,不会参与修复或发布。同 id 的另一个准备请求会等待当前预留发布或释放。发布只接受精确的预留 Session,并直接附接已提交游标,无需重建历史。设置失败或取消时,未发生变化的未发布 Session 会返回 LRU;发生变更或完成附接后,系统会消费该预留。
存量 `load(id)` API 使用相同的准备和修复机制,随后丢弃其预留并返回不可变逻辑视图。它保留为兼容 API,不承担历史到恢复的复用路径。该生命周期扩展了[共享持久化协调器](2026-06-18-shared-persistence-write-coordinator.md),同时继续遵循[会话持久化决策](2026-06-14-session-persistence.md)所规定的存储与恢复规则。
@@ -32,6 +32,8 @@ agent loop(智能体循环)通过同一条设置与发布流水线消费这
历史读取使用 `inspect()`,因此重复分页可以借用同一份不可变准备状态,而不会激活 agent。后续恢复调用 `prepare()`,直接取得检查阶段保留的精确 Session;系统不会再次完整读取、解压、解析、复制、验证或冻结日志。
如果持久日志在检查后发生变化,其 revision 也会变化。下一次历史读取或恢复会丢弃保留的 Session,并实体化新日志,因此旧事件对象图不会被关联到较新的快照 revision。
冷 continuable subagent 访问沿用同一路径。系统先检查子会话并完成 descriptor 授权,再由 `ctx.agents.resume()` 预留并发布保留的 Session。这样既遵循 [continuable subagent 会话决策](../feature/2026-07-28-continuable-subagent-conversations.md)中的生命周期与授权规则,也消除了重复冷读。
## 边界
@@ -41,10 +43,11 @@ agent loop(智能体循环)通过同一条设置与发布流水线消费这
- 缓存属于单个持久化协调器,而不是进程全局 Session map。实时 Session 由现有存储持有,绝不占用准备容量。
- 新建流程绝不认领相同 id 的冷持久化准备对象。持久化冲突仍会被拒绝。
- 第三方持久化实现继续获得通过 `load()` 实现的抽象 `prepare()` 回退。它们使用相同发布接口,但只有覆盖准备流程后才能复用精确对象。
- Revision 校验在复用点和修复提交点建立新鲜性,但不会为后端增加跨进程 writer 排他。
## 验证
共享持久化契约覆盖无变更且已配平的冷检查与后续修复。`persistence.spec.ts` 与 `preparations.spec.ts` 覆盖同 id 进行中读取共享、检查与准备之间的精确 Session 复用、修复只提交一次、独占预留、设置失败后释放、就绪项 LRU 淘汰、预留期间拒绝 append,以及只允许发布预留 Session。agent loop 与 continuable subagent 测试覆盖统一发布流水线,以及取消和拆卸期间从检查到恢复的路径。
共享持久化契约覆盖无变更且已配平的冷检查与后续修复。`persistence.spec.ts` 与 `preparations.spec.ts` 覆盖同 id 进行中读取共享、检查与准备之间的精确 Session 复用、在历史读取与恢复前由 revision 触发刷新、修复只提交一次、独占预留、设置失败后释放、就绪项 LRU 淘汰、预留期间拒绝 append,以及只允许发布预留 Session。后端测试覆盖完整读取与轻量读取使用同一 revision 身份。agent loop 与 continuable subagent 测试覆盖统一发布流水线,以及取消和拆卸期间从检查到恢复的路径。
## 考虑过的替代方案