@deepseek-ai/dsh-session-persistence 0.1.5-rc.1 → 0.1.6-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/session/session-persistence/README.md
5
- README.md: f0243b7a38dda8d9296a0a1416c9c8a9a990df88
6
- README.zh.md: 47c6f4e9a4fede80b2869ba6d1c986c9b144632a
5
+ README.md: 67ace6d102c0d58855c69987883208e819108ec8
6
+ README.zh.md: f2f5e5f7d0c16e66b08a3fc2f65ed7efe447b128
package/README.md CHANGED
@@ -146,7 +146,7 @@ Persistence does not mutate live request prefixes. A resumed loop can reuse prov
146
146
 
147
147
  These limits define where the seam's guarantees stop. They are current package constraints, not a task backlog.
148
148
 
149
- - **Write ownership is in-process only** — the provider's writer table excludes a second writer inside one backend instance; the durable cross-process lease is the planned next layer on the same handle shape, and until it lands another process must not write the same session.
149
+ - **The seam guarantees write ownership only within one backend instance** — cross-process exclusion is provider-specific. The shipped JSONL provider adds a kernel-backed lease across instances and processes; another provider must document an equivalent guarantee or require deployments to prevent concurrent writers.
150
150
  - **A backend plugin reload under live sessions fails their writers loudly** — a reloaded backend cannot serve handles the old instance issued; writes fail until the sessions restart, and nothing silently re-adopts the logs.
151
151
  - **Only handle-acquired sessions persist** — `ctx.sessions.create` + `session/flush` alone stores nothing; agent-loop is the production acquisition point, and tests seed storage through `create`/`append`/`close`.
152
152
  - **No deletion or retention API** — pruning stored sessions is out-of-band backend maintenance.
package/README.zh.md CHANGED
@@ -29,7 +29,7 @@ kind: "package-reference"
29
29
 
30
30
  ### 选择后端
31
31
 
32
- seam 随产品交付 [JSONL](../session-persistence-jsonl/README.zh.md) 后端。它把每个 Session 存为一份仅追加 `.jsonl.zstd` 产物。第三方后端可以直接实现该服务;必须遵守的[后端约定](#understand-the-implementation)见下文。
32
+ seam 随产品交付 [JSONL](../session-persistence-jsonl/README.zh.md) 后端。它为每个会话存储一份仅追加的 `.jsonl.zstd` 日志。第三方后端可以直接实现该服务;必须遵守的[后端约定](#understand-the-implementation)见下文。
33
33
 
34
34
  ### 服务提供什么
35
35
 
@@ -46,7 +46,7 @@ await ctx.sessionPersistence.flush() // backend-wide d
46
46
 
47
47
  服务级 `flush()` 排空每个活跃写句柄已路由的事件并把其会话实体化,效果与各句柄自己的 `flush` 完全相同;失败按会话聚合为一个 `AggregateError` 而不中途放弃清扫,清扫途中被关闭的句柄视同已 flush,因为 close 本身会持久排空。
48
48
 
49
- 每一次日志读写都流经返回的 `SessionHandle`;不存在按 id 寻址的 append 或 load 方法。`handle.read(offset?, length?)` 返回 `{ eventState, events }`:外层 slice 属于调用方,`eventState` 则区分独占的 `detached` event graph 与可能同时保存在 backend cache 中的 `shared-frozen` graph。该状态由生产者建立,slice 即使为空也会保留原状态。两种状态都能直接接管而无需复制;需要修改 event 的 consumer 必须先 clone。Read 绝不包含撕裂尾部,同一句柄上的重复读取绝不会观察到比先前读取更旧的状态,写句柄也能读到自己成功的 append。`handle.append(events)` 追加一个连续批次,其第一个 `seq` 等于已存储 next-seq;完成时的持久化是尽力而为的——批次被接受、有序,并对同一后端实例上的读取可见,只有完成的 `flush` 才承诺它在崩溃后依然存在(交付的 JSONL 后端恰好会立即持久化每个批次)。`handle.flush()` 是持久性屏障,同时把空的已创建会话实体化,使其可被持久列出。`handle.close()` 幂等且不可取消:读句柄释放本地资源;写句柄完成待处理的持久化并释放写所有权。一旦某次 `append` 或 `flush` 完成,其后在同一后端实例上开始的读取——无论经由任何句柄,还是经由 `stat`/`list`——至少能观察到该前缀。
49
+ 每一次日志读写都流经返回的 `SessionHandle`;不存在按 id 寻址的 append 或 load 方法。`handle.read(offset?, length?)` 返回 `{ eventState, events }`:外层 slice 属于调用方,`eventState` 则区分由调用方独占的 `detached` 事件图与可能同时位于后端缓存中的 `shared-frozen` 事件图。该状态由生成方确定,即使切片为空也会保留。两种状态都能直接接管而无需复制;需要可变事件的消费方必须先克隆事件。读取绝不包含撕裂尾部,同一句柄上的重复读取绝不会观察到比先前读取更旧的状态,写句柄也能读到自己成功的 append。`handle.append(events)` 追加一个连续批次,其第一个 `seq` 等于已存储 next-seq;完成时的持久化是尽力而为的——批次被接受、有序,并对同一后端实例上的读取可见,只有完成的 `flush` 才承诺它在崩溃后依然存在(交付的 JSONL 后端恰好会立即持久化每个批次)。`handle.flush()` 是持久性屏障,同时把空的已创建会话实体化,使其可被持久列出。`handle.close()` 幂等且不可取消:读句柄释放本地资源;写句柄完成待处理的持久化并释放写所有权。一旦某次 `append` 或 `flush` 完成,其后在同一后端实例上开始的读取——无论经由任何句柄,还是经由 `stat`/`list`——至少能观察到该前缀。
50
50
 
51
51
  ### 所有权与可见性
52
52
 
@@ -54,7 +54,7 @@ await ctx.sessionPersistence.flush() // backend-wide d
54
54
 
55
55
  ### 实时写路径与关闭排空
56
56
 
57
- 实时写路径由后端自持:它一次性安装会话监听器,把每个已发布会话的事件按 id 路由到该会话的活跃写句柄——`session/event` 复制进有界的内部批处理窗口,`session/flush` 是即时的持久性与错误观察屏障,`session/disposed` 执行最终排空并关闭句柄。没有活跃写句柄的已发布会话不做任何持久化。后台写入失败时按序保留其事件、暂停自动路径并记入日志;下一次显式 flush 会重试并响亮地拒绝。`close()` 本身会先经由仍然打开的存储排空路由缓冲区再释放所有权,因此即便根 fiber 的 dispose 并发运行各 fiber 的 disposer,后端 teardown 的关闭清扫也能保证应用关闭不丢数据。
57
+ 实时写路径由后端自持:它一次性安装会话监听器,把每个已发布会话的事件按 id 路由到该会话的活跃写句柄——`session/event` 复制进有界的内部批处理窗口,`session/flush` 是即时的持久性与错误观察屏障,`session/disposed` 执行最终排空并关闭句柄。没有活跃写句柄的已发布会话不做任何持久化。后台写入失败时按序保留其事件、暂停自动路径并记入日志;下一次显式 flush 会重试,并在再次失败时明确返回拒绝。`close()` 本身会先经由仍然打开的存储排空路由缓冲区再释放所有权,因此即便根 fiber 的 dispose(资源释放)并发运行各 fiber 的 disposer,后端拆卸时的关闭清扫也能保证应用关闭不丢数据。
58
58
 
59
59
  ### 恢复与崩溃恢复
60
60
 
@@ -62,7 +62,7 @@ await ctx.sessionPersistence.flush() // backend-wide d
62
62
 
63
63
  ### 失败与恢复
64
64
 
65
- 当前构建无法忠实解读的存储日志会以方向感知的错误被拒绝,绝不错读。`SessionHandle` 只暴露由 `SESSION_FORMAT_VERSION` 标识的当前逻辑记录;provider 必须在返回句柄前转换任何受支持的历史存储,随产品交付的 JSONL provider 会通过静态 catalog 迁移受支持的历史代际。更高格式会要求操作者升级 harness。本构建不认识的事件类型会被拒绝,除非其信封标记为 `ignorable`;已提交前缀中的损坏以 `SessionPersistenceCorruptionError` 拒绝。
65
+ 当前构建无法忠实解读的存储日志会被拒绝,并返回指明拒绝方向的错误,绝不会被误读。`SessionHandle` 只暴露由 `SESSION_FORMAT_VERSION` 标识的当前逻辑记录;提供方必须在返回句柄前转换任何受支持的历史存储,随产品交付的 JSONL 提供方会通过静态 catalog 迁移受支持的历史代际。更新的格式会要求操作者升级 harness。本构建不认识的事件类型会被拒绝,除非其信封标记为 `ignorable`;已提交前缀中的损坏以 `SessionPersistenceCorruptionError` 拒绝。
66
66
 
67
67
  -----
68
68
 
@@ -76,7 +76,7 @@ await ctx.sessionPersistence.flush() // backend-wide d
76
76
 
77
77
  ### 设计理念
78
78
 
79
- 本包是 seam,而不是后端框架:它只导出抽象 `SessionPersistence` 服务、`SessionHandle` 约定、消费方捕获的稳定 error 类、纯函数的存储记录校验辅助(`storage-contract`)以及带品牌类型的 revision——再无其他。每个 provider 拥有自己完整的存储运行时(句柄类、修改排序、单写者记账、实时事件路由、teardown),`tests/` 下的两套共享测试套件——`runPersistenceContract` 与 `runLiveWritePathContract`——钉住每个 provider 都必须一致的可观察行为。有意为之的后果:各 provider 在存储恰好相似之处可以彼此相像,但没有任何实现机制跨越包边界。
79
+ 本包是 seam,而不是后端框架:它只导出抽象 `SessionPersistence` 服务、`SessionHandle` 约定、消费方捕获的稳定错误类、纯函数的存储记录校验辅助(`storage-contract`)以及带品牌类型的修订值——再无其他。每个提供方拥有自己完整的存储运行时(句柄类、修改排序、单写者记账、实时事件路由、拆卸),`tests/` 下的两套共享测试套件——`runPersistenceContract` 与 `runLiveWritePathContract`——固定所有提供方都必须一致的可观察行为。有意为之的后果:各提供方在存储恰好相似之处可以彼此相像,但没有任何实现机制跨越包边界。
80
80
 
81
81
  ### 每个后端必须遵守的不变量
82
82
 
@@ -84,8 +84,8 @@ await ctx.sessionPersistence.flush() // backend-wide d
84
84
  - **撕裂的物理尾部绝不到达读取方。** 它属于一次从未完成的 append;写路径在第一次新 append 之前将其持久截断。
85
85
  - **无损 JSON 数据。** 批次与 header 经过共享的单遍校验并快照边界(`materializeAppendBatch`/`materializeCreateHeader`);无法序列化的载荷在调用处被拒绝。
86
86
  - **持久性。** `append` 尽力而为地持久化;`flush`——逐句柄或服务级——是承诺存储并同时把空会话实体化的屏障。
87
- - **失败即关闭的读取。** `validateStoredEvents` 拒绝未知事件词汇与已废弃的预发布形态;`assertVersion` 拒绝外来格式版本。
88
- - **每个后端实例单写者。** provider 的进程内认领在 `create`/`open('write')` 时取得,在句柄关闭时释放。
87
+ - **遇到未知或无效格式时拒绝读取。** `validateStoredEvents` 拒绝未知事件词汇与已废弃的预发布形态;`assertVersion` 拒绝外来格式版本。
88
+ - **每个后端实例单写者。** 提供方的进程内认领在 `create`/`open('write')` 时取得,在句柄关闭时释放。
89
89
 
90
90
  ### 源码地图
91
91
 
@@ -93,18 +93,18 @@ await ctx.sessionPersistence.flush() // backend-wide d
93
93
  |---|---|
94
94
  | [`src/index.ts`](src/index.ts) | 插件入口:抽象 `SessionPersistence` 服务与重新导出的 seam 词汇 |
95
95
  | [`src/handle.ts`](src/handle.ts) | `SessionHandle` 约定:read/append/flush/close 语义与新鲜度规则 |
96
- | [`src/storage-contract.ts`](src/storage-contract.ts) | 共享校验:版本门、失败即关闭词汇表、批次实体化、连续性 |
96
+ | [`src/storage-contract.ts`](src/storage-contract.ts) | 共享校验:版本门禁、未知事件词汇拒绝、批次实体化、连续性 |
97
97
  | [`src/errors.ts`](src/errors.ts) | 稳定的句柄/所有权失败与格式拒绝 |
98
98
  | [`src/revision.ts`](src/revision.ts) | 带品牌类型的不透明修订值 token |
99
99
  | — | 不发布运行时不变式伴生入口;持久化正确性需要后端往返与崩溃尾部测试;本包不暴露可持续观察的进程内关系。 |
100
100
 
101
101
  ### 写入路径概览
102
102
 
103
- 写入器会话的每个 `session/event` 都复制进该句柄的内部缓冲。第一个待处理事件开启固定批处理窗口;后续事件加入但不重置截止时间。窗口到期后经由句柄的修改链排空待处理前缀;排空期间接纳的事件按顺序合并进下一个链上的批次。`session/flush` 取消等待并排空至完全停稳,随后运行 `handle.flush()`,因此 loop 在下一轮次前把它用作排序与错误观察检查点。被拒绝的后台排空保留其事件并暂停自动计时器;显式 flush、写入器 close 或后端 teardown 会立即重试并响亮地拒绝。构造 seed 事件绝不发出 `session/event`,因此发布前通过句柄追加的 seed 绝不会被重新入队。
103
+ 写入器会话的每个 `session/event` 都复制进该句柄的内部缓冲。第一个待处理事件开启固定批处理窗口;后续事件加入但不重置截止时间。窗口到期后经由句柄的修改链排空待处理前缀;排空期间接纳的事件按顺序合并进下一个链上的批次。`session/flush` 取消等待并排空至完全停稳,随后运行 `handle.flush()`,因此 loop 在下一轮次前把它用作排序与错误观察检查点。失败的后台排空保留其事件并暂停自动计时器;显式 flush、写入器 close 或后端拆卸会立即重试,并在再次失败时明确返回拒绝。构造 seed 事件绝不发出 `session/event`,因此发布前通过句柄追加的 seed 绝不会被重新入队。
104
104
 
105
105
  ### 存储记录校验
106
106
 
107
- seam 的共享辅助函数校验由 `SESSION_FORMAT_VERSION` 标识的当前逻辑记录,append 只写当前格式([理由](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md))。历史解码与不可变后继发布属于各 provider 内部,并在其返回句柄前完成。每个后端都在句柄读取与写 open 预热时运行 `storage-contract` 校验,把未知事件类型作为 `SessionFormatUnsupportedError` 拒绝,把 malformed 当前记录作为 `SessionPersistenceCorruptionError` 拒绝,并在后端为每个会话保留一份产物时附上原始日志的 `SessionLocation`。
107
+ seam 的共享辅助函数校验由 `SESSION_FORMAT_VERSION` 标识的当前逻辑记录,append 只写当前格式([理由](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md))。历史解码与不可变后继发布属于各提供方内部,并在其返回句柄前完成。每个后端都在句柄读取与写 open 预热时运行 `storage-contract` 校验,把未知事件类型作为 `SessionFormatUnsupportedError` 拒绝,把格式错误的当前记录作为 `SessionPersistenceCorruptionError` 拒绝,并在后端为每个会话保留一份产物时附上原始日志的 `SessionLocation`。
108
108
 
109
109
  </details>
110
110
  -----
@@ -137,18 +137,18 @@ seam 不添加提示词或 schema。恢复会将已存储的表层事件还原
137
137
 
138
138
  #### KV Cache 影响
139
139
 
140
- 持久化不修改实时请求前缀。只有当重建历史、当前 envelope 与模型路由匹配时,恢复 loop 才能重用提供方缓存;崩溃修复结果仅追加,不重写较早历史。
140
+ 持久化不修改当前请求前缀。只有当重建历史、当前 envelope 与模型路由匹配时,恢复 loop 才能重用提供方缓存;崩溃修复结果仅追加,不重写较早历史。
141
141
 
142
142
  ## 已知限制与延期工作
143
143
 
144
144
  <a id="known-limitations-and-deferred-work"></a>
145
145
 
146
146
 
147
- 这些限制界定 seam 保证的终点。它们是当前包约束,不是任务积压。
147
+ 这些限制界定 seam 保证的终点。它们是当前包约束,不是待办事项。
148
148
 
149
- - **写所有权仅限进程内**——provider 的写入器表只在单个后端实例内排除第二个写入方;持久的跨进程租约是计划在同一句柄形态上叠加的下一层,在它落地之前另一进程不得写入同一会话。
150
- - **在有活跃会话时重载后端插件会使其写入器响亮地失败**——重载后的后端无法服务旧实例签发的句柄;写入会持续失败直到会话重启,没有任何机制静默重新接管日志。
151
- - **只有通过句柄获取的会话才会持久化**——仅靠 `ctx.sessions.create` + `session/flush` 不存储任何内容;agent-loop 是生产环境的获取点,测试通过 `create`/`append`/`close` 播种存储。
149
+ - **seam 只保证单个后端实例内的写所有权**——跨进程排他由具体提供方负责。随产品交付的 JSONL 提供方通过内核锁在不同实例和进程之间提供租约;其他提供方必须记录等效保证,或要求部署方阻止并发写入。
150
+ - **在有活跃会话时重载后端插件会使其写入器明确报错**——重载后的后端无法服务旧实例签发的句柄;写入会持续失败直到会话重启,没有任何机制静默重新接管日志。
151
+ - **只有通过句柄获取的会话才会持久化**——仅靠 `ctx.sessions.create` + `session/flush` 不存储任何内容;agent-loop 是生产环境的获取点,测试通过 `create`/`append`/`close` 写入初始存储数据。
152
152
  - **无删除或保留接口**——剪枝已存储会话属于带外后端维护。
153
153
  - **`list()` 无分页且无过滤**——它返回每个已存储会话的快照;适合本地存储,大规模时无索引。
154
154
  - **合成 closer 是唯一崩溃方案**——恢复通过写句柄追加 `interruptedTurnClosers`;没有继续中断轮次而不先关闭它的部分轮次恢复。
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-session-persistence",
3
3
  "description": "Abstract durable session persistence seam (ctx.sessionPersistence) for the DeepSeek Harness",
4
- "version": "0.1.5-rc.1",
4
+ "version": "0.1.6-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,19 +27,19 @@
27
27
  ],
28
28
  "license": "MIT",
29
29
  "peerDependencies": {
30
- "@deepseek-ai/dsh-brand": "^0.1.5-rc.1",
30
+ "@deepseek-ai/dsh-brand": "^0.1.6-alpha.1",
31
31
  "@deepseek-ai/cordis": "^4.0.2",
32
- "@deepseek-ai/dsh-session": "^0.1.5-rc.1",
33
- "@deepseek-ai/dsh-timeout": "^0.1.5-rc.1"
32
+ "@deepseek-ai/dsh-session": "^0.1.6-alpha.1",
33
+ "@deepseek-ai/dsh-timeout": "^0.1.6-alpha.1"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@deepseek-ai/cordis": "^4.0.2",
37
- "@deepseek-ai/dsh-brand": "^0.1.5-rc.1",
38
- "@deepseek-ai/dsh-scope": "^0.1.5-rc.1",
39
- "@deepseek-ai/dsh-session": "^0.1.5-rc.1",
40
- "@deepseek-ai/dsh-timeout": "^0.1.5-rc.1"
37
+ "@deepseek-ai/dsh-brand": "^0.1.6-alpha.1",
38
+ "@deepseek-ai/dsh-scope": "^0.1.6-alpha.1",
39
+ "@deepseek-ai/dsh-session": "^0.1.6-alpha.1",
40
+ "@deepseek-ai/dsh-timeout": "^0.1.6-alpha.1"
41
41
  },
42
42
  "dependencies": {
43
- "@deepseek-ai/dsh-util-values": "^0.1.5-rc.1"
43
+ "@deepseek-ai/dsh-util-values": "^0.1.6-alpha.1"
44
44
  }
45
45
  }