@x1a0f3n9/dsh-session-persistence 0.1.2-alpha.6

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/session/session-persistence/README.md
5
+ README.md: 6cb03e0bdef87d0d82a3e397eeaefb0498b7e371
6
+ README.zh.md: adb8b77954356547717a10655f8a4f804d8f9769
package/README.md ADDED
@@ -0,0 +1,147 @@
1
+ ---
2
+ description: "The durable session-storage seam for users and maintainers choosing a persistence backend, resuming sessions, or building a backend against the shared service contract."
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @x1a0f3n9/dsh-session-persistence
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ `dsh-session-persistence` stores a session's event log durably, reloads it on resume, and lists stored sessions through the backend-neutral `ctx.sessionPersistence` service. During normal operation the log is append-only; the destructive turn-tail deletion API rewrites durable storage before truncating the live log. The persisted unit is the existing `SessionEvent` log — there is no parallel stored message type. `SessionHeader.isSeeded` makes lineage visible to lightweight listing, while the exact `inheritedEventCount` accompanies every body-bearing storage read and prepared Session. A backend owns its storage, while the service owns append-only logs, contiguous sequence numbers, crash recovery that preserves an interrupted turn instead of truncating it, and durable writes that resolve only after the batch is safe. The shipped JSONL provider implements this service with one artifact per Session; third-party providers may implement the same contract without changing the loop or model.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ Mount one persistence backend to make sessions durable. The backend registers itself as `ctx.sessionPersistence`; nothing else in the composition changes — the loop, resume, and replay all call the same service.
29
+
30
+ ### Choosing a backend
31
+
32
+ The seam ships the [JSONL](../session-persistence-jsonl/README.md) backend. It stores one append-only `.jsonl.zstd` artifact per Session and returns its absolute path from `locate(meta)`. A third-party backend may implement the service directly; the [backend contract](#understand-the-implementation) below is what it must honor.
33
+
34
+ ### What the service provides
35
+
36
+ With a backend mounted, you can store a session's events durably, reload the stored log, and list what is stored:
37
+
38
+ ```text
39
+ await ctx.sessionPersistence.create(meta, inheritedEventCount) // cut required when meta.isSeeded
40
+ await ctx.sessionPersistence.ensureMaterialized(session) // persist an empty resumable session
41
+ await ctx.sessionPersistence.append(id, events) // durably persist a batch
42
+ const { meta, inheritedEventCount, events } = await ctx.sessionPersistence.load(id)
43
+ const headers = await ctx.sessionPersistence.list() // every stored session
44
+ ```
45
+
46
+ `append` resolves only after the batch is durable, so a resolved write survives an OS crash or power loss. Ordinary `create(meta, inheritedEventCount)` remains lazy; `meta.isSeeded: true` requires the sibling exact cut, while unseeded metadata may omit it and rejects a nonzero value. The first materializing batch for a seeded session must reach the complete inherited prefix, so storage never exposes metadata whose cut exceeds its log. A lifecycle frontend calls `ensureMaterialized` only when an empty session must itself appear in durable listing without inventing an event. `load` returns an immutable balanced log and commits any needed crash recovery; `inspect` reads the same complete view without committing recovery. `readFrom` accepts a `SessionLogOffset` and returns a detached `SessionEventSuffix` carrying that `fromSeq`, the unchanged inherited cut, and only stored events at or after the cut. A session's artifact location (`locate`) resolves without filesystem I/O.
47
+
48
+ ### Resuming and crash recovery
49
+
50
+ Resume is `load` plus session preparation: the stored log comes back with its header lineage and exact inherited cut intact, so ownership checks do not infer the cut from a marker or the full restore length. A session that crashed mid-turn reloads with its interrupted final turn preserved and balanced: `load` appends synthetic `tool/result` and `turn/end {interrupted}` closers for unanswered calls instead of dropping the events — a single turn can be large, and those events were durably written before the crash. Only a never-fully-written torn tail fragment is discarded.
51
+
52
+ ### Failures and recovery
53
+
54
+ A stored log the current build cannot faithfully interpret is refused with a direction-aware error, never misread. `SESSION_FORMAT_VERSION` remains v0 and this build provides no format-migration path; a newer version instructs the operator to upgrade the harness. The decoder accepts only the bounded same-version record variants named below. An event type unknown to this build refuses unless its envelope marks it `ignorable`, and committed-prefix corruption rejects as `SessionPersistenceCorruptionError`. A `load` on an id still bound to a live session first flushes its snapshot and rejects while its turn is open; a cold load applies recovery.
55
+
56
+ -----
57
+
58
+ <a id="understand-the-implementation"></a>
59
+ ## Understand the implementation
60
+
61
+ <details>
62
+ <summary>Implementation internals — click to expand</summary>
63
+
64
+ This section explains how the seam realizes durable storage and how backends plug in; the observable contract is covered in [Use this package](#use-this-package) and the generated [Cordis API](../../../docs/subsystems/persistence.md#cordis-surface).
65
+
66
+ ### Design concept
67
+
68
+ The package is the Service Definition of a capability seam with two halves. The abstract `SessionPersistence` service is the public contract; a `PersistenceCoordinator` provides backend-neutral orchestration for buffering, serialization, materialization, repair, adoption, and quiescent disposal. The JSONL provider implements the small durable primitives for stored reads, append, repair, and listing; a third-party provider may reuse the same coordinator or implement the service directly.
69
+
70
+ ### The invariants every backend honors
71
+
72
+ - **Append-only; a crashed turn is closed, not truncated.** Flushed events are never rewritten; `load` preserves an interrupted final turn and durably appends synthetic closers.
73
+ - **Contiguous `seq`.** A gap in the middle of the log rejects; `append`'s first `seq` must equal the stored next-seq.
74
+ - **Lossless JSON data.** Batches pass the shared one-pass lossless-JSON boundary; non-serializable payloads reject at the append site.
75
+ - **Durability.** `append` resolves only once the batch is durable.
76
+
77
+ ### Source map
78
+
79
+ | File | Role |
80
+ |---|---|
81
+ | [`src/index.ts`](src/index.ts) | Plugin entry: the abstract `SessionPersistence` service and re-exported metadata types |
82
+ | [`src/coordinator.ts`](src/coordinator.ts) | Shared write orchestration: batching, serialization, repair, adoption, disposal, format refusal |
83
+ | [`src/write-behind.ts`](src/write-behind.ts) | The per-session bounded write controller and flush barrier |
84
+ | [`src/preparations.ts`](src/preparations.ts) | Bounded retention of unpublished Session preparations for resume reuse |
85
+ | [`src/revision.ts`](src/revision.ts) | The branded opaque revision token |
86
+ | — | No runtime invariant companion is published; persistence correctness requires backend round-trip and crash-tail tests; this package exposes no continuously observable in-process relation. |
87
+
88
+ ### The write path at a glance
89
+
90
+ Each `session/event` copies the event into its session's controller. The first pending event starts a fixed batching window; later events join without resetting its deadline. Expiry starts one durable append; events admitted during that write form a separately bounded follow-up batch. `session/flush` cancels the wait and drains through quiescence, so the loop uses it as the ordering and error-observation checkpoint before the next turn. A rejected background write retains its events and pauses automatic retry; a new event starts a fresh window, while explicit flush or backend teardown retries immediately.
91
+
92
+ ### Stored-record compatibility
93
+
94
+ Backend reads normalize only the explicitly supported v0 record variants before validating current records. The coordinator uses the same normalized view for `load`, `inspect`, `readFrom`, ownerless-state claims, and HMR adoption. Reads do not rewrite stored records, and later appends use current v0. The [pre-identity message](../../../.agents/notes/implemented/bug-fix/2026-07-28-load-pre-identity-session-messages.md) and [pre-react-loop session](../../../.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.md) notes own these bounded exceptions; they are not a general format-migration promise.
95
+
96
+ </details>
97
+ -----
98
+
99
+ <a id="further-exploration"></a>
100
+ ## Further Exploration
101
+
102
+ Read these pages when the package-level contract is not enough. They move from the shared durability model to the shipped backends and the decision evidence.
103
+
104
+ - [Session persistence subsystem](../../../docs/subsystems/persistence.md) — the full service contract, flush checkpoint, crash recovery, and generated Cordis API.
105
+ - [JSONL persistence backend](../session-persistence-jsonl/README.md) — the shipped per-session-file backend.
106
+ - [Session checkpoint policy](../session-checkpoint-policy/README.md) — the plugin that flushes through this service at semantic boundaries.
107
+ - [Session package map](../README.md) — adjacent persistence, projection, title, and telemetry packages.
108
+
109
+ -----
110
+
111
+ <a id="model-experience"></a>
112
+ ## Model Experience
113
+
114
+ ### Resumed conversation history
115
+
116
+ #### What the model sees
117
+
118
+ The seam adds no prompt or schema. Resume restores stored surface events as message history; stored request headers reconstruct earlier calls, while the new loop composes the current system prompt, tools, and session prefix for its next request. Crash repair marks an assistant request without a durable call as `TOOL_NOT_STARTED`; a durable call without a result becomes `TOOL_OUTCOME_UNKNOWN`, whose text lets the model retry read-only or idempotent work but directs it to verify side effects or ask the user instead of retrying blindly.
119
+
120
+ #### Token effect
121
+
122
+ Zero tokens during ordinary persistence. Resume restores retained history cost and pays the current request envelope normally; each repaired call adds the quoted retained error text.
123
+
124
+ #### KV Cache effect
125
+
126
+ Persistence does not mutate live request prefixes. A resumed loop can reuse provider cache only when its reconstructed history, current envelope, and model route match; crash-repair results append without rewriting earlier history.
127
+
128
+ ## Known Limitations and Deferred Work
129
+
130
+ <a id="known-limitations-and-deferred-work"></a>
131
+
132
+
133
+ These limits define where the seam's guarantees stop. They are current package constraints, not a task backlog.
134
+
135
+ - **No deletion or retention API** — pruning stored sessions is out-of-band backend maintenance.
136
+ - **`list()` is unpaginated and unfiltered** — it returns every stored session's header; fine for local stores, unindexed at scale.
137
+ - **Synthetic closers are the only crash story** — a backend must synthesize `tool/result`/`step/end`/`turn/end` closers on load; there is no partial-turn resume that continues an interrupted turn instead of closing it.
138
+
139
+ <a id="dev-note"></a>
140
+ ### Dev Note
141
+
142
+ <details>
143
+ <summary>Working context for maintainers — click to expand</summary>
144
+
145
+ None.
146
+
147
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,147 @@
1
+ ---
2
+ description: "面向用户与维护者的持久会话存储 seam 说明,用于选择持久化后端、恢复会话,或按共享服务约定构建后端。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @x1a0f3n9/dsh-session-persistence
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ `dsh-session-persistence` 通过后端无关的 `ctx.sessionPersistence` 服务持久存储会话的事件日志、在恢复时重新加载并列出已存储会话。 正常写入时日志只追加;破坏性删除轮次尾部会先重写持久化数据,再截断内存日志。持久化单元就是现有 `SessionEvent` 日志——不存在另一套并行的存储消息类型。`SessionHeader.isSeeded` 让轻量列表可见血缘,而精确的 `inheritedEventCount` 随每次带正文的存储读取与 prepared Session 一同传输。后端拥有自己的存储,而服务拥有仅追加日志、连续序列号、保留中断轮次而非截断的崩溃恢复,以及只在批次安全后才返回的持久写入。随产品交付的 JSONL provider 用每个 Session 一份产物实现该服务;第三方 provider 可以实现同一约定,而不改变 loop 或模型。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ 挂载一个持久化后端即可让会话持久化。后端把自己注册为 `ctx.sessionPersistence`;组合中的其他部分不变——loop、恢复与回放调用的是同一个服务。
29
+
30
+ ### 选择后端
31
+
32
+ seam 随产品交付 [JSONL](../session-persistence-jsonl/README.zh.md) 后端。它把每个 Session 存为一份仅追加 `.jsonl.zstd` 产物,并由 `locate(meta)` 返回绝对路径。第三方后端可以直接实现该服务;必须遵守的[后端约定](#understand-the-implementation)见下文。
33
+
34
+ ### 服务提供什么
35
+
36
+ 挂载后端后,你可以持久存储会话事件、重新加载已存储日志并列出已存储内容:
37
+
38
+ ```text
39
+ await ctx.sessionPersistence.create(meta, inheritedEventCount) // cut required when meta.isSeeded
40
+ await ctx.sessionPersistence.ensureMaterialized(session) // persist an empty resumable session
41
+ await ctx.sessionPersistence.append(id, events) // durably persist a batch
42
+ const { meta, inheritedEventCount, events } = await ctx.sessionPersistence.load(id)
43
+ const headers = await ctx.sessionPersistence.list() // every stored session
44
+ ```
45
+
46
+ `append` 只在批次持久后返回,因此成功返回的写入在操作系统崩溃或断电后依然存在。普通 `create(meta, inheritedEventCount)` 保持惰性;`meta.isSeeded: true` 要求单独的精确 cut,unseeded metadata 可以省略它并拒绝非零值。seeded 会话的首个物化批次必须到达完整继承前缀,因此存储绝不公开 cut 超过日志的 metadata。只有当空会话本身必须出现在持久列表中时,生命周期前端才调用 `ensureMaterialized`,且不会虚构事件。`load` 返回不可变的平衡日志并提交任何需要的崩溃恢复;`inspect` 读取同一份完整视图但不提交恢复。`readFrom` 接受 `SessionLogOffset`,并返回分离的 `SessionEventSuffix`,其中携带该 `fromSeq`、不变的继承 cut,以及 cut 位置或之后的存储事件。会话的产物位置(`locate`)不经文件系统 I/O 即可解析。
47
+
48
+ ### 恢复与崩溃恢复
49
+
50
+ 恢复就是 `load` 加会话准备:存储日志连同其 header 血缘与精确继承切点一起返回,因此所有权检查不从标记或完整恢复长度推断切点。中途崩溃的会话重新加载时,其被中断的最终轮次会保留并保持平衡:`load` 为未获回答的调用追加合成 `tool/result` 与 `turn/end {interrupted}` closer,而不是丢弃事件——单个轮次可能很大,而这些事件在崩溃前已持久写入。只有从未完整写入的撕裂尾部碎片会被丢弃。
51
+
52
+ ### 失败与恢复
53
+
54
+ 当前构建无法忠实解读的存储日志会以方向感知的错误被拒绝,绝不错读。`SESSION_FORMAT_VERSION` 保持 v0,本构建不提供格式迁移路径;更高版本会要求操作者升级 harness。解码器只接受下文点名的有限同版本记录变体。本构建不认识的事件类型会被拒绝,除非其信封标记为 `ignorable`;已提交前缀中的损坏以 `SessionPersistenceCorruptionError` 拒绝。对仍绑定到活动会话的 id 执行 `load`,会先刷新其快照并在轮次开放时拒绝;冷 load 应用恢复。
55
+
56
+ -----
57
+
58
+ <a id="understand-the-implementation"></a>
59
+ ## 理解实现
60
+
61
+ <details>
62
+ <summary>实现细节——点击展开</summary>
63
+
64
+ 本节说明 seam 如何实现持久存储以及后端如何接入;可观察约定见[使用本包](#use-this-package)与生成的 [Cordis API](../../../docs/subsystems/persistence.zh.md#cordis-surface)。
65
+
66
+ ### 设计理念
67
+
68
+ 本包是能力 seam 的 Service Definition,分两半。抽象的 `SessionPersistence` 服务是公开约定;`PersistenceCoordinator` 为缓冲、串行化、物化、修复、接管与完全停稳的 dispose 提供后端无关编排。JSONL provider 实现存储读取、追加、修复与列出所需的小型持久原语;第三方 provider 可以复用同一 coordinator,也可以直接实现该服务。
69
+
70
+ ### 每个后端必须遵守的不变量
71
+
72
+ - **仅追加;崩溃轮次会被关闭,而非截断。** 已 flush 事件绝不重写;`load` 保留中断的最终轮次并持久追加合成 closer。
73
+ - **连续 `seq`。** 日志中间的缺口会被拒绝;`append` 的第一个 `seq` 必须等于已存储 next-seq。
74
+ - **无损 JSON 数据。** 批次经过共享单遍无损 JSON 边界;无法序列化的载荷在 append 处被拒绝。
75
+ - **持久性。** `append` 只在批次持久后返回。
76
+
77
+ ### 源码地图
78
+
79
+ | 文件 | 职责 |
80
+ |---|---|
81
+ | [`src/index.ts`](src/index.ts) | 插件入口:抽象 `SessionPersistence` 服务与重新导出的元数据类型 |
82
+ | [`src/coordinator.ts`](src/coordinator.ts) | 共享写入编排:批处理、串行化、修复、接管、dispose、格式拒绝 |
83
+ | [`src/write-behind.ts`](src/write-behind.ts) | 每会话有界写入控制器与 flush 屏障 |
84
+ | [`src/preparations.ts`](src/preparations.ts) | 为恢复复用而有界保留的未发布 Session 准备结果 |
85
+ | [`src/revision.ts`](src/revision.ts) | 带品牌类型的不透明修订值 token |
86
+ | — | 不发布运行时不变式伴生入口;协调器断言存储/活动身份与 cwd。 |
87
+
88
+ ### 写入路径概览
89
+
90
+ 每个 `session/event` 把事件复制到其会话的 controller。第一个待处理事件开启固定批处理窗口;后续事件加入但不重置截止时间。窗口到期后启动一次持久追加;该次写入期间接纳的事件形成另一个独立有界的后续批次。`session/flush` 取消等待并排空至完全停稳,因此 loop 在下一轮次前把它用作排序与错误观察检查点。被拒绝的后台写入保留其事件并暂停自动重试;新事件开启新窗口,而显式 flush 或后端拆卸会立即重试。
91
+
92
+ ### 存储记录兼容
93
+
94
+ 后端读取只会在校验当前记录之前,规范化明确支持的 v0 记录变体。协调器对 `load`、`inspect`、`readFrom`、无所有者状态认领与 HMR 接管使用同一份规范化视图。读取不会重写已存记录,后续追加使用当前 v0。[消息标识机制引入前的消息](../../../.agents/notes/implemented/bug-fix/2026-07-28-load-pre-identity-session-messages.zh.md)与 [react-loop 引入前会话](../../../.agents/notes/implemented/bug-fix/2026-08-04-load-pre-react-loop-sessions.zh.md)笔记规定这些有限例外;它们不构成通用格式迁移承诺。
95
+
96
+ </details>
97
+ -----
98
+
99
+ <a id="further-exploration"></a>
100
+ ## 进一步探索
101
+
102
+ 当包级约定不够用时阅读以下页面。它们从共享持久性模型逐步进入随产品交付的后端与决策证据。
103
+
104
+ - [会话持久化子系统](../../../docs/subsystems/persistence.zh.md)——完整服务约定、flush 检查点、崩溃恢复与生成的 Cordis API。
105
+ - [JSONL 持久化后端](../session-persistence-jsonl/README.zh.md)——随产品交付、按会话存储文件的后端。
106
+ - [会话检查点策略](../session-checkpoint-policy/README.zh.md)——在语义边界上经由本服务刷新的插件。
107
+ - [会话包映射](../README.zh.md)——相邻的持久化、投影、标题与遥测包。
108
+
109
+ -----
110
+
111
+ <a id="model-experience"></a>
112
+ ## 模型体验
113
+
114
+ ### 恢复的对话历史
115
+
116
+ #### 模型看到什么
117
+
118
+ seam 不添加提示词或 schema。恢复会将已存储的表层事件还原为消息历史;已存储请求 header 重建较早调用,新 loop 则为下一次请求组合当前系统提示词、工具与会话前缀。崩溃修复将没有持久调用的 assistant 请求标记为 `TOOL_NOT_STARTED`;有持久调用但无结果时变为 `TOOL_OUTCOME_UNKNOWN`,其文本允许模型重试只读或幂等工作,但要求验证副作用或询问用户,而不是盲目重试。
119
+
120
+ #### Token 影响
121
+
122
+ 普通持久化期间为零 token。恢复后会重新计入保留历史的 token 用量,并照常计入当前请求 envelope 的 token 用量;每个已修复调用都会增加一段以引用形式保留的错误文本。
123
+
124
+ #### KV Cache 影响
125
+
126
+ 持久化不修改实时请求前缀。只有当重建历史、当前 envelope 与模型路由匹配时,恢复 loop 才能重用提供方缓存;崩溃修复结果仅追加,不重写较早历史。
127
+
128
+ ## 已知限制与延期工作
129
+
130
+ <a id="known-limitations-and-deferred-work"></a>
131
+
132
+
133
+ 这些限制界定 seam 保证的终点。它们是当前包约束,不是任务积压。
134
+
135
+ - **无删除或保留接口**——剪枝已存储会话属于带外后端维护。
136
+ - **`list()` 无分页且无过滤**——它返回每个已存储会话的 header;适合本地存储,大规模时无索引。
137
+ - **合成 closer 是唯一崩溃方案**——后端必须在 load 时合成 `tool/result`/`step/end`/`turn/end` closer;没有继续中断轮次而不先关闭它的部分轮次恢复。
138
+
139
+ <a id="dev-note"></a>
140
+ ### 开发备注
141
+
142
+ <details>
143
+ <summary>维护者的工作上下文——点击展开</summary>
144
+
145
+ 无。
146
+
147
+ </details>