dsh-plugin-dev-kb 1.8.0 → 1.8.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.
Files changed (229) hide show
  1. package/CHANGELOG.md +186 -178
  2. package/README.en.md +172 -172
  3. package/README.md +183 -183
  4. package/kb/INDEX.md +236 -234
  5. package/kb/README.md +88 -88
  6. package/kb/extra/AGENTS.md +77 -77
  7. package/kb/extra/deepseek-llm-api-wire-extensions.md +162 -162
  8. package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +162 -162
  9. package/kb/extra/defensive-patterns/package-surface.md +233 -232
  10. package/kb/extra/defensive-patterns/package-surface.zh.md +233 -232
  11. package/kb/extra/defensive-patterns/remote-and-api.md +117 -117
  12. package/kb/extra/defensive-patterns/remote-and-api.zh.md +117 -117
  13. package/kb/extra/development.md +221 -221
  14. package/kb/extra/development.zh.md +228 -228
  15. package/kb/extra/event-producer-consumer.md +133 -123
  16. package/kb/extra/event-producer-consumer.zh.md +135 -125
  17. package/kb/extra/module-graph.md +1961 -1888
  18. package/kb/extra/module-graph.zh.md +1963 -1890
  19. package/kb/extra/persistence-changes/2026-11-05-timeline-priority-notice.md +60 -0
  20. package/kb/extra/persistence-changes/2026-11-05-timeline-priority-notice.zh.md +60 -0
  21. package/kb/extra/persistence-changes/README.md +92 -92
  22. package/kb/extra/persistence-changes/README.zh.md +92 -92
  23. package/kb/extra/persistence-changes/historical-formats/README.md +75 -75
  24. package/kb/extra/persistence-changes/historical-formats/README.zh.md +75 -75
  25. package/kb/extra/persistence-changes/releases/README.md +103 -103
  26. package/kb/extra/persistence-changes/releases/README.zh.md +103 -103
  27. package/kb/extra/rescope.md +55 -55
  28. package/kb/extra/rescope.zh.md +55 -55
  29. package/kb/extra/subsystems/boot.md +631 -621
  30. package/kb/extra/subsystems/boot.zh.md +631 -621
  31. package/kb/extra/subsystems/computer-use.md +179 -137
  32. package/kb/extra/subsystems/computer-use.zh.md +179 -137
  33. package/kb/extra/subsystems/extensions.md +387 -384
  34. package/kb/extra/subsystems/extensions.zh.md +387 -384
  35. package/kb/extra/subsystems/feedback.md +418 -400
  36. package/kb/extra/subsystems/feedback.zh.md +418 -400
  37. package/kb/extra/subsystems/forge.md +2169 -1226
  38. package/kb/extra/subsystems/forge.zh.md +2171 -1226
  39. package/kb/extra/subsystems/liveshare.md +167 -158
  40. package/kb/extra/subsystems/liveshare.zh.md +167 -158
  41. package/kb/extra/subsystems/mcp.md +237 -234
  42. package/kb/extra/subsystems/mcp.zh.md +237 -234
  43. package/kb/extra/subsystems/office-kit.md +122 -0
  44. package/kb/extra/subsystems/office-kit.zh.md +122 -0
  45. package/kb/extra/subsystems/working-directory.md +95 -64
  46. package/kb/extra/subsystems/working-directory.zh.md +95 -64
  47. package/kb/extra/subsystems/worktrees.md +187 -127
  48. package/kb/extra/subsystems/worktrees.zh.md +187 -127
  49. package/kb/extra/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.md +21 -21
  50. package/kb/extra/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.zh.md +21 -21
  51. package/kb/extra/upgrade-guide/v0.2.1-rc.4/sandbox-unsupported-policy-error/guide.md +18 -0
  52. package/kb/extra/upgrade-guide/v0.2.1-rc.4/sandbox-unsupported-policy-error/guide.zh.md +18 -0
  53. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/llm-error-builtin-codes/guide.md +18 -0
  54. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/llm-error-builtin-codes/guide.zh.md +18 -0
  55. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-kit-package/guide.md +34 -0
  56. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-kit-package/guide.zh.md +34 -0
  57. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-operation-tools/guide.md +49 -0
  58. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-operation-tools/guide.zh.md +49 -0
  59. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-skill-resources/guide.md +36 -0
  60. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-skill-resources/guide.zh.md +36 -0
  61. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-notification-fields/guide.md +26 -0
  62. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-notification-fields/guide.zh.md +26 -0
  63. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-remote-provider/guide.md +22 -0
  64. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-remote-provider/guide.zh.md +22 -0
  65. package/kb/extra/upgrade-guide/v0.2.2-rc.1/archive-worktrees/guide.md +21 -0
  66. package/kb/extra/upgrade-guide/v0.2.2-rc.1/archive-worktrees/guide.zh.md +21 -0
  67. package/kb/extra/upgrade-guide/v0.2.2-rc.1/attachment-error-codes/guide.md +23 -0
  68. package/kb/extra/upgrade-guide/v0.2.2-rc.1/attachment-error-codes/guide.zh.md +23 -0
  69. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-ax-read-limits/guide.md +20 -0
  70. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-ax-read-limits/guide.zh.md +20 -0
  71. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-hover-ax-only/guide.md +18 -0
  72. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-hover-ax-only/guide.zh.md +18 -0
  73. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-native-platform/guide.md +23 -0
  74. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-native-platform/guide.zh.md +23 -0
  75. package/kb/extra/upgrade-guide/v0.2.2-rc.1/dsh-doc-skill/guide.md +35 -0
  76. package/kb/extra/upgrade-guide/v0.2.2-rc.1/dsh-doc-skill/guide.zh.md +35 -0
  77. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-change-request-overview/guide.md +26 -0
  78. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-change-request-overview/guide.zh.md +26 -0
  79. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-pinned-diff/guide.md +26 -0
  80. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-pinned-diff/guide.zh.md +26 -0
  81. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-review-reads/guide.md +19 -0
  82. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-review-reads/guide.zh.md +19 -0
  83. package/kb/extra/upgrade-guide/v0.2.2-rc.1/package-export-cleanup/guide.md +26 -0
  84. package/kb/extra/upgrade-guide/v0.2.2-rc.1/package-export-cleanup/guide.zh.md +26 -0
  85. package/kb/extra/upgrade-guide/v0.2.2-rc.1/plugin-update-method/guide.md +22 -0
  86. package/kb/extra/upgrade-guide/v0.2.2-rc.1/plugin-update-method/guide.zh.md +22 -0
  87. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-bootstrap-protocol/guide.md +25 -0
  88. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-bootstrap-protocol/guide.zh.md +25 -0
  89. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-inspect-output/guide.md +48 -0
  90. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-inspect-output/guide.zh.md +48 -0
  91. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-scoped-repl-default/guide.md +45 -0
  92. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-scoped-repl-default/guide.zh.md +45 -0
  93. package/kb/extra/upgrade-guide/v0.2.2-rc.1/runtime-context-reminders/guide.md +19 -0
  94. package/kb/extra/upgrade-guide/v0.2.2-rc.1/runtime-context-reminders/guide.zh.md +19 -0
  95. package/kb/extra/upgrade-guide/v0.2.2-rc.1/session-append-overloads/guide.md +17 -0
  96. package/kb/extra/upgrade-guide/v0.2.2-rc.1/session-append-overloads/guide.zh.md +17 -0
  97. package/kb/extra/upgrade-guide/v0.2.2-rc.1/subagent-interrupt-default/guide.md +27 -0
  98. package/kb/extra/upgrade-guide/v0.2.2-rc.1/subagent-interrupt-default/guide.zh.md +27 -0
  99. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-backend-cleanup-error/guide.md +20 -0
  100. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-backend-cleanup-error/guide.zh.md +20 -0
  101. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-bundle-sidebar-backend/guide.md +19 -0
  102. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-bundle-sidebar-backend/guide.zh.md +19 -0
  103. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-fetch-transport-error-codes/guide.md +18 -0
  104. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-fetch-transport-error-codes/guide.zh.md +18 -0
  105. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-search-billing/guide.md +41 -0
  106. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-search-billing/guide.zh.md +41 -0
  107. package/kb/extra/upgrade-guide/v0.2.2-rc.1/window-drag-regions/guide.md +21 -0
  108. package/kb/extra/upgrade-guide/v0.2.2-rc.1/window-drag-regions/guide.zh.md +21 -0
  109. package/kb/extra/upgrade-guide/v0.2.2-rc.2/client-module-metadata/guide.md +29 -0
  110. package/kb/extra/upgrade-guide/v0.2.2-rc.2/client-module-metadata/guide.zh.md +29 -0
  111. package/kb/extra/upgrade-guide/v0.2.2-rc.2/connection-page-peer/guide.md +25 -0
  112. package/kb/extra/upgrade-guide/v0.2.2-rc.2/connection-page-peer/guide.zh.md +25 -0
  113. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cordis-root-and-intercept-metadata/guide.md +31 -0
  114. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cordis-root-and-intercept-metadata/guide.zh.md +31 -0
  115. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-agents-without-preset/guide.md +24 -0
  116. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-agents-without-preset/guide.zh.md +24 -0
  117. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-macos-provider/guide.md +24 -0
  118. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-macos-provider/guide.zh.md +24 -0
  119. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-partial-observations/guide.md +19 -0
  120. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-partial-observations/guide.zh.md +19 -0
  121. package/kb/extra/upgrade-guide/v0.2.2-rc.2/deepseek-files-timeout/guide.md +15 -0
  122. package/kb/extra/upgrade-guide/v0.2.2-rc.2/deepseek-files-timeout/guide.zh.md +15 -0
  123. package/kb/extra/upgrade-guide/v0.2.2-rc.2/experimental-tool-error-codes/guide.md +28 -0
  124. package/kb/extra/upgrade-guide/v0.2.2-rc.2/experimental-tool-error-codes/guide.zh.md +28 -0
  125. package/kb/extra/upgrade-guide/v0.2.2-rc.2/package-export-cleanup/guide.md +36 -0
  126. package/kb/extra/upgrade-guide/v0.2.2-rc.2/package-export-cleanup/guide.zh.md +36 -0
  127. package/kb/extra/upgrade-guide/v0.2.2-rc.2/python-ptc-removal/guide.md +22 -0
  128. package/kb/extra/upgrade-guide/v0.2.2-rc.2/python-ptc-removal/guide.zh.md +22 -0
  129. package/kb/extra/upgrade-guide/v0.2.2-rc.2/session-status-package/guide.md +31 -0
  130. package/kb/extra/upgrade-guide/v0.2.2-rc.2/session-status-package/guide.zh.md +31 -0
  131. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shared-search-filter/guide.md +18 -0
  132. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shared-search-filter/guide.zh.md +18 -0
  133. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shell-fs-tool-error-codes/guide.md +30 -0
  134. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shell-fs-tool-error-codes/guide.zh.md +30 -0
  135. package/kb/extra/web-styling.md +57 -57
  136. package/kb/extra/web-styling.zh.md +57 -57
  137. package/kb/meta/search-index.json +4508 -3980
  138. package/kb/meta/source.json +18 -18
  139. package/kb/site/develop/practice/llm-adapter.md +192 -192
  140. package/kb/site/en/develop/practice/llm-adapter.md +192 -192
  141. package/kb/site/en/guide/desktop-uninstall-cleanup.md +63 -0
  142. package/kb/site/en/guide/plugin-market.md +153 -151
  143. package/kb/site/en/guide/providers.md +234 -234
  144. package/kb/site/en/guide/quickstart.md +35 -34
  145. package/kb/site/en/reference/capability-seams.md +789 -767
  146. package/kb/site/en/reference/config-catalog.md +5432 -5128
  147. package/kb/site/en/reference/cookbook/adding-a-package.md +175 -175
  148. package/kb/site/en/reference/cookbook/adding-a-tool.md +105 -104
  149. package/kb/site/en/reference/cookbook/extension-cookbook.md +178 -135
  150. package/kb/site/en/reference/cordis-api/context.md +373 -369
  151. package/kb/site/en/reference/cordis-api/fiber.md +457 -433
  152. package/kb/site/en/reference/cordis-api/inherited.md +44 -44
  153. package/kb/site/en/reference/cordis-api/service.md +118 -107
  154. package/kb/site/en/reference/experimental-persistence-catalog.md +36 -23
  155. package/kb/site/en/reference/index.md +174 -174
  156. package/kb/site/en/reference/persistence-catalog.md +9453 -9432
  157. package/kb/site/en/reference/subsystems/compaction.md +288 -288
  158. package/kb/site/en/reference/subsystems/core.md +1172 -1171
  159. package/kb/site/en/reference/subsystems/credentials.md +526 -497
  160. package/kb/site/en/reference/subsystems/filesystem.md +708 -708
  161. package/kb/site/en/reference/subsystems/index.md +82 -82
  162. package/kb/site/en/reference/subsystems/llm-streaming.md +1290 -1288
  163. package/kb/site/en/reference/subsystems/lsp.md +230 -206
  164. package/kb/site/en/reference/subsystems/persistence.md +451 -432
  165. package/kb/site/en/reference/subsystems/plan.md +91 -91
  166. package/kb/site/en/reference/subsystems/ptc-runtime.md +295 -261
  167. package/kb/site/en/reference/subsystems/sandbox.md +250 -232
  168. package/kb/site/en/reference/subsystems/session-query.md +525 -515
  169. package/kb/site/en/reference/subsystems/session-reference.md +231 -229
  170. package/kb/site/en/reference/subsystems/session.md +1478 -1431
  171. package/kb/site/en/reference/subsystems/sidebar-right.md +165 -165
  172. package/kb/site/en/reference/subsystems/skills.md +356 -356
  173. package/kb/site/en/reference/subsystems/slots.md +235 -231
  174. package/kb/site/en/reference/subsystems/terminal.md +188 -188
  175. package/kb/site/en/reference/subsystems/tools.md +902 -891
  176. package/kb/site/en/reference/subsystems/user-questions.md +330 -327
  177. package/kb/site/en/reference/subsystems/web-client.md +161 -104
  178. package/kb/site/en/reference/subsystems/web-server.md +434 -351
  179. package/kb/site/en/reference/subsystems/web.md +268 -268
  180. package/kb/site/en/reference/subsystems/workflow.md +287 -282
  181. package/kb/site/en/reference/subsystems/workspace.md +743 -651
  182. package/kb/site/en/reference/tool-catalog.md +2867 -2704
  183. package/kb/site/guide/desktop-uninstall-cleanup.md +63 -0
  184. package/kb/site/guide/plugin-market.md +153 -151
  185. package/kb/site/guide/providers.md +234 -234
  186. package/kb/site/guide/quickstart.md +35 -34
  187. package/kb/site/reference/capability-seams.md +789 -767
  188. package/kb/site/reference/config-catalog.md +5432 -5128
  189. package/kb/site/reference/cookbook/adding-a-package.md +175 -175
  190. package/kb/site/reference/cookbook/adding-a-tool.md +107 -106
  191. package/kb/site/reference/cookbook/extension-cookbook.md +182 -139
  192. package/kb/site/reference/cordis-api/context.md +373 -369
  193. package/kb/site/reference/cordis-api/fiber.md +459 -435
  194. package/kb/site/reference/cordis-api/inherited.md +44 -44
  195. package/kb/site/reference/cordis-api/service.md +118 -107
  196. package/kb/site/reference/experimental-persistence-catalog.md +36 -23
  197. package/kb/site/reference/index.md +178 -178
  198. package/kb/site/reference/persistence-catalog.md +9453 -9432
  199. package/kb/site/reference/subsystems/compaction.md +288 -288
  200. package/kb/site/reference/subsystems/core.md +1184 -1183
  201. package/kb/site/reference/subsystems/credentials.md +526 -497
  202. package/kb/site/reference/subsystems/filesystem.md +708 -708
  203. package/kb/site/reference/subsystems/index.md +82 -82
  204. package/kb/site/reference/subsystems/llm-streaming.md +1296 -1294
  205. package/kb/site/reference/subsystems/lsp.md +230 -206
  206. package/kb/site/reference/subsystems/persistence.md +451 -432
  207. package/kb/site/reference/subsystems/plan.md +91 -91
  208. package/kb/site/reference/subsystems/ptc-runtime.md +295 -261
  209. package/kb/site/reference/subsystems/sandbox.md +250 -232
  210. package/kb/site/reference/subsystems/session-query.md +525 -515
  211. package/kb/site/reference/subsystems/session-reference.md +231 -229
  212. package/kb/site/reference/subsystems/session.md +1482 -1435
  213. package/kb/site/reference/subsystems/sidebar-right.md +165 -165
  214. package/kb/site/reference/subsystems/skills.md +356 -356
  215. package/kb/site/reference/subsystems/slots.md +239 -235
  216. package/kb/site/reference/subsystems/terminal.md +188 -188
  217. package/kb/site/reference/subsystems/tools.md +902 -891
  218. package/kb/site/reference/subsystems/user-questions.md +330 -327
  219. package/kb/site/reference/subsystems/web-client.md +161 -104
  220. package/kb/site/reference/subsystems/web-server.md +434 -351
  221. package/kb/site/reference/subsystems/web.md +268 -268
  222. package/kb/site/reference/subsystems/workflow.md +287 -282
  223. package/kb/site/reference/subsystems/workspace.md +743 -651
  224. package/kb/site/reference/tool-catalog.md +2872 -2709
  225. package/lib/index.js +359 -359
  226. package/package.json +68 -68
  227. package/skills/dsh-plugin-dev-kb.md +71 -70
  228. package/kb/extra/subsystems/office-to-pdf.md +0 -87
  229. package/kb/extra/subsystems/office-to-pdf.zh.md +0 -87
@@ -1,432 +1,451 @@
1
- ---
2
- editSource: "docs/subsystems/persistence.md"
3
- rawMarkdownPath: "en/reference/subsystems/persistence.md"
4
- outline: [2,3]
5
- ---
6
-
7
- # Session Persistence
8
-
9
- 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).
10
-
11
- 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 reference](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/session/session-persistence/README.md).
12
-
13
- ## `SessionHandle` — one open channel onto a stored session
14
-
15
- 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.
16
-
17
- ```ts type-equiv
18
- /** One persistence event slice returned by {@link SessionHandle.read}. */
19
- interface SessionHandleReadResult {
20
- /**
21
- * Whether event values are exclusively owned or shared only after deep
22
- * freezing. Slicing preserves the producer's state even when no events remain.
23
- */
24
- readonly eventState: SessionSeedEventState
25
- /** Event values in a caller-owned outer array. */
26
- readonly events: readonly SessionEvent[]
27
- }
28
- ```
29
-
30
- ```ts type-equiv
31
- /**
32
- * One open channel onto a stored session. A handle is single-owner state, not
33
- * a shared service: `read` never backtracks below what this handle already
34
- * observed, a `write` handle reads its own successful appends, and `close()`
35
- * is the one teardown (idempotent, uncancellable; `Symbol.asyncDispose`
36
- * delegates to it). Every operation on a closed handle rejects with
37
- * `SessionHandleClosedError`.
38
- *
39
- * Freshness across handles: once an `append` or `flush` resolves on a write
40
- * handle, every read STARTED afterwards on the same backend instance — on any
41
- * handle, or through `stat`/`list` — observes at least that prefix.
42
- * Reads concurrent with a mutation carry no ordering promise beyond the valid
43
- * contiguous prefix.
44
- */
45
- interface SessionHandle extends AsyncDisposable {
46
- /** The stored session this handle addresses. */
47
- readonly id: SessionId
48
- /** The immutable stored header, fixed at `create`/`open`. */
49
- readonly header: SessionHeader
50
- /**
51
- * Exact fork-inherited prefix length stored with the log; `0` when
52
- * `header.isSeeded` is false. Storage metadata paired with the header for
53
- * every body read, never part of the replayable event log.
54
- */
55
- readonly inheritedEventCount: SessionLogOffset
56
- /** Whether this handle may mutate the log. */
57
- readonly access: SessionAccess
58
-
59
- /**
60
- * Read a slice of the valid contiguous logical log. The slice is a legal log
61
- * prefix segment: a torn physical tail is never returned, and repeated reads
62
- * on this handle never observe an older state than a prior read.
63
- * @param offset - first logical event seq to include; defaults to `0`.
64
- * @param length - maximum number of events to return; defaults to the rest
65
- * of the log. An offset at or past the end returns an empty list.
66
- * @param options - optional cancellation.
67
- * @returns the caller-owned outer slice plus the ownership state of its event values.
68
- */
69
- read(offset?: number, length?: number, options?: SessionHandleReadOptions): Promise<SessionHandleReadResult>
70
-
71
- /**
72
- * Append a contiguous batch continuing the current logical end. The first
73
- * event's `seq` MUST equal the stored next-seq; committed events are never
74
- * rewritten. Persistence is best-effort: on resolution the batch is
75
- * accepted, ordered, and visible to reads on this backend instance, but
76
- * only a resolved {@link flush} promises it survives a crash — a backend
77
- * may buffer or batch physical writes behind append. Rejects with
78
- * `SessionReadOnlyError` on a read handle and `SessionOwnershipLostError`
79
- * when write ownership is gone.
80
- * @param events - the contiguous batch, in seq order.
81
- * @param options - optional cancellation observed before the write starts.
82
- */
83
- append(events: readonly SessionEvent[], options?: SessionHandleAppendOptions): Promise<void>
84
-
85
- /**
86
- * The durability barrier — the one operation that promises storage: on
87
- * resolution every acknowledged append is durable and the session is
88
- * materialized for other processes; an empty created session becomes
89
- * durably listable here. Callers that must survive a crash flush; a backend
90
- * whose `append` already persists on resolution treats this as
91
- * materialize-if-needed. Rejects with `SessionReadOnlyError` on a read
92
- * handle.
93
- * @param options - optional cancellation observed before the barrier starts.
94
- */
95
- flush(options?: SessionHandleFlushOptions): Promise<void>
96
-
97
- /**
98
- * Release the handle: a read handle frees local resources; a write handle
99
- * completes pending durability and releases write ownership. Idempotent,
100
- * asynchronous, and deliberately not cancellable.
101
- */
102
- close(): Promise<void>
103
- }
104
- ```
105
-
106
- 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.
107
-
108
- ## The flush checkpoint
109
-
110
- `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.
111
-
112
- ## Crash recovery preserves an interrupted turn
113
-
114
- 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)).
115
-
116
- 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.
117
-
118
- 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.
119
-
120
- ## `SessionLocation` — per-Session artifact metadata
121
-
122
- `SessionLocation` is observational metadata for diagnostics and export; log access still goes through a session handle's `read`. Materialized persistence snapshots carry the selected artifact path, and a `SessionFormatUnsupportedError` can 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.
123
-
124
- ```ts type-equiv
125
- /**
126
- * A backend-resolved, per-Session local artifact path used by observational
127
- * snapshots and refusal diagnostics. It names a backend target for the Session
128
- * and does not authorize reads; log access still goes through a session
129
- * handle's `read`. It is not persisted Session content.
130
- */
131
- interface SessionLocation {
132
- /** Backend-specific artifact kind, for example `jsonl`. */
133
- readonly kind: string
134
- /** Absolute path to this session's backend-owned artifact. */
135
- readonly path: string
136
- }
137
- ```
138
-
139
- <a id="sessionheader--metadata-beside-the-log"></a>
140
-
141
- ## `SessionHeader` — metadata beside the log
142
-
143
- 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`.
144
-
145
- Source: [`packages/core/session/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
146
-
147
- ```ts type-equiv
148
- /**
149
- * Immutable validated storage metadata, kept outside the conversation event log.
150
- */
151
- interface SessionHeader {
152
- /**
153
- * Current logical format version, stamped from {@link SESSION_FORMAT_VERSION}.
154
- * Historical physical headers are translated before entering this interface.
155
- */
156
- readonly version: typeof SESSION_FORMAT_VERSION
157
- /** The session's id (mirrors the {@link Session}'s id). */
158
- readonly id: SessionId
159
- /** Non-negative safe-integer Unix epoch milliseconds when the session was created. */
160
- readonly createdAt: number
161
- /** Absolute working directory the session was created in (if any). */
162
- readonly cwd?: string
163
- /**
164
- * Direct creator of a subagent Session. Ordinary forks omit this field;
165
- * historical ordinary forks may retain their source id here. Presence alone
166
- * does not classify a Session as a subagent; use `origin` for that distinction.
167
- */
168
- readonly parentSession?: SessionId
169
- /**
170
- * Whether this Session contains a fork-inherited event prefix. The exact prefix
171
- * length is Session state rather than ordinary header metadata.
172
- */
173
- readonly isSeeded: boolean
174
- /**
175
- * Coarse product classification for a session created as a subagent child.
176
- * This is presentation metadata, not proof that the child is continuable.
177
- */
178
- readonly origin?: 'subagent'
179
- /**
180
- * Delegation depth: absent (zero) for a top-level session, parent depth + 1
181
- * for a subagent child. Persisted so a recursion budget survives restart and
182
- * resume — a runtime-only depth would reset a resumed child to top-level.
183
- */
184
- readonly delegationDepth?: number
185
- /**
186
- * Id of the agent preset this session's agent was composed from, when the
187
- * deployment composes per session. Durable because the preset decides the
188
- * session's tools and prompt: a resume that restored a different composition
189
- * would replay history the model can no longer act on.
190
- */
191
- readonly agentPreset?: string
192
- }
193
- ```
194
-
195
- ## Format refusal — logs a build cannot faithfully read
196
-
197
- 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.
198
-
199
- ## `CreateSessionOptions` — seeding and metadata
200
-
201
- 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.
202
-
203
- ```ts type-equiv
204
- /**
205
- * Options for creating a {@link Session} via the store. `seed` replays/forks
206
- * an existing event log; `meta` carries the caller-supplied storage fields the
207
- * store folds into a {@link SessionHeader}.
208
- */
209
- interface CreateSessionOptions {
210
- /** Initial replay or fork history supplied at construction. */
211
- readonly seed?: readonly SessionEvent[]
212
- /**
213
- * Exact fork-inherited prefix length when `meta.isSeeded` is true. The
214
- * constructor appends the child-owned tagged marker at the cut unless
215
- * the seed already includes it followed by child-owned fork closers.
216
- */
217
- readonly inheritedEventCount?: SessionLogOffset
218
- /**
219
- * Storage metadata read once before publication. `isSeeded` marks fork
220
- * lineage; supplying replay history alone does not make it inherited.
221
- */
222
- readonly meta?: {
223
- readonly cwd?: string
224
- readonly parentSession?: SessionId
225
- readonly createdAt?: number
226
- readonly isSeeded?: boolean
227
- readonly origin?: 'subagent'
228
- readonly delegationDepth?: number
229
- readonly agentPreset?: string
230
- }
231
- }
232
- ```
233
-
234
- 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 })`.
235
-
236
- ## Preparation and restoration ownership
237
-
238
- `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.
239
-
240
- ```ts type-equiv
241
- /**
242
- * Aliasing state of an adoptable Session seed. `shared-frozen` permits deeply
243
- * frozen aliases plus independently owned unfrozen values in the same seed.
244
- */
245
- type SessionSeedEventState = 'detached' | 'shared-frozen'
246
- ```
247
-
248
- ```ts type-equiv
249
- /**
250
- * Adoptable storage values transferred to {@link SessionStore.prepare}
251
- * without another copy or freeze pass.
252
- */
253
- interface RestoredSessionOptions {
254
- /** Events that are independently owned or already deeply frozen. */
255
- readonly seed: SessionEvent[]
256
- /** Independently owned storage metadata to validate and freeze in place. */
257
- readonly meta: SessionHeader
258
- /** Exact number of fork-inherited leading events decoded from storage. */
259
- readonly inheritedEventCount: SessionLogOffset
260
- /** Aliasing state carried from the operation that produced the seed. */
261
- readonly eventState: SessionSeedEventState
262
- }
263
- ```
264
-
265
- ```ts type-equiv
266
- /** Inputs accepted while constructing an unpublished Session. */
267
- type PrepareSessionOptions =
268
- | (CreateSessionOptions & { readonly eventState?: undefined })
269
- | RestoredSessionOptions
270
- ```
271
-
272
- ```ts type-equiv
273
- /** Options for a preparation whose provider retains unpublished state. */
274
- interface SessionPreparationOptions {
275
- /** Release provider-owned state when the Session was not published. */
276
- readonly release?: () => void
277
- }
278
- ```
279
-
280
- ```ts public-api
281
- /**
282
- * One exact unpublished Session and the provider state that keeps it usable.
283
- * Disposal is synchronous and idempotent. Providers decide whether release
284
- * returns the Session to a cache or discards it; publication may consume that
285
- * state before disposal, making the callback a no-op.
286
- */
287
- declare class SessionPreparation implements Disposable {
288
- /** The exact Session to use for setup and publication. */
289
- readonly session: Session;
290
- /**
291
- * Wrap an unpublished Session in one preparation lifetime.
292
- * @param session - exact unpublished Session.
293
- * @param options - optional provider release behavior.
294
- * @returns a preparation disposed after publication or rollback.
295
- */
296
- static create(session: Session, options?: SessionPreparationOptions): SessionPreparation;
297
- /** Release provider state once when this preparation leaves its caller. */
298
- [Symbol.dispose](): void;
299
- }
300
- ```
301
-
302
- ## Lightweight source revisions
303
-
304
- 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.
305
-
306
- ```ts type-equiv
307
- /**
308
- * Backend-owned token that identifies both one storage source and one revision
309
- * of a persisted session log.
310
- */
311
- type SessionPersistenceRevision = Branded<'SessionPersistenceRevision'>
312
- ```
313
-
314
- ```ts type-equiv
315
- /**
316
- * Lightweight stored-session observation returned by {@link SessionPersistence.stat}
317
- * and {@link SessionPersistence.list} without reading the full event log.
318
- */
319
- interface SessionPersistenceSnapshot {
320
- /** Detached metadata for one stored session. */
321
- readonly header: SessionHeader
322
- /** Header-only format classification; absent without versioned artifact metadata. */
323
- readonly formatStatus?: 'current' | 'migration-required'
324
- /** Opaque change token; see {@link SessionPersistence.stat}. */
325
- readonly revision: SessionPersistenceRevision
326
- /** Logical event count, when the backend can provide it cheaply from metadata; otherwise absent. */
327
- readonly eventCount?: number
328
- /** Physical artifact byte size, when the backend can provide it cheaply (JSONL); otherwise absent. */
329
- readonly sizeBytes?: number
330
- /**
331
- * Backend-resolved location of the selected materialized per-Session
332
- * artifact, for diagnostics and export. Absent when the backend has no
333
- * materialized per-Session file, including an unmaterialized pending Session.
334
- */
335
- readonly location?: SessionLocation
336
- }
337
- ```
338
-
339
- 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.
340
-
341
- ## The backend
342
-
343
- 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:
344
-
345
- - **[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.
346
-
347
- <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
348
-
349
- <a id="cordis-surface"></a>
350
-
351
- ## Cordis API
352
-
353
- Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
354
-
355
- <a id="ctxsessionpersistence--sessionpersistence-abstract-seam"></a>
356
-
357
- ### `ctx.sessionPersistence` — `SessionPersistence` (abstract seam)
358
-
359
- Durable append-only session storage addressed through per-session handles.
360
-
361
- 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.
362
-
363
- 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.
364
-
365
- Freshness: once an `append` or `flush` resolves, reads started afterwards on this backend instance observe at least that prefix.
366
-
367
- ```ts cordis-catalog
368
- /**
369
- * Create a new stored session and take its write ownership.
370
- * @param header - the immutable header (id, version, cwd, lineage) to store.
371
- * @param options - optional cancellation.
372
- * @returns a `write` handle owned by the caller; close it to release ownership.
373
- * @throws {SessionAlreadyExistsError} when the id already exists.
374
- */
375
- abstract create(header: SessionHeader, options?: SessionPersistenceCreateOptions): Promise<SessionHandle>
376
-
377
- /**
378
- * Open an existing stored session.
379
- *
380
- * `read` never takes ownership and works while another handle (or process)
381
- * holds write ownership. `write` atomically claims single-writer ownership;
382
- * an existing active owner rejects.
383
- * @param id - the stored session to open.
384
- * @param access - `read` or `write`.
385
- * @param options - optional cancellation.
386
- * @returns the open handle.
387
- * @throws {SessionPersistenceNotFoundError} when the session does not exist.
388
- * @throws {SessionAlreadyOwnedError} for `write` when ownership is taken.
389
- */
390
- abstract open(id: SessionId, access: SessionAccess, options?: SessionPersistenceOpenOptions): Promise<SessionHandle>
391
-
392
- /**
393
- * Flush every active write handle owned by this service instance in one
394
- * durability barrier: each handle's routed live events drain durably and
395
- * its session materializes, exactly as that handle's own
396
- * `SessionHandle.flush` would. Read handles buffer nothing and are
397
- * untouched. A handle closed concurrently counts as flushed — close itself
398
- * drains durably.
399
- * @returns resolution once every write handle active at the call has flushed.
400
- * @throws {AggregateError} naming each session whose flush failed; the
401
- * remaining handles still flush.
402
- */
403
- abstract flush(): Promise<void>
404
-
405
- /**
406
- * Observe one stored session without reading its event log or taking
407
- * ownership.
408
- *
409
- * The snapshot's `revision` is an opaque change token comparable only
410
- * against revisions from the same service instance and session id: equal
411
- * revisions may be treated as an unchanged log; unequal revisions promise
412
- * nothing. Write-ownership churn does not change a revision. It exists for
413
- * derived read-model caches keyed off `stat`/`list`; it plays no part in
414
- * open, read, or resume.
415
- * @param id - the stored session to observe.
416
- * @param options - optional cancellation.
417
- * @returns the snapshot, or `undefined` when the session does not exist.
418
- */
419
- abstract stat(id: SessionId, options?: SessionPersistenceStatOptions): Promise<SessionPersistenceSnapshot | undefined>
420
-
421
- /**
422
- * List every stored session visible to this process, in no promised order.
423
- * @param options - optional cancellation.
424
- * @returns one snapshot per stored session.
425
- */
426
- abstract list(options?: SessionPersistenceListOptions): Promise<readonly SessionPersistenceSnapshot[]>
427
- ```
428
-
429
- Types: [SessionId](./core.md)
430
-
431
- Source: [`packages/session/session-persistence/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/session/session-persistence/src/index.ts)
432
- <!-- END GENERATED cordis-surface -->
1
+ ---
2
+ editSource: "docs/subsystems/persistence.md"
3
+ rawMarkdownPath: "en/reference/subsystems/persistence.md"
4
+ outline: [2,3]
5
+ ---
6
+
7
+ # Session Persistence
8
+
9
+ 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).
10
+
11
+ 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 reference](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/session/session-persistence/README.md).
12
+
13
+ ## `SessionHandle` — one open channel onto a stored session
14
+
15
+ 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.
16
+
17
+ ```ts type-equiv
18
+ /** One persistence event slice returned by {@link SessionHandle.read}. */
19
+ interface SessionHandleReadResult {
20
+ /**
21
+ * Whether event values are exclusively owned or shared only after deep
22
+ * freezing. Slicing preserves the producer's state even when no events remain.
23
+ */
24
+ readonly eventState: SessionSeedEventState
25
+ /** Event values in a caller-owned outer array. */
26
+ readonly events: readonly SessionEvent[]
27
+ }
28
+ ```
29
+
30
+ ```ts type-equiv
31
+ /**
32
+ * One open channel onto a stored session. A handle is single-owner state, not
33
+ * a shared service: `read` never backtracks below what this handle already
34
+ * observed, a `write` handle reads its own successful appends, and `close()`
35
+ * is the one teardown (idempotent, uncancellable; `Symbol.asyncDispose`
36
+ * delegates to it). Every operation on a closed handle rejects with
37
+ * `SessionHandleClosedError`.
38
+ *
39
+ * Freshness across handles: once an `append` or `flush` resolves on a write
40
+ * handle, every read STARTED afterwards on the same backend instance — on any
41
+ * handle, or through `stat`/`list` — observes at least that prefix.
42
+ * Reads concurrent with a mutation carry no ordering promise beyond the valid
43
+ * contiguous prefix.
44
+ */
45
+ interface SessionHandle extends AsyncDisposable {
46
+ /** The stored session this handle addresses. */
47
+ readonly id: SessionId
48
+ /** The immutable stored header, fixed at `create`/`open`. */
49
+ readonly header: SessionHeader
50
+ /**
51
+ * Exact fork-inherited prefix length stored with the log; `0` when
52
+ * `header.isSeeded` is false. Storage metadata paired with the header for
53
+ * every body read, never part of the replayable event log.
54
+ */
55
+ readonly inheritedEventCount: SessionLogOffset
56
+ /** Whether this handle may mutate the log. */
57
+ readonly access: SessionAccess
58
+
59
+ /**
60
+ * Read a slice of the valid contiguous logical log. The slice is a legal log
61
+ * prefix segment: a torn physical tail is never returned, and repeated reads
62
+ * on this handle never observe an older state than a prior read.
63
+ * @param offset - first logical event seq to include; defaults to `0`.
64
+ * @param length - maximum number of events to return; defaults to the rest
65
+ * of the log. An offset at or past the end returns an empty list.
66
+ * @param options - optional cancellation.
67
+ * @returns the caller-owned outer slice plus the ownership state of its event values.
68
+ */
69
+ read(offset?: number, length?: number, options?: SessionHandleReadOptions): Promise<SessionHandleReadResult>
70
+
71
+ /**
72
+ * Append a contiguous batch continuing the current logical end. The first
73
+ * event's `seq` MUST equal the stored next-seq; committed events are never
74
+ * rewritten. Persistence is best-effort: on resolution the batch is
75
+ * accepted, ordered, and visible to reads on this backend instance, but
76
+ * only a resolved {@link flush} promises it survives a crash — a backend
77
+ * may buffer or batch physical writes behind append. Rejects with
78
+ * `SessionReadOnlyError` on a read handle and `SessionOwnershipLostError`
79
+ * when write ownership is gone.
80
+ * @param events - the contiguous batch, in seq order.
81
+ * @param options - optional cancellation observed before the write starts.
82
+ */
83
+ append(events: readonly SessionEvent[], options?: SessionHandleAppendOptions): Promise<void>
84
+
85
+ /**
86
+ * The durability barrier — the one operation that promises storage: on
87
+ * resolution every acknowledged append is durable and the session is
88
+ * materialized for other processes; an empty created session becomes
89
+ * durably listable here. Callers that must survive a crash flush; a backend
90
+ * whose `append` already persists on resolution treats this as
91
+ * materialize-if-needed. Rejects with `SessionReadOnlyError` on a read
92
+ * handle.
93
+ * @param options - optional cancellation observed before the barrier starts.
94
+ */
95
+ flush(options?: SessionHandleFlushOptions): Promise<void>
96
+
97
+ /**
98
+ * Release the handle: a read handle frees local resources; a write handle
99
+ * completes pending durability and releases write ownership. Idempotent,
100
+ * asynchronous, and deliberately not cancellable.
101
+ */
102
+ close(): Promise<void>
103
+ }
104
+ ```
105
+
106
+ 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.
107
+
108
+ ## The flush checkpoint
109
+
110
+ `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.
111
+
112
+ ## Crash recovery preserves an interrupted turn
113
+
114
+ 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)).
115
+
116
+ 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.
117
+
118
+ 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.
119
+
120
+ ## `SessionLocation` — per-Session artifact metadata
121
+
122
+ `SessionLocation` is observational metadata for diagnostics and export; log access still goes through a session handle's `read`. Materialized persistence snapshots carry the selected artifact path, and a `SessionFormatUnsupportedError` can 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.
123
+
124
+ ```ts type-equiv
125
+ /**
126
+ * A backend-resolved, per-Session local artifact path used by observational
127
+ * snapshots and refusal diagnostics. It names a backend target for the Session
128
+ * and does not authorize reads; log access still goes through a session
129
+ * handle's `read`. It is not persisted Session content.
130
+ */
131
+ interface SessionLocation {
132
+ /** Backend-specific artifact kind, for example `jsonl`. */
133
+ readonly kind: string
134
+ /** Absolute path to this session's backend-owned artifact. */
135
+ readonly path: string
136
+ }
137
+ ```
138
+
139
+ <a id="sessionheader--metadata-beside-the-log"></a>
140
+
141
+ ## `SessionHeader` — metadata beside the log
142
+
143
+ 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`.
144
+
145
+ Source: [`packages/core/session/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
146
+
147
+ ```ts type-equiv
148
+ /**
149
+ * Immutable validated storage metadata, kept outside the conversation event log.
150
+ */
151
+ interface SessionHeader {
152
+ /**
153
+ * Current logical format version, stamped from {@link SESSION_FORMAT_VERSION}.
154
+ * Historical physical headers are translated before entering this interface.
155
+ */
156
+ readonly version: typeof SESSION_FORMAT_VERSION
157
+ /** The session's id (mirrors the {@link Session}'s id). */
158
+ readonly id: SessionId
159
+ /** Non-negative safe-integer Unix epoch milliseconds when the session was created. */
160
+ readonly createdAt: number
161
+ /** Absolute working directory the session was created in (if any). */
162
+ readonly cwd?: string
163
+ /**
164
+ * Direct creator of a subagent Session. Ordinary forks omit this field;
165
+ * historical ordinary forks may retain their source id here. Presence alone
166
+ * does not classify a Session as a subagent; use `origin` for that distinction.
167
+ */
168
+ readonly parentSession?: SessionId
169
+ /**
170
+ * Whether this Session contains a fork-inherited event prefix. The exact prefix
171
+ * length is Session state rather than ordinary header metadata.
172
+ */
173
+ readonly isSeeded: boolean
174
+ /**
175
+ * Coarse product classification for a session created as a subagent child.
176
+ * This is presentation metadata, not proof that the child is continuable.
177
+ */
178
+ readonly origin?: 'subagent'
179
+ /**
180
+ * Delegation depth: absent (zero) for a top-level session, parent depth + 1
181
+ * for a subagent child. Persisted so a recursion budget survives restart and
182
+ * resume — a runtime-only depth would reset a resumed child to top-level.
183
+ */
184
+ readonly delegationDepth?: number
185
+ /**
186
+ * Id of the agent preset this session's agent was composed from, when the
187
+ * deployment composes per session. Durable because the preset decides the
188
+ * session's tools and prompt: a resume that restored a different composition
189
+ * would replay history the model can no longer act on.
190
+ */
191
+ readonly agentPreset?: string
192
+ }
193
+ ```
194
+
195
+ ## Format refusal — logs a build cannot faithfully read
196
+
197
+ 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.
198
+
199
+ ## `CreateSessionOptions` — seeding and metadata
200
+
201
+ 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.
202
+
203
+ ```ts type-equiv
204
+ /**
205
+ * Options for creating a {@link Session} via the store. `seed` replays/forks
206
+ * an existing event log; `meta` carries the caller-supplied storage fields the
207
+ * store folds into a {@link SessionHeader}.
208
+ */
209
+ interface CreateSessionOptions {
210
+ /** Initial replay or fork history supplied at construction. */
211
+ readonly seed?: readonly SessionEvent[]
212
+ /**
213
+ * Exact fork-inherited prefix length when `meta.isSeeded` is true. The
214
+ * constructor appends the child-owned tagged marker at the cut unless
215
+ * the seed already includes it followed by child-owned fork closers.
216
+ */
217
+ readonly inheritedEventCount?: SessionLogOffset
218
+ /**
219
+ * Storage metadata read once before publication. `isSeeded` marks fork
220
+ * lineage; supplying replay history alone does not make it inherited.
221
+ */
222
+ readonly meta?: {
223
+ readonly cwd?: string
224
+ readonly parentSession?: SessionId
225
+ readonly createdAt?: number
226
+ readonly isSeeded?: boolean
227
+ readonly origin?: 'subagent'
228
+ readonly delegationDepth?: number
229
+ readonly agentPreset?: string
230
+ }
231
+ }
232
+ ```
233
+
234
+ 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 })`.
235
+
236
+ ## Preparation and restoration ownership
237
+
238
+ `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.
239
+
240
+ ```ts type-equiv
241
+ /**
242
+ * Aliasing state of an adoptable Session seed. `shared-frozen` permits deeply
243
+ * frozen aliases plus independently owned unfrozen values in the same seed.
244
+ */
245
+ type SessionSeedEventState = 'detached' | 'shared-frozen'
246
+ ```
247
+
248
+ ```ts type-equiv
249
+ /**
250
+ * Adoptable storage values transferred to {@link SessionStore.prepare}
251
+ * without another copy or freeze pass.
252
+ */
253
+ interface RestoredSessionOptions {
254
+ /** Events that are independently owned or already deeply frozen. */
255
+ readonly seed: SessionEvent[]
256
+ /** Independently owned storage metadata to validate and freeze in place. */
257
+ readonly meta: SessionHeader
258
+ /** Exact number of fork-inherited leading events decoded from storage. */
259
+ readonly inheritedEventCount: SessionLogOffset
260
+ /** Aliasing state carried from the operation that produced the seed. */
261
+ readonly eventState: SessionSeedEventState
262
+ }
263
+ ```
264
+
265
+ ```ts type-equiv
266
+ /** Inputs accepted while constructing an unpublished Session. */
267
+ type PrepareSessionOptions =
268
+ | (CreateSessionOptions & { readonly eventState?: undefined })
269
+ | RestoredSessionOptions
270
+ ```
271
+
272
+ ```ts type-equiv
273
+ /** Options for a preparation whose provider retains unpublished state. */
274
+ interface SessionPreparationOptions {
275
+ /** Release provider-owned state when the Session was not published. */
276
+ readonly release?: () => void
277
+ }
278
+ ```
279
+
280
+ ```ts public-api
281
+ /**
282
+ * One exact unpublished Session and the provider state that keeps it usable.
283
+ * Disposal is synchronous and idempotent. Providers decide whether release
284
+ * returns the Session to a cache or discards it; publication may consume that
285
+ * state before disposal, making the callback a no-op.
286
+ */
287
+ declare class SessionPreparation implements Disposable {
288
+ /** The exact Session to use for setup and publication. */
289
+ readonly session: Session;
290
+ /**
291
+ * Wrap an unpublished Session in one preparation lifetime.
292
+ * @param session - exact unpublished Session.
293
+ * @param options - optional provider release behavior.
294
+ * @returns a preparation disposed after publication or rollback.
295
+ */
296
+ static create(session: Session, options?: SessionPreparationOptions): SessionPreparation;
297
+ /** Release provider state once when this preparation leaves its caller. */
298
+ [Symbol.dispose](): void;
299
+ }
300
+ ```
301
+
302
+ ## Lightweight source revisions
303
+
304
+ 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.
305
+
306
+ ```ts type-equiv
307
+ /**
308
+ * Backend-owned token that identifies both one storage source and one revision
309
+ * of a persisted session log.
310
+ */
311
+ type SessionPersistenceRevision = Branded<'SessionPersistenceRevision'>
312
+ ```
313
+
314
+ ```ts type-equiv
315
+ /**
316
+ * Lightweight stored-session observation returned by {@link SessionPersistence.stat}
317
+ * and {@link SessionPersistence.list} without reading the full event log.
318
+ */
319
+ interface SessionPersistenceSnapshot {
320
+ /** Detached metadata for one stored session. */
321
+ readonly header: SessionHeader
322
+ /** Header-only format classification; absent without versioned artifact metadata. */
323
+ readonly formatStatus?: 'current' | 'migration-required'
324
+ /** Opaque change token; see {@link SessionPersistence.stat}. */
325
+ readonly revision: SessionPersistenceRevision
326
+ /** Logical event count, when the backend can provide it cheaply from metadata; otherwise absent. */
327
+ readonly eventCount?: number
328
+ /** Physical artifact byte size, when the backend can provide it cheaply (JSONL); otherwise absent. */
329
+ readonly sizeBytes?: number
330
+ /**
331
+ * Backend-resolved location of the selected materialized per-Session
332
+ * artifact, for diagnostics and export. Absent when the backend has no
333
+ * materialized per-Session file, including an unmaterialized pending Session.
334
+ */
335
+ readonly location?: SessionLocation
336
+ }
337
+ ```
338
+
339
+ 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.
340
+
341
+ ## The backend
342
+
343
+ 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:
344
+
345
+ - **[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.
346
+
347
+ <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
348
+
349
+ <a id="cordis-surface"></a>
350
+
351
+ ## Cordis API
352
+
353
+ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
354
+
355
+ <a id="ctxdeepseekchatimport--deepseekchatimportservice"></a>
356
+
357
+ ### `ctx.deepseekChatImport` — `DeepseekChatImportService`
358
+
359
+ Serves the owner-only export upload route and the limits the browser checks first.
360
+
361
+ ```ts cordis-catalog
362
+ /**
363
+ * Report the limits the browser checks before uploading. Only the owner may
364
+ * import, so every other principal is refused.
365
+ * @param signal - Client cancellation or Remote withdrawal.
366
+ * @returns the largest accepted archive size.
367
+ * @throws {RemoteError} `deepseekChatImport/owner-only` for a caller that is not the owner.
368
+ */
369
+ @Remote limits(signal: AbortSignal): DeepseekChatImportLimits
370
+ ```
371
+
372
+ Source: [`packages/experimental/deepseek-chat-import/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/experimental/deepseek-chat-import/src/index.ts)
373
+
374
+ <a id="ctxsessionpersistence--sessionpersistence-abstract-seam"></a>
375
+
376
+ ### `ctx.sessionPersistence` — `SessionPersistence` (abstract seam)
377
+
378
+ Durable append-only session storage addressed through per-session handles.
379
+
380
+ 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.
381
+
382
+ 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.
383
+
384
+ Freshness: once an `append` or `flush` resolves, reads started afterwards on this backend instance observe at least that prefix.
385
+
386
+ ```ts cordis-catalog
387
+ /**
388
+ * Create a new stored session and take its write ownership.
389
+ * @param header - the immutable header (id, version, cwd, lineage) to store.
390
+ * @param options - optional cancellation.
391
+ * @returns a `write` handle owned by the caller; close it to release ownership.
392
+ * @throws {SessionAlreadyExistsError} when the id already exists.
393
+ */
394
+ abstract create(header: SessionHeader, options?: SessionPersistenceCreateOptions): Promise<SessionHandle>
395
+
396
+ /**
397
+ * Open an existing stored session.
398
+ *
399
+ * `read` never takes ownership and works while another handle (or process)
400
+ * holds write ownership. `write` atomically claims single-writer ownership;
401
+ * an existing active owner rejects.
402
+ * @param id - the stored session to open.
403
+ * @param access - `read` or `write`.
404
+ * @param options - optional cancellation.
405
+ * @returns the open handle.
406
+ * @throws {SessionPersistenceNotFoundError} when the session does not exist.
407
+ * @throws {SessionAlreadyOwnedError} for `write` when ownership is taken.
408
+ */
409
+ abstract open(id: SessionId, access: SessionAccess, options?: SessionPersistenceOpenOptions): Promise<SessionHandle>
410
+
411
+ /**
412
+ * Flush every active write handle owned by this service instance in one
413
+ * durability barrier: each handle's routed live events drain durably and
414
+ * its session materializes, exactly as that handle's own
415
+ * `SessionHandle.flush` would. Read handles buffer nothing and are
416
+ * untouched. A handle closed concurrently counts as flushed — close itself
417
+ * drains durably.
418
+ * @returns resolution once every write handle active at the call has flushed.
419
+ * @throws {AggregateError} naming each session whose flush failed; the
420
+ * remaining handles still flush.
421
+ */
422
+ abstract flush(): Promise<void>
423
+
424
+ /**
425
+ * Observe one stored session without reading its event log or taking
426
+ * ownership.
427
+ *
428
+ * The snapshot's `revision` is an opaque change token comparable only
429
+ * against revisions from the same service instance and session id: equal
430
+ * revisions may be treated as an unchanged log; unequal revisions promise
431
+ * nothing. Write-ownership churn does not change a revision. It exists for
432
+ * derived read-model caches keyed off `stat`/`list`; it plays no part in
433
+ * open, read, or resume.
434
+ * @param id - the stored session to observe.
435
+ * @param options - optional cancellation.
436
+ * @returns the snapshot, or `undefined` when the session does not exist.
437
+ */
438
+ abstract stat(id: SessionId, options?: SessionPersistenceStatOptions): Promise<SessionPersistenceSnapshot | undefined>
439
+
440
+ /**
441
+ * List every stored session visible to this process, in no promised order.
442
+ * @param options - optional cancellation.
443
+ * @returns one snapshot per stored session.
444
+ */
445
+ abstract list(options?: SessionPersistenceListOptions): Promise<readonly SessionPersistenceSnapshot[]>
446
+ ```
447
+
448
+ Types: [SessionId](./core.md)
449
+
450
+ Source: [`packages/session/session-persistence/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/session/session-persistence/src/index.ts)
451
+ <!-- END GENERATED cordis-surface -->