docs(i18n): refresh persistence Agent Notes

This commit is contained in:
Tianyi Cui
2026-07-23 20:52:40 +08:00
parent daf4f91320
commit cff3cb6dcd
4 changed files with 9 additions and 10 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-18-shared-persistence-write-coordinator.md: 12800b118bd2fd1189319d0edf37cace3d916a9c
2026-06-18-shared-persistence-write-coordinator.zh.md: 23d6c66989880369c4e26c38145559c198ea1033
2026-06-18-shared-persistence-write-coordinator.md: 2349c50735045ae99e70bb5594b69f4252994b91
2026-06-18-shared-persistence-write-coordinator.zh.md: c68cbd984197bf68415d1f2bb68f297f51548bc3

View File

@@ -18,11 +18,10 @@ Status: implemented
### 钩子接口(`PersistenceBackend<TornMarker>`
六个方法(五个必需 + 一个可选的生命周期钩子)——协调器与存储之间唯一的 seam
五个必需成员加一个可选的生命周期钩子,构成协调器与存储之间唯一的边界
- `name`——后端标签,用于 dispose 失败时的 `AggregateError`
- `loadStored(id)`——按 id 读取已存储前缀,扫描任何存储范围JSONL 的每个 cwd bucketSQLite 的 id 全局唯一)。用于恢复/加载,以及通过 `!== undefined` 进行创建碰撞探测
- `loadLive(id, cwd)`——读取限定于 `cwd` 的已存储前缀。**与 `loadStored` 有意区分**HMR live-adoption 只能接管与存活会话处于同一 cwd 的持久化日志;同 id 但不同 cwd 的日志是碰撞而非恢复。合并二者会重新引入跨 cwd 接管 bug。SQLite 忽略 `cwd`
- `loadStored(id)`——按 id 跨所有存储范围读取一个已存储前缀JSONL 的所有 cwd bucketSQLite 的 id 全局唯一)。恢复/加载、存活会话接管与创建碰撞探测共用此查找。协调器会断言返回的 id并在修复或发布状态之前拒绝已存储记录与存活会话的 cwd 不匹配
- `appendBatch(meta, events, isMaterialized)`——持久追加一个连续批次,在尚未物化时原子地惰性物化会话(物化写入与首批事件必须一起提交——崩溃不得留下一个已物化但为空的会话;这就是为什么没有单独的 `materialize` 钩子)。
- `commitRepair(meta, tornMarker, closers)`——使崩溃修复持久化:截断损坏的尾部(当且仅当 `tornMarker !== undefined`)并追加 `closers`。**不要求原子性**——JSONL 合理地分两步 fsync先截断再追加SQLite 在一个事务中完成 DELETE+INSERT。用于 `load`(截断 + 合成 closers和 live-adoption仅截断`closers = []`)。
- `list()`——列出所有已存储的元数据。
@@ -39,8 +38,8 @@ Status: implemented
## 曾考虑的替代方案
- **后端继承的基类**——否决,改用组合:后端只暴露钩子,无法触及协调器的私有编排状态,且第三方后端仍可完全不使用协调器、直接实现抽象服务。
- **更宽的钩子面**——每个候选钩子都被折叠掉:没有单独的 `materialize` 钩子(物化写入必须在 `appendBatch` 内与首批事件原子提交;没有单独的创建碰撞探测(即 `loadStored(id) !== undefined``list()` 也不经由协调器透传列举不需要任何编排
- **更宽的钩子面**——每个候选钩子都被折叠掉:没有限定存储范围的实时查找,因为 `loadStored` 加上协调器的 cwd 检查即可维持碰撞边界;没有存储定位器泛型,因为经验证的 JSONL 元数据可还原其路径,而 SQLite 已按 id 绑定;没有单独的 `materialize` 钩子,因为首批事件必须与物化原子提交;没有单独的创建碰撞探测,因为它就是 `loadStored(id) !== undefined``list()` 也不经由协调器透传,因为列举不需要任何编排。
## 后果
协调器增加了一层间接、一个不透明的 torn marker 和脱离会话生命周期的退役任务,但将此前每个后端重复的、对正确性要求很高的编排逻辑集中到一处。会话 dispose 仍是仅观察事件,因此会话所有者不会等待持久化退役;协调器会收容失败、保留未提交的缓冲区,并以后端 teardown 为静止状态边界。其钩子面保持窄小:碰撞检查复用 `loadStored`物化保持在 `appendBatch` 内原子完成列举绕过协调器。新后端只需实现存储原语,而无需复制事件-缓冲区-flush 生命周期。
协调器增加了一层间接、一个不透明的 torn marker 和脱离会话生命周期的退役任务,但将此前每个后端重复的、对正确性要求很高的编排逻辑集中到一处。会话 dispose 仍是仅观察事件,因此会话所有者不会等待持久化退役;协调器会收容失败、保留未提交的缓冲区,并以后端 teardown 为静止状态边界。其钩子面保持窄小:标识校验、接管与碰撞检查复用 `loadStored`物化保持在 `appendBatch` 内原子完成列举绕过协调器。新后端只需实现存储原语,而无需复制事件-缓冲区-flush 生命周期。

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-20-prune-dead-seam-methods.md: bb91194ed0483ca4c43acdde8370e7152ac876ea
2026-06-20-prune-dead-seam-methods.zh.md: f441ac0d91b4f673cbbbd4148c9183cc1185a54f
2026-06-20-prune-dead-seam-methods.md: 70596d908bb1d7559e876d93bce0e25874ce1ff0
2026-06-20-prune-dead-seam-methods.zh.md: 0953eaa57dc9b490a7399a412338fc21a06a0ad2

View File

@@ -14,7 +14,7 @@ Status: implemented
该抽象服务在 create/append 之外声明了更多操作:`load``list``has``delete``ctx.sessionPersistence` 的生产消费方只用了两个agent loop智能体循环的恢复路径调用 `load()`[packages/core/agent-loop/src/index.ts:176](../../../../packages/core/agent-loop/src/index.ts)ACPAgent Client Protocol桥接层为 `session/list` 调用 `list()`[packages/ui/acp/src/index.ts:494](../../../../packages/ui/acp/src/index.ts))。在 `packages/*/src``examples/` 中 grep 所有 `sessionPersistence.*` / `persistence.*` 的使用,找不到对该服务的 `has(``delete(` 调用。`packages/ui/acp/src/index.ts` 中的 `.has(`/`.delete(` 调用作用于内存中的 `SessionStore` 和一个本地的 loading id `Set`,而非 persistence。`has`/`delete` 的唯一调用者是契约测试套件和各后端的 spec。
`has()` 不仅没有被使用——它还是共享协调器中最复杂的分支:带有多行理由说明的“已跟踪/未跟踪”双重探测(对实时跟踪的 session 使用 `loadLive(id, cwd)`,对未跟踪 session 使用 `loadStored(id)``delete()` 则拖入每个后端都必须实现的 `deleteStored` 后端 hook。这属于[删除可变 session summary](2026-06-19-drop-mutable-session-summary.md) 的同类模式:契约测试覆盖了两者,但已发布代码从不会询问“这个 session 是否已持久化?”或删除某个 session。
`has()` 不仅被使用:在 `loadStored(id)` 已负责持久化存在性检查的情况下,它仍增加了协调器的已跟踪/未跟踪探测和一个契约分支`delete()` 则拖入每个后端都必须实现的 `deleteStored` 后端 hook。这属于[删除可变 session summary](2026-06-19-drop-mutable-session-summary.md) 的同类模式:契约测试覆盖了两者,但已发布代码从不会询问“这个 session 是否已持久化?”或删除某个 session。
## 决策