dsh-plugin-dev-kb 1.0.9 → 1.3.0

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.
Files changed (169) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.en.md +14 -8
  3. package/README.md +14 -8
  4. package/docs/banner.svg +25 -0
  5. package/kb/INDEX.md +8 -2
  6. package/kb/README.md +4 -4
  7. package/kb/extra/AGENTS.md +3 -3
  8. package/kb/extra/cookbook/adding-a-remote-api.md +197 -0
  9. package/kb/extra/cookbook/adding-a-remote-api.zh.md +197 -0
  10. package/kb/extra/cookbook/adding-a-session-format-version.md +109 -0
  11. package/kb/extra/cookbook/adding-a-session-format-version.zh.md +109 -0
  12. package/kb/extra/cookbook/adding-a-vendored-package.md +1 -1
  13. package/kb/extra/cookbook/adding-a-vendored-package.zh.md +1 -1
  14. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.md +1 -1
  15. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.zh.md +1 -1
  16. package/kb/extra/deepseek-llm-api-wire-extensions.md +19 -15
  17. package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +19 -15
  18. package/kb/extra/development.md +13 -3
  19. package/kb/extra/development.zh.md +13 -3
  20. package/kb/extra/event-producer-consumer.md +49 -46
  21. package/kb/extra/event-producer-consumer.zh.md +52 -49
  22. package/kb/extra/i18n/README.md +2 -1
  23. package/kb/extra/i18n/README.zh.md +3 -2
  24. package/kb/extra/i18n/style-samples.md +2 -2
  25. package/kb/extra/i18n/translation-prompt.md +1 -1
  26. package/kb/extra/module-graph.md +493 -1002
  27. package/kb/extra/module-graph.zh.md +495 -1004
  28. package/kb/extra/rescope.md +1 -1
  29. package/kb/extra/rescope.zh.md +1 -1
  30. package/kb/extra/session-format-status.md +47 -0
  31. package/kb/extra/session-format-status.zh.md +47 -0
  32. package/kb/extra/subsystems/agent-team.md +3 -2
  33. package/kb/extra/subsystems/agent-team.zh.md +3 -2
  34. package/kb/extra/subsystems/attachment.md +157 -4
  35. package/kb/extra/subsystems/attachment.zh.md +157 -4
  36. package/kb/extra/subsystems/feedback.md +164 -30
  37. package/kb/extra/subsystems/feedback.zh.md +164 -30
  38. package/kb/extra/testing.md +9 -4
  39. package/kb/extra/testing.zh.md +9 -4
  40. package/kb/extra/web-styling.md +5 -0
  41. package/kb/extra/web-styling.zh.md +5 -0
  42. package/kb/meta/search-index.json +223 -151
  43. package/kb/meta/site-pages.txt +187 -181
  44. package/kb/meta/source.json +5 -5
  45. package/kb/meta/topics.md +5 -5
  46. package/kb/site/develop/basic/publish.md +1 -1
  47. package/kb/site/develop/cordis-tutorial/07-into-the-harness.md +3 -2
  48. package/kb/site/develop/practice/llm-adapter.md +4 -3
  49. package/kb/site/en/develop/basic/publish.md +1 -1
  50. package/kb/site/en/develop/cordis-tutorial/07-into-the-harness.md +3 -2
  51. package/kb/site/en/develop/practice/llm-adapter.md +4 -3
  52. package/kb/site/en/guide/network-proxy.md +87 -0
  53. package/kb/site/en/guide/providers.md +70 -17
  54. package/kb/site/en/guide/python-sdk.md +19 -3
  55. package/kb/site/en/guide/schedule.md +2 -0
  56. package/kb/site/en/reference/agent-lifecycle.md +17 -8
  57. package/kb/site/en/reference/api-gateway.md +2 -2
  58. package/kb/site/en/reference/capability-seams.md +24 -10
  59. package/kb/site/en/reference/config-catalog.md +322 -264
  60. package/kb/site/en/reference/cookbook/adding-a-package.md +1 -2
  61. package/kb/site/en/reference/cookbook/adding-a-settings-card.md +10 -8
  62. package/kb/site/en/reference/cookbook/adding-a-tool.md +1 -1
  63. package/kb/site/en/reference/cookbook/extension-cookbook.md +16 -13
  64. package/kb/site/en/reference/index.md +26 -9
  65. package/kb/site/en/reference/persistence-catalog.md +189 -95
  66. package/kb/site/en/reference/subsystems/approval.md +1 -1
  67. package/kb/site/en/reference/subsystems/client-modules.md +9 -0
  68. package/kb/site/en/reference/subsystems/client-resources.md +94 -0
  69. package/kb/site/en/reference/subsystems/code-runtime.md +7 -3
  70. package/kb/site/en/reference/subsystems/commands.md +25 -16
  71. package/kb/site/en/reference/subsystems/compaction.md +10 -10
  72. package/kb/site/en/reference/subsystems/conversation.md +11 -4
  73. package/kb/site/en/reference/subsystems/core.md +173 -29
  74. package/kb/site/en/reference/subsystems/credentials.md +5 -4
  75. package/kb/site/en/reference/subsystems/filesystem.md +15 -0
  76. package/kb/site/en/reference/subsystems/goal.md +38 -2
  77. package/kb/site/en/reference/subsystems/index.md +3 -1
  78. package/kb/site/en/reference/subsystems/invariants.md +1 -1
  79. package/kb/site/en/reference/subsystems/llm-streaming.md +43 -12
  80. package/kb/site/en/reference/subsystems/lsp.md +1 -1
  81. package/kb/site/en/reference/subsystems/permission-presets.md +5 -5
  82. package/kb/site/en/reference/subsystems/persistence.md +211 -199
  83. package/kb/site/en/reference/subsystems/plan.md +3 -3
  84. package/kb/site/en/reference/subsystems/sandbox.md +2 -0
  85. package/kb/site/en/reference/subsystems/schedule.md +9 -3
  86. package/kb/site/en/reference/subsystems/scope.md +1 -1
  87. package/kb/site/en/reference/subsystems/session-projection.md +50 -24
  88. package/kb/site/en/reference/subsystems/session-query.md +19 -13
  89. package/kb/site/en/reference/subsystems/session-reference.md +29 -1
  90. package/kb/site/en/reference/subsystems/session-telemetry.md +27 -10
  91. package/kb/site/en/reference/subsystems/session-title.md +6 -6
  92. package/kb/site/en/reference/subsystems/session.md +208 -103
  93. package/kb/site/en/reference/subsystems/settings.md +30 -12
  94. package/kb/site/en/reference/subsystems/shell.md +9 -6
  95. package/kb/site/en/reference/subsystems/sidebar-right.md +151 -0
  96. package/kb/site/en/reference/subsystems/skills.md +1 -1
  97. package/kb/site/en/reference/subsystems/slots.md +41 -32
  98. package/kb/site/en/reference/subsystems/spill.md +13 -6
  99. package/kb/site/en/reference/subsystems/storage.md +28 -5
  100. package/kb/site/en/reference/subsystems/subagent.md +58 -105
  101. package/kb/site/en/reference/subsystems/subprocess.md +25 -26
  102. package/kb/site/en/reference/subsystems/system-prompt.md +18 -3
  103. package/kb/site/en/reference/subsystems/token-meter.md +4 -3
  104. package/kb/site/en/reference/subsystems/tools.md +5 -5
  105. package/kb/site/en/reference/subsystems/typert.md +18 -18
  106. package/kb/site/en/reference/subsystems/web-client.md +1 -1
  107. package/kb/site/en/reference/subsystems/workspace.md +88 -9
  108. package/kb/site/en/reference/tool-catalog.md +56 -66
  109. package/kb/site/en/reference/tool-execution-pipeline.md +2 -2
  110. package/kb/site/guide/network-proxy.md +87 -0
  111. package/kb/site/guide/providers.md +70 -17
  112. package/kb/site/guide/python-sdk.md +19 -3
  113. package/kb/site/guide/schedule.md +2 -0
  114. package/kb/site/reference/agent-lifecycle.md +17 -8
  115. package/kb/site/reference/api-gateway.md +2 -2
  116. package/kb/site/reference/capability-seams.md +24 -10
  117. package/kb/site/reference/config-catalog.md +321 -263
  118. package/kb/site/reference/cookbook/adding-a-package.md +1 -2
  119. package/kb/site/reference/cookbook/adding-a-settings-card.md +10 -8
  120. package/kb/site/reference/cookbook/adding-a-tool.md +1 -1
  121. package/kb/site/reference/cookbook/extension-cookbook.md +16 -13
  122. package/kb/site/reference/index.md +26 -9
  123. package/kb/site/reference/persistence-catalog.md +190 -96
  124. package/kb/site/reference/subsystems/approval.md +1 -1
  125. package/kb/site/reference/subsystems/client-modules.md +9 -0
  126. package/kb/site/reference/subsystems/client-resources.md +94 -0
  127. package/kb/site/reference/subsystems/code-runtime.md +7 -3
  128. package/kb/site/reference/subsystems/commands.md +25 -16
  129. package/kb/site/reference/subsystems/compaction.md +10 -10
  130. package/kb/site/reference/subsystems/conversation.md +11 -4
  131. package/kb/site/reference/subsystems/core.md +173 -29
  132. package/kb/site/reference/subsystems/credentials.md +5 -4
  133. package/kb/site/reference/subsystems/filesystem.md +15 -0
  134. package/kb/site/reference/subsystems/goal.md +38 -2
  135. package/kb/site/reference/subsystems/index.md +14 -12
  136. package/kb/site/reference/subsystems/invariants.md +1 -1
  137. package/kb/site/reference/subsystems/llm-streaming.md +43 -12
  138. package/kb/site/reference/subsystems/lsp.md +1 -1
  139. package/kb/site/reference/subsystems/permission-presets.md +5 -5
  140. package/kb/site/reference/subsystems/persistence.md +210 -198
  141. package/kb/site/reference/subsystems/plan.md +3 -3
  142. package/kb/site/reference/subsystems/schedule.md +9 -3
  143. package/kb/site/reference/subsystems/scope.md +1 -1
  144. package/kb/site/reference/subsystems/session-projection.md +50 -24
  145. package/kb/site/reference/subsystems/session-query.md +19 -13
  146. package/kb/site/reference/subsystems/session-reference.md +29 -1
  147. package/kb/site/reference/subsystems/session-telemetry.md +27 -10
  148. package/kb/site/reference/subsystems/session-title.md +6 -6
  149. package/kb/site/reference/subsystems/session.md +208 -103
  150. package/kb/site/reference/subsystems/settings.md +30 -12
  151. package/kb/site/reference/subsystems/shell.md +9 -6
  152. package/kb/site/reference/subsystems/sidebar-right.md +151 -0
  153. package/kb/site/reference/subsystems/skills.md +1 -1
  154. package/kb/site/reference/subsystems/slots.md +41 -32
  155. package/kb/site/reference/subsystems/spill.md +13 -6
  156. package/kb/site/reference/subsystems/storage.md +28 -5
  157. package/kb/site/reference/subsystems/subagent.md +58 -105
  158. package/kb/site/reference/subsystems/subprocess.md +25 -26
  159. package/kb/site/reference/subsystems/system-prompt.md +18 -3
  160. package/kb/site/reference/subsystems/token-meter.md +4 -3
  161. package/kb/site/reference/subsystems/tools.md +5 -5
  162. package/kb/site/reference/subsystems/typert.md +18 -18
  163. package/kb/site/reference/subsystems/web-client.md +1 -1
  164. package/kb/site/reference/subsystems/workspace.md +88 -9
  165. package/kb/site/reference/tool-catalog.md +55 -65
  166. package/kb/site/reference/tool-execution-pipeline.md +2 -2
  167. package/lib/index.js +25 -44
  168. package/package.json +3 -2
  169. package/skills/dsh-plugin-dev-kb.md +4 -4
@@ -5,31 +5,127 @@ outline: [2,3]
5
5
 
6
6
  # Session Persistence
7
7
 
8
- The **durability seam** for the event log. [session.md](./session.md) describes the in-memory `Session` — the append-only `SessionEvent` log that is the source of truth. This page describes how that log is made durable: the abstract `SessionPersistence` service, its backends, the flush checkpoint, crash recovery, and the metadata header that travels alongside the log. The event vocabulary the log carries is enumerated, member by member, in the generated [persistence log event catalog](../persistence-catalog.md).
8
+ The **durability seam** for the event log. [session.md](./session.md) describes the in-memory `Session` — the append-only `SessionEvent` log that is the source of truth. This page describes how that log is made durable: the abstract `SessionPersistence` service, its provider model and shipped JSONL backend, the flush checkpoint, crash recovery, and the metadata header that travels alongside the log. The event vocabulary the log carries is enumerated, member by member, in the generated [persistence log event catalog](../persistence-catalog.md).
9
9
 
10
- The seam is a [capability seam](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-06-13-capability-seams.md): one abstract service ([dsh-session-persistence](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence), `ctx.sessionPersistence`) defining locate/create/append, reusable Session preparation, logical load/inspect, physical suffix reads, and lightweight list/snapshot observation over the existing `SessionEvent` — **no parallel persisted event type** — and three interchangeable providers implementing the same contract. See the [session-persistence Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-06-14-session-persistence.md).
10
+ The seam is a [capability seam](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-06-13-capability-seams.md): one abstract service ([dsh-session-persistence](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence), `ctx.sessionPersistence`) exposing `create`/`open`/`stat`/`list` over the existing `SessionEvent` — **no parallel persisted event type** — where `create` and `open` return a per-session `SessionHandle` (`read`/`append`/`flush`/`close`) that carries all log access and single-writer ownership. The repository ships [dsh-session-persistence-jsonl](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence-jsonl) as its provider; out-of-tree providers may implement the same service contract. See the [handle-based persistence Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.md) and the [session-persistence Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-06-14-session-persistence.md).
11
+
12
+ ## `SessionHandle` — one open channel onto a stored session
13
+
14
+ Every log read and write flows through a handle, never through id-addressed service methods: the handle is the single door the cross-process write lease guards. A read returns a caller-owned outer slice and the producer-established aliasing state of its event values. One handle type serves both accesses — a mutation on a `read` handle is a runtime `SessionReadOnlyError` rather than a typed split — and in-process single-writer ownership makes a second `open(id, 'write')` reject with `SessionAlreadyOwnedError` while an owner is active.
15
+
16
+ ```ts type-equiv
17
+ /** One persistence event slice returned by {@link SessionHandle.read}. */
18
+ interface SessionHandleReadResult {
19
+ /**
20
+ * Whether event values are exclusively owned or shared only after deep
21
+ * freezing. Slicing preserves the producer's state even when no events remain.
22
+ */
23
+ readonly eventState: SessionSeedEventState
24
+ /** Event values in a caller-owned outer array. */
25
+ readonly events: readonly SessionEvent[]
26
+ }
27
+ ```
28
+
29
+ ```ts type-equiv
30
+ /**
31
+ * One open channel onto a stored session. A handle is single-owner state, not
32
+ * a shared service: `read` never backtracks below what this handle already
33
+ * observed, a `write` handle reads its own successful appends, and `close()`
34
+ * is the one teardown (idempotent, uncancellable; `Symbol.asyncDispose`
35
+ * delegates to it). Every operation on a closed handle rejects with
36
+ * `SessionHandleClosedError`.
37
+ *
38
+ * Freshness across handles: once an `append` or `flush` resolves on a write
39
+ * handle, every read STARTED afterwards on the same backend instance — on any
40
+ * handle, or through `stat`/`list` — observes at least that prefix.
41
+ * Reads concurrent with a mutation carry no ordering promise beyond the valid
42
+ * contiguous prefix.
43
+ */
44
+ interface SessionHandle extends AsyncDisposable {
45
+ /** The stored session this handle addresses. */
46
+ readonly id: SessionId
47
+ /** The immutable stored header, fixed at `create`/`open`. */
48
+ readonly header: SessionHeader
49
+ /**
50
+ * Exact fork-inherited prefix length stored with the log; `0` when
51
+ * `header.isSeeded` is false. Storage metadata paired with the header for
52
+ * every body read, never part of the replayable event log.
53
+ */
54
+ readonly inheritedEventCount: SessionLogOffset
55
+ /** Whether this handle may mutate the log. */
56
+ readonly access: SessionAccess
57
+
58
+ /**
59
+ * Read a slice of the valid contiguous logical log. The slice is a legal log
60
+ * prefix segment: a torn physical tail is never returned, and repeated reads
61
+ * on this handle never observe an older state than a prior read.
62
+ * @param offset - first logical event seq to include; defaults to `0`.
63
+ * @param length - maximum number of events to return; defaults to the rest
64
+ * of the log. An offset at or past the end returns an empty list.
65
+ * @param options - optional cancellation.
66
+ * @returns the caller-owned outer slice plus the ownership state of its event values.
67
+ */
68
+ read(offset?: number, length?: number, options?: SessionHandleReadOptions): Promise<SessionHandleReadResult>
69
+
70
+ /**
71
+ * Append a contiguous batch continuing the current logical end. The first
72
+ * event's `seq` MUST equal the stored next-seq; committed events are never
73
+ * rewritten. Persistence is best-effort: on resolution the batch is
74
+ * accepted, ordered, and visible to reads on this backend instance, but
75
+ * only a resolved {@link flush} promises it survives a crash — a backend
76
+ * may buffer or batch physical writes behind append. Rejects with
77
+ * `SessionReadOnlyError` on a read handle and `SessionOwnershipLostError`
78
+ * when write ownership is gone.
79
+ * @param events - the contiguous batch, in seq order.
80
+ * @param options - optional cancellation observed before the write starts.
81
+ */
82
+ append(events: readonly SessionEvent[], options?: SessionHandleAppendOptions): Promise<void>
83
+
84
+ /**
85
+ * The durability barrier — the one operation that promises storage: on
86
+ * resolution every acknowledged append is durable and the session is
87
+ * materialized for other processes; an empty created session becomes
88
+ * durably listable here. Callers that must survive a crash flush; a backend
89
+ * whose `append` already persists on resolution treats this as
90
+ * materialize-if-needed. Rejects with `SessionReadOnlyError` on a read
91
+ * handle.
92
+ * @param options - optional cancellation observed before the barrier starts.
93
+ */
94
+ flush(options?: SessionHandleFlushOptions): Promise<void>
95
+
96
+ /**
97
+ * Release the handle: a read handle frees local resources; a write handle
98
+ * completes pending durability and releases write ownership. Idempotent,
99
+ * asynchronous, and deliberately not cancellable.
100
+ */
101
+ close(): Promise<void>
102
+ }
103
+ ```
104
+
105
+ A created session is observable in this process from the moment `create` resolves, while a backend may defer physical materialization (a pure optimization) until the first `append` or `flush`; other processes see only materialized sessions, and a session that never materialized before a crash never existed.
11
106
 
12
107
  ## The flush checkpoint
13
108
 
14
- `session/event` is a *synchronous* notification; persistence plugins copy the event into a per-session controller without blocking the producer. The first pending event starts a fixed batching window, and later events join without resetting its deadline. Expiry starts one durable batch; events admitted during that write receive their own deadline and form a follow-up batch. `session/flush` cancels the wait and drains through quiescence, so the loop still uses it as the ordering and error-observation checkpoint before claiming the next ordinary turn. A rejected background write retains its events and pauses automatic retry; a new event starts a fresh window, while explicit flush retries immediately and reports failure through `agent/error` and the logger, never as a session event past the closed turn. Disposal performs the same final drain. The configured maximum bounds only intentional batching wait, not event-loop scheduling or backend durability latency ([decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-08-bounded-session-persistence-write-batching.md)).
109
+ `session/event` is a *synchronous* notification; the mounted backend routes it by session id into the active write handle's bounded write-behind window without blocking the producer (the backend installs these listeners once, because persistence already enforces one active write handle per id). The first pending event starts a fixed internal batching window, and later events join without resetting its deadline. Expiry starts one durable `append` through the session's write handle; events admitted during that write receive their own deadline and form a follow-up batch. `session/flush` cancels the wait and drains through quiescence, so the loop still uses it as the ordering and error-observation checkpoint before claiming the next ordinary turn. A rejected background write retains its events in order, pauses the automatic path, and is reported through the logger; the next explicit flush retries and rejects loudly to its caller. `session/disposed` performs the same final drain and closes the handle, and `close()` itself drains the routed buffer through the still-open storage, so backend teardown's close sweep loses nothing. The window bounds only intentional batching wait, not event-loop scheduling or backend durability latency.
15
110
 
16
111
  ## Crash recovery preserves an interrupted turn
17
112
 
18
- A backend that reloads a log crashed mid-turn finds an open `turn/start` with no `turn/end`. It does **not** truncate — a single turn can be huge in a long-horizon task (many steps, large tool output), and those events were durably appended before the crash. Instead it closes the orphaned turn with a synthetic `turn/end { reason: { kind: 'interrupted' } }`, keeping the interrupted execution balanced without changing any standalone events before or after it. `interrupted` is the one `TurnEndReason` no loop emits (see [session.md](./session.md#why-a-turn-ended-turnendreasonmap)).
113
+ A log crashed mid-turn ends with an open `turn/start` and no `turn/end`. Persistence does **not** truncate or repair it — a single turn can be huge in a long-horizon task (many steps, large tool output), and those events were durably appended before the crash. It returns the physically valid contiguous log; only the incomplete fragment of a torn physical tail, belonging to an append that never resolved, is discarded — complete records recovered from it (the JSONL backend partially decodes a torn Zstandard frame) are durably rewritten by the write path before the handle's first new append. Repair is the reader's job: resume (agent-loop) reads the stored log through its write handle, computes `interruptedTurnClosers` — missing tool errors, any open `step/end`, and a synthetic `turn/end { reason: { kind: 'interrupted' } }` and appends them through the same handle as an ordinary batch before publishing the Session. `interrupted` is the one `TurnEndReason` no loop emits (see [session.md](./session.md#why-a-turn-ended-turnendreasonmap)).
19
114
 
20
- Repair applies only to cold sessions. For a live id, `SessionPersistence.load(id)` waits until the authoritative in-memory snapshot is durable and returns it only when balanced; an open live turn rejects rather than receiving synthetic interruption boundaries. HMR adopts a live prefix without closing its active turn.
115
+ Repair therefore writes only under write ownership: a live session's write handle is held by its lifecycle owner, so a concurrent `open(id, 'write')` rejects with `SessionAlreadyOwnedError` instead of racing repair against a live turn. Read-only observers (session-query) balance an interrupted cold log with the same closers in memory only, writing nothing back.
21
116
 
22
- `SessionPersistence.inspect(id)` constructs an immutable logical Session without publishing it or writing recovery. Cold inspection balances an interrupted turn in memory while leaving torn physical tails untouched; inspection of an already-live Session borrows its current immutable snapshot and may therefore contain an open turn. Coordinator-backed implementations retain the exact cold unpublished Session in a bounded LRU, so repeated history reads and a later `prepare(id)` share one read, decompression, validation, freeze, and Session construction. `prepare(id)` reserves the Session, commits pending repair, and returns a disposable publication handle; `load(id)` uses the same machinery to commit repair without publication. The [Session preparation decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-05-session-preparation.md) owns this lifecycle.
117
+ Read-only observation is `open(id, 'read')`: the handle serves validated contiguous prefix slices, never a torn tail, and repeated reads on one handle never observe an older state than a prior read. There is no persistence-side prepared-Session cache: session-query owns its cold-read cache, keying one balanced cold Session per id on the `stat().revision` change token and re-reading only when the token changes. The [handle-based persistence Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.md) owns this lifecycle; the archived [Session preparation record](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/archived/architecture/2026-08-05-session-preparation.md) documents the original publication-boundary `SessionPreparation` decision.
23
118
 
24
- ## `SessionLocation` — optional per-session artifact target
119
+ ## `SessionLocation` — refusal-diagnostics artifact target
25
120
 
26
- `SessionPersistence.locate(meta)` synchronously resolves a backend-owned independent artifact without reading, creating, or flushing it. JSONL returns the absolute transcript path inside its project/session directory; SQLite returns `undefined` because sessions share one database. A returned path can therefore name a file that does not yet exist or lacks the current unflushed turn; it is a location hint, not authorization or a freshness guarantee.
121
+ `SessionLocation` is not a consumer-facing query: log access goes through a session handle's `read`. It survives only as refusal diagnostics, letting a `SessionFormatUnsupportedError` name the raw log a build refused to interpret. JSONL supplies the absolute transcript path inside its project/session directory; a backend without one artifact per session supplies nothing.
27
122
 
28
123
  ```ts type-equiv
29
124
  /**
30
- * A backend-resolved, per-session local artifact location. The path is an
31
- * absolute target path and can name an artifact that has not materialized yet.
32
- * Consumers must treat it as a location hint, never as an authorization token.
125
+ * A backend-resolved, per-session local artifact location. Carried only by
126
+ * refusal diagnostics ({@link SessionFormatUnsupportedError}) so a user can
127
+ * find the raw log a build refused to interpret; it is not a consumer-facing
128
+ * query — log access goes through a session handle's `read`.
33
129
  */
34
130
  interface SessionLocation {
35
131
  /** Backend-specific artifact kind, for example `jsonl`. */
@@ -43,7 +139,7 @@ interface SessionLocation {
43
139
 
44
140
  ## `SessionHeader` — metadata beside the log
45
141
 
46
- Per-session metadata travels **separately** from the event log: format version, cwd, lineage, and the seed boundary are storage concerns, not conversation events, so they stay out of `SessionEventMap` and never reach `deriveMessages()`. The header is attached to a `Session` via `session.header`.
142
+ Per-session metadata travels **separately** from the event log: the header carries format version, cwd, and the `isSeeded` lineage bit, while body-bearing storage values carry the exact inherited cut beside it. Neither belongs to `SessionEventMap` or reaches `deriveMessages()`. The logical header is attached through `session.header`; the Session exposes its cut as `inheritedEventCount`.
47
143
 
48
144
  Source: [`packages/core/session/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
49
145
 
@@ -53,11 +149,10 @@ Source: [`packages/core/session/src/types.ts`](https://github.com/deepseek-ai/de
53
149
  */
54
150
  interface SessionHeader {
55
151
  /**
56
- * On-disk format version, stamped from {@link SESSION_FORMAT_VERSION} when the
57
- * session is created. A persistence backend rejects any other version on load
58
- * (no migration — see the constant).
152
+ * Current logical format version, stamped from {@link SESSION_FORMAT_VERSION}.
153
+ * Historical physical headers are translated before entering this interface.
59
154
  */
60
- readonly version: number
155
+ readonly version: typeof SESSION_FORMAT_VERSION
61
156
  /** The session's id (mirrors the {@link Session}'s id). */
62
157
  readonly id: SessionId
63
158
  /** Non-negative safe-integer Unix epoch milliseconds when the session was created. */
@@ -67,10 +162,10 @@ interface SessionHeader {
67
162
  /** The session this one was forked from (seed lineage), if any. */
68
163
  readonly parentSession?: SessionId
69
164
  /**
70
- * How many leading events were inherited through a seed. Persisting this
71
- * boundary lets resume and replay distinguish parent history from child work.
165
+ * Whether this Session contains a fork-inherited event prefix. The exact prefix
166
+ * length is Session state rather than ordinary header metadata.
72
167
  */
73
- readonly seedLength?: number
168
+ readonly isSeeded: boolean
74
169
  /**
75
170
  * Coarse product classification for a session created as a subagent child.
76
171
  * This is presentation metadata, not proof that the child is continuable.
@@ -94,11 +189,11 @@ interface SessionHeader {
94
189
 
95
190
  ## Format refusal — logs a build cannot faithfully read
96
191
 
97
- A backend refuses a log it cannot faithfully interpret with `SessionFormatUnsupportedError`, distinct from `SessionPersistenceCorruptionError` because nothing is damaged. A header `version` ahead of `SESSION_FORMAT_VERSION` names the direction ("written by a newer harness upgrade the harness to open it"); one behind it states that this build ships no upgrade path. After legacy-shape normalization, an event type outside this build's generated set (`KNOWN_SESSION_EVENT_TYPES`, emitted by `gen-persistence-catalog`) also refuses reconstruction because silently skipping it could change how the rest of the log must be read. The message appends the raw log path when the backend keeps one artifact per session, so the refused text stays reachable. The JSONL backend refuses a foreign version straight from the raw header line, before validating this format version's header fields or decoding any event row a structurally different future format still reports the upgrade direction, never "corrupt"; SQLite gates whole-file structure through its own `SCHEMA_VERSION` pragma first. Design rationale and the deferred upgrader chain live in the [fail-closed event-vocabulary note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/simplification/2026-08-25-fail-closed-session-event-vocabulary.md).
192
+ A backend refuses a log it cannot faithfully interpret with `SessionFormatUnsupportedError`, distinct from `SessionPersistenceCorruptionError` because nothing is damaged. `stat` and `list` classify the highest canonical generation and translate a supported historical header without reading or mutating its body. Historical `open` calls share one per-session migration preparation before returning current logical values and leave every source path, byte, and inode unchanged. The JSONL provider returns a read handle from that in-memory result without publishing; a write open holds its single-writer claim and file lease while it reuses the preparation, exclusively publishes the final current generation, and only then returns the writable handle. A future highest generation refuses even when an older readable generation remains. Current-format restoration retains installed extensions and unknown events carrying `ignorable: true`; historical v0/v1/v2 migration refuses an unknown type even when marked ignorable. The message appends the selected raw log path when the backend keeps one artifact per session. An out-of-tree backend must enforce equivalent current-only handle values and direction-aware refusals at its physical-format entry. The [released-format migration decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md) owns the chain and immutable-publication rules.
98
193
 
99
194
  ## `CreateSessionOptions` — seeding and metadata
100
195
 
101
- Creating a `Session` through the store takes a `seed` (initial replay or fork history) and `meta` (the storage-level fields the store folds into a `SessionHeader`). The store fills in `version`/`id` and defaults `createdAt`; the caller may supply the validated absolute `cwd`, the `parentSession` lineage, the `seedLength` seed boundary, the optional coarse `origin`, the `delegationDepth`, the `agentPreset` the agent was composed from, and an existing `createdAt`. `origin: 'subagent'` lets product navigation hide duplicate child rows; it does not prove that a descriptor is valid or that the child can resume.
196
+ Creating a `Session` through the store takes a `seed` (initial replay or fork history), an optional exact `inheritedEventCount`, and `meta` (the storage-level fields the store folds into a `SessionHeader`). The store fills in `version`/`id` and defaults `createdAt`; the caller may supply the validated absolute `cwd`, `parentSession` lineage, `isSeeded` lineage bit, optional coarse `origin`, `delegationDepth`, `agentPreset`, and an existing `createdAt`. A seeded creation requires an explicit seed equal to its inherited prefix and an exact cut; the constructor appends the child-owned tagged end-seed marker at that cut before setup adds child-owned events. `origin: 'subagent'` lets product navigation hide duplicate child rows; it does not prove that a descriptor is valid or that the child can resume.
102
197
 
103
198
  ```ts type-equiv
104
199
  /**
@@ -110,14 +205,20 @@ interface CreateSessionOptions {
110
205
  /** Initial replay or fork history supplied at construction. */
111
206
  readonly seed?: readonly SessionEvent[]
112
207
  /**
113
- * Storage metadata read once before publication. `seedLength` is explicit
114
- * because a resumed seed contains the full stored log, not only its inherited prefix.
208
+ * Exact fork-inherited prefix length when `meta.isSeeded` is true. The
209
+ * constructor seed is exactly this inherited prefix; the constructor
210
+ * appends the child-owned tagged marker at the cut.
211
+ */
212
+ readonly inheritedEventCount?: SessionLogOffset
213
+ /**
214
+ * Storage metadata read once before publication. `isSeeded` marks fork
215
+ * lineage; supplying replay history alone does not make it inherited.
115
216
  */
116
217
  readonly meta?: {
117
218
  readonly cwd?: string
118
219
  readonly parentSession?: SessionId
119
220
  readonly createdAt?: number
120
- readonly seedLength?: number
221
+ readonly isSeeded?: boolean
121
222
  readonly origin?: 'subagent'
122
223
  readonly delegationDepth?: number
123
224
  readonly agentPreset?: string
@@ -125,47 +226,41 @@ interface CreateSessionOptions {
125
226
  }
126
227
  ```
127
228
 
128
- Replay/fork is therefore `ctx.sessions.create(id, { seed: seedEvents })`; resuming a *persisted* session into a live agent is `ctx.agents.resume({ resumeSessionId })`.
229
+ Replay/fork is therefore `ctx.agents.create({ sessionId, seed, meta })` — a fork additionally supplies `inheritedEventCount` with `meta.isSeeded: true`, and only agent-loop-published sessions persist, and the loop stores the seed through the new session's write handle before publication; resuming a *persisted* session into a live agent is `ctx.agents.resume({ resumeSessionId })`.
129
230
 
130
- ## `SessionRawArtifact` verbatim stored artifact text
231
+ ## Preparation and restoration ownership
131
232
 
132
- A backend's own artifact text for one session, byte-identical to what it durably wrote (decoded from its physical encoding). `readRaw` returns it without reconstructing from parsed events, so backend-specific serialization (chunk packing, key order, line breaks) survives. Consumers first test `supportsRawArtifacts`: `false` means the backend does not provide this capability (for example SQLite), while `readRaw(...) === undefined` means a supported backend has no materialized artifact for that session.
233
+ `SessionStore.prepare()` accepts ordinary creation options or an adoptable seed through `RestoredSessionOptions`. Its `eventState` says whether event values are independently owned or shared only after deep freezing; the producer establishes that state, and slicing does not infer a different state from result length. Restoration validates and adopts those values without another copy or freeze pass. `SessionPreparation` then owns the exact unpublished Session until publication or rollback; disposal is synchronous and idempotent. agent-loop's resume reads this result through the session's write handle and appends independently owned `interruptedTurnClosers` before preparation.
133
234
 
134
235
  ```ts type-equiv
135
- /** A backend's own raw artifact text for one session, verbatim. */
136
- interface SessionRawArtifact {
137
- /** The session header parsed from the artifact's own first line. */
138
- readonly meta: SessionHeader
139
- /** The artifact's base filename on disk, without any physical encoding suffix. */
140
- readonly filename: string
141
- /** The artifact's full text content, decoded from the backend's physical encoding. */
142
- readonly content: string
143
- }
236
+ /**
237
+ * Aliasing state of an adoptable Session seed. `shared-frozen` permits deeply
238
+ * frozen aliases plus independently owned unfrozen values in the same seed.
239
+ */
240
+ type SessionSeedEventState = 'detached' | 'shared-frozen'
144
241
  ```
145
242
 
146
- ## Preparation and restoration ownership
147
-
148
- `SessionStore.prepare()` accepts ordinary creation options or fresh persistence graphs transferred through `RestoredSessionOptions`. The restoration branch validates and freezes the transferred header and events in place, so callers must retain no mutable aliases. `SessionPreparation` then owns the exact unpublished Session until publication or rollback; disposal is synchronous and idempotent. Persistence inspection exposes only `SessionInspection`, an immutable logical view borrowed from the same prepared Session.
149
-
150
243
  ```ts type-equiv
151
244
  /**
152
- * Fresh storage values transferred to {@link SessionStore.prepare} without a
153
- * second serialization copy. Callers retain no mutable aliases.
245
+ * Adoptable storage values transferred to {@link SessionStore.prepare}
246
+ * without another copy or freeze pass.
154
247
  */
155
248
  interface RestoredSessionOptions {
156
- /** Fresh detached storage events to validate and freeze in place. */
249
+ /** Events that are independently owned or already deeply frozen. */
157
250
  readonly seed: SessionEvent[]
158
- /** Fresh detached storage metadata to validate and freeze in place. */
251
+ /** Independently owned storage metadata to validate and freeze in place. */
159
252
  readonly meta: SessionHeader
160
- /** Select the persistence ownership-transfer path. */
161
- readonly seedSource: 'persistence'
253
+ /** Exact number of fork-inherited leading events decoded from storage. */
254
+ readonly inheritedEventCount: SessionLogOffset
255
+ /** Aliasing state carried from the operation that produced the seed. */
256
+ readonly eventState: SessionSeedEventState
162
257
  }
163
258
  ```
164
259
 
165
260
  ```ts type-equiv
166
261
  /** Inputs accepted while constructing an unpublished Session. */
167
262
  type PrepareSessionOptions =
168
- | (CreateSessionOptions & { readonly seedSource?: undefined })
263
+ | (CreateSessionOptions & { readonly eventState?: undefined })
169
264
  | RestoredSessionOptions
170
265
  ```
171
266
 
@@ -199,19 +294,9 @@ declare class SessionPreparation implements Disposable {
199
294
  }
200
295
  ```
201
296
 
202
- ```ts type-equiv
203
- /** Immutable logical session prepared from persistence or a live owner. */
204
- interface SessionInspection {
205
- /** Validated immutable session metadata. */
206
- readonly meta: SessionHeader
207
- /** Validated contiguous logical event log. */
208
- readonly events: readonly SessionEvent[]
209
- }
210
- ```
211
-
212
297
  ## Lightweight source revisions
213
298
 
214
- Consumers of derived state compare a cheap opaque revision before loading a full event log. The persistence backend owns its representation and changes it transactionally with append or mutating load repair; callers compare it only for equality.
299
+ Consumers of derived read models compare a cheap opaque revision before loading a full event log. The revision is a per-backend-instance change token from `stat`/`list`: equal revisions may be treated as an unchanged log; unequal revisions promise nothing, and write-ownership churn never changes one. session-query keys its cold-read cache on it; the token plays no part in open, read, or resume.
215
300
 
216
301
  ```ts type-equiv
217
302
  /**
@@ -222,21 +307,29 @@ type SessionPersistenceRevision = Branded<'SessionPersistenceRevision'>
222
307
  ```
223
308
 
224
309
  ```ts type-equiv
225
- /** Lightweight immutable source identity returned without loading a full log. */
310
+ /**
311
+ * Lightweight stored-session observation returned by {@link SessionPersistence.stat}
312
+ * and {@link SessionPersistence.list} without reading the full event log.
313
+ */
226
314
  interface SessionPersistenceSnapshot {
227
- /** Detached metadata for one materialized session. */
228
- header: SessionHeader
229
- /** Opaque source-qualified token that changes whenever this stored log changes. */
230
- revision: SessionPersistenceRevision
315
+ /** Detached metadata for one stored session. */
316
+ readonly header: SessionHeader
317
+ /** Opaque change token; see {@link SessionPersistence.stat}. */
318
+ readonly revision: SessionPersistenceRevision
319
+ /** Logical event count, when the backend can provide it cheaply from metadata; otherwise absent. */
320
+ readonly eventCount?: number
321
+ /** Physical artifact byte size, when the backend can provide it cheaply (JSONL); otherwise absent. */
322
+ readonly sizeBytes?: number
231
323
  }
232
324
  ```
233
325
 
234
- ## The backends
326
+ The optional `eventCount`/`sizeBytes` fields remain cheap backend observations for consumers that explicitly need them. Session listing does not use either field to open cold logs: it reads headers plus identity-checked projection-cache hints only, so a cache or Session-format upgrade never turns startup into a body scan.
235
327
 
236
- All implement the same abstract `SessionPersistence` (locate/create/append/prepare/load/inspect/readFrom/list/listSnapshots over `SessionEvent`, with optional cancellation on observation methods) and pass the shared `runPersistenceContract` suite:
328
+ ## The backend
237
329
 
238
- - **[dsh-session-persistence-jsonl](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence-jsonl)** an append-only logical JSONL log per session, stored as checksummed concatenated Zstandard frames by default or raw lines by configuration, with crash-safe atomic writes, interrupted-turn recovery, and a read/replay path.
239
- - **[dsh-session-persistence-sqlite](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence-sqlite)** — an opt-in `node:sqlite` backend using schema 19 to store exact same-block delta runs in bounded physical `text-chunks`, `reasoning-chunks`, and `tool-call-chunks` rows. It reconstructs the complete logical event stream before returning it, packs only newly durable batches, and rejects older schemas rather than migrating them.
330
+ The shipped provider implements the abstract `SessionPersistence` contract (`create`/`open`/`stat`/`list`, with per-session `SessionHandle`s carrying `read`/`append`/`flush`/`close` and optional cancellation throughout) and passes the shared persistence contract suite:
331
+
332
+ - **[dsh-session-persistence-jsonl](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence-jsonl)** — an append-only logical JSONL log per session, stored as checksummed concatenated Zstandard frames by default or raw lines by configuration, with crash-safe atomic materialization, per-batch `fsync` appends, and torn-tail truncation before the first new append. `stat`/`list` carry `sizeBytes` and a best-effort `fs.stat`-derived revision.
240
333
 
241
334
  <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
242
335
 
@@ -250,158 +343,77 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp
250
343
 
251
344
  ### `ctx.sessionPersistence` — `SessionPersistence` (abstract seam)
252
345
 
253
- Durable append-only session storage. Implementations preserve contiguous, losslessly JSON-serializable events; append resolves only after durability, and load balances a complete interrupted tail without rewriting committed events.
346
+ Durable append-only session storage addressed through per-session handles.
254
347
 
255
- ```ts cordis-catalog
256
- /**
257
- * Resolve this backend's independent local artifact for a session without
258
- * reading, creating, flushing, or otherwise materializing it. Backends such
259
- * as SQLite that do not own one artifact per session return `undefined`.
260
- * @param meta - the immutable session header whose artifact is requested.
261
- * @returns the backend-specific absolute location, when one exists.
262
- */
263
- abstract locate(meta: SessionHeader): SessionLocation | undefined
348
+ Storage semantics shared by every backend: events are contiguous from seq 0 and never rewritten; a torn physical tail is never returned to a reader and is truncated by the write path before its first append; reads validate current-format records only and refuse unknown vocabulary fail-closed. `append` persists best-effort; `flush` — per handle or service-wide — is the durability barrier.
264
349
 
265
- /**
266
- * Read a session's backend-owned artifact text verbatim — the exact durable
267
- * bytes the backend wrote (decoded from its physical encoding, e.g. a
268
- * decompressed JSONL). The returned `content` is the raw text, not a
269
- * reconstruction from parsed events, so it preserves backend-specific
270
- * serialization (chunk packing, key order, line breaks). Callers first test
271
- * {@link supportsRawArtifacts}; `undefined` then means only that the requested
272
- * session has no materialized artifact.
273
- * @param _id - the persisted session to read (unused by the default: no
274
- * per-session artifact).
275
- * @param signal - optional cancellation for backend read work.
276
- * @returns the raw artifact plus its parsed header, or `undefined` when the
277
- * session is absent.
278
- * @throws when this backend does not expose per-session raw artifacts.
279
- */
280
- readRaw(_id: SessionId, signal?: AbortSignal): Promise<SessionRawArtifact | undefined>
350
+ Visibility: a created session is observable through `stat`/`list`/`open` in this process from the moment `create` resolves, even while a backend defers physical materialization (a pure optimization); other processes see the session only once it materializes, and a session that never materialized before a crash never existed. `SessionHandle.flush` forces materialization.
281
351
 
282
- /**
283
- * Register a new session's metadata. A backend MAY defer the physical write
284
- * until the first {@link append} (lazy materialization), in which case a
285
- * created-but-never-appended session is absent from {@link list}
286
- * — abandoned sessions leave nothing behind.
287
- * @param meta - the immutable header (id, version, cwd, lineage) to record.
288
- */
289
- abstract create(meta: SessionHeader): Promise<void>
290
-
291
- /**
292
- * Ensure a live session has a durable header even when it has no events.
293
- * Ordinary sessions remain lazily materialized; lifecycle frontends call
294
- * this only when an empty session itself is a durable resumable resource.
295
- * @param _session - exact live session whose registered header is materialized.
296
- */
297
- ensureMaterialized(_session: Session): Promise<void>
298
-
299
- /**
300
- * Durably persist a batch of events. Honors the append-only and contiguous-
301
- * seq contracts: the first event's `seq` MUST equal the stored next-seq
302
- * (after `load` has durably closed any interrupted turn). Rejects non-JSON-
303
- * serializable `event.data` with an error naming the offending event type.
304
- * @param id - the session the batch belongs to.
305
- * @param events - the contiguous batch to persist, in seq order.
306
- */
307
- abstract append(id: SessionId, events: readonly SessionEvent[]): Promise<void>
308
-
309
- /**
310
- * Prepare the exact unpublished Session used by resume. Implementations may
311
- * reuse object graphs retained by an earlier {@link inspect} after confirming
312
- * their durable revision is still current; disposal releases an unpublished
313
- * reservation. Revision retries require the durable log to remain unchanged
314
- * for one read/check round trip; continuous external writers may delay completion.
315
- * @param id - persisted session to prepare.
316
- * @param signal - optional cancellation for preparation work.
317
- * @returns one owned unpublished Session preparation.
318
- */
319
- async prepare(id: SessionId, signal?: AbortSignal): Promise<SessionPreparation>
320
-
321
- /**
322
- * Load an immutable balanced logical view and commit any required cold
323
- * recovery. A complete interrupted final turn is preserved and durably
324
- * closed with missing tool errors plus any open step and turn boundaries;
325
- * only a torn final record is discarded. Unknown versions and corruption in
326
- * the committed prefix reject. Implementations MUST NOT crash-repair an
327
- * identity still bound to a live Session: a balanced live log may return as a
328
- * durable snapshot, while an open live turn rejects. Returned values may be
329
- * shared with immutable live or prepared state and must not be mutated.
330
- * Revision-based implementations may wait for one stable read/check round trip.
331
- * @param id - the persisted session to reload.
332
- * @returns the header and a log ending on a balanced `turn/end`.
333
- */
334
- abstract load(id: SessionId): Promise<SessionInspection>
352
+ Freshness: once an `append` or `flush` resolves, reads started afterwards on this backend instance observe at least that prefix.
335
353
 
354
+ ```ts cordis-catalog
336
355
  /**
337
- * Inspect an immutable logical session without committing recovery or
338
- * publishing it. A cold complete interrupted turn receives synthetic closers
339
- * in memory and a torn physical tail remains untouched. An already-live
340
- * Session instead yields its current immutable snapshot, which may contain an
341
- * open turn and its `session/end-seed` boundary. Coordinator-backed
342
- * implementations retain the exact cold unpublished Session for bounded
343
- * reuse by a later {@link prepare}. A stale ready source is reloaded; a source
344
- * already committing or reserved for resume remains exclusive, and inspection
345
- * may borrow its immutable view. Callers borrow only the immutable header and
346
- * log. Continuous external writers may delay revision convergence.
347
- * @param id - the persisted session to inspect.
348
- * @param signal - optional cancellation for queued and backend read work.
349
- * @returns the validated header and current logical event log.
356
+ * Create a new stored session and take its write ownership.
357
+ * @param header - the immutable header (id, version, cwd, lineage) to store.
358
+ * @param options - optional cancellation.
359
+ * @returns a `write` handle owned by the caller; close it to release ownership.
360
+ * @throws {SessionAlreadyExistsError} when the id already exists.
350
361
  */
351
- abstract inspect(id: SessionId, signal?: AbortSignal): Promise<SessionInspection>
362
+ abstract create(header: SessionHeader, options?: SessionPersistenceCreateOptions): Promise<SessionHandle>
352
363
 
353
364
  /**
354
- * Borrow one exact inspection while retaining any reusable prepared source.
355
- * A cold observation must pin the exact prepared Session that a later
356
- * {@link prepare} reserves. Implementations must not degrade this operation
357
- * to a detached {@link inspect} result.
358
- * @param id - persisted session to observe.
359
- * @param signal - optional cancellation for preparation work.
360
- * @returns a disposable immutable observation.
365
+ * Open an existing stored session.
366
+ *
367
+ * `read` never takes ownership and works while another handle (or process)
368
+ * holds write ownership. `write` atomically claims single-writer ownership;
369
+ * an existing active owner rejects.
370
+ * @param id - the stored session to open.
371
+ * @param access - `read` or `write`.
372
+ * @param options - optional cancellation.
373
+ * @returns the open handle.
374
+ * @throws {SessionPersistenceNotFoundError} when the session does not exist.
375
+ * @throws {SessionAlreadyOwnedError} for `write` when ownership is taken.
361
376
  */
362
- abstract borrowSession(id: SessionId, signal?: AbortSignal): Promise<BorrowedSessionSource>
377
+ abstract open(id: SessionId, access: SessionAccess, options?: SessionPersistenceOpenOptions): Promise<SessionHandle>
363
378
 
364
379
  /**
365
- * Read the stored events from `fromSeq` onward the read-from-seq
366
- * primitive for read models that resume from a watermark (e.g. a persisted
367
- * projection cache folding only the tail past its checkpoint). Unlike
368
- * {@link inspect}, it is a detached physical suffix read: no preparation
369
- * cache, torn-tail truncation, synthetic closers, or coordinator-state
370
- * publication. Only events from the valid contiguous stored prefix are
371
- * returned, so a torn fragment never reaches the caller. `fromSeq` at or
372
- * beyond the stored prefix returns an empty event list (never an error).
373
- * Backends whose medium can seek by seq
374
- * (SQLite) read only the suffix; sequential media (JSONL, both encodings)
375
- * still parse the whole artifact and skip forward — the primitive bounds
376
- * what is RETURNED and refolded, not every backend's physical read.
377
- * @param id - the persisted session to read.
378
- * @param fromSeq - first event seq to include; a non-negative safe integer.
379
- * @param signal - optional cancellation for queued and backend read work.
380
- * @returns the header and the stored events with `seq >= fromSeq`.
380
+ * Flush every active write handle owned by this service instance in one
381
+ * durability barrier: each handle's routed live events drain durably and
382
+ * its session materializes, exactly as that handle's own
383
+ * `SessionHandle.flush` would. Read handles buffer nothing and are
384
+ * untouched. A handle closed concurrently counts as flushed — close itself
385
+ * drains durably.
386
+ * @returns resolution once every write handle active at the call has flushed.
387
+ * @throws {AggregateError} naming each session whose flush failed; the
388
+ * remaining handles still flush.
381
389
  */
382
- abstract readFrom(id: SessionId, fromSeq: number, signal?: AbortSignal): Promise<{ meta: SessionHeader; events: SessionEvent[] }>
390
+ abstract flush(): Promise<void>
383
391
 
384
392
  /**
385
- * Lightweight listing from metadata, without a full-log parse.
386
- * @param signal - optional cancellation for backend listing work.
387
- * @returns one header per materialized session.
393
+ * Observe one stored session without reading its event log or taking
394
+ * ownership.
395
+ *
396
+ * The snapshot's `revision` is an opaque change token comparable only
397
+ * against revisions from the same service instance and session id: equal
398
+ * revisions may be treated as an unchanged log; unequal revisions promise
399
+ * nothing. Write-ownership churn does not change a revision. It exists for
400
+ * derived read-model caches keyed off `stat`/`list`; it plays no part in
401
+ * open, read, or resume.
402
+ * @param id - the stored session to observe.
403
+ * @param options - optional cancellation.
404
+ * @returns the snapshot, or `undefined` when the session does not exist.
388
405
  */
389
- abstract list(signal?: AbortSignal): Promise<SessionHeader[]>
406
+ abstract stat(id: SessionId, options?: SessionPersistenceStatOptions): Promise<SessionPersistenceSnapshot | undefined>
390
407
 
391
408
  /**
392
- * List materialized sessions with cheap per-log change tokens.
393
- *
394
- * Repeated observations of an unchanged log return the same revision. A
395
- * successful mutating {@link load} repair changes the next listed revision.
396
- * Revisions also distinguish independently backed stores so backend-local
397
- * counters cannot compare equal across different persistence sources.
398
- * @param signal - optional cancellation for backend snapshot-listing work.
399
- * @returns one header and opaque revision per materialized session without loading full logs.
409
+ * List every stored session visible to this process, in no promised order.
410
+ * @param options - optional cancellation.
411
+ * @returns one snapshot per stored session.
400
412
  */
401
- abstract listSnapshots(signal?: AbortSignal): Promise<SessionPersistenceSnapshot[]>
413
+ abstract list(options?: SessionPersistenceListOptions): Promise<readonly SessionPersistenceSnapshot[]>
402
414
  ```
403
415
 
404
- Types: [Session](./session.md) · [SessionEvent](./session.md) · [SessionId](./core.md)
416
+ Types: [SessionId](./core.md)
405
417
 
406
418
  Source: [`packages/session/session-persistence/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/session/session-persistence/src/index.ts)
407
419
  <!-- END GENERATED cordis-surface -->