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.zh.md"
3
- rawMarkdownPath: "reference/subsystems/persistence.md"
4
- outline: [2,3]
5
- ---
6
-
7
- # 会话持久化
8
-
9
- 事件日志的**持久性 seam**。[session.md](./session.md) 描述了内存中的 `Session`:仅追加的 `SessionEvent` 日志即为真源。本页描述如何使该日志持久化:抽象的 `SessionPersistence` 服务、它的提供方模型与随产品交付的 JSONL 后端、flush 检查点、崩溃恢复,以及随日志一同存储的元数据头。日志承载的事件词汇在生成的[持久化日志事件目录](../persistence-catalog.md)中逐项列举。
10
-
11
- 该 seam 是一个[能力 seam](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md):一个抽象服务([dsh-session-persistence](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence),`ctx.sessionPersistence`)在现有 `SessionEvent` 上暴露 `create`/`open`/`stat`/`list`——**没有平行的持久化事件类型**——其中 `create` 与 `open` 返回逐会话的 `SessionHandle`(`read`/`append`/`flush`/`close`),它承载全部日志访问与单写者所有权。仓库随产品交付 [dsh-session-persistence-jsonl](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence-jsonl) 作为其 provider;仓库外 provider 可以实现同一服务约定。见[基于句柄的持久化 Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.zh.md)与 [session-persistence reference](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/session/session-persistence/README.zh.md)。
12
-
13
- ## `SessionHandle`——通向已存储会话的一条打开通道
14
-
15
- 每一次日志读写都经由句柄流动,绝不经由按 id 寻址的服务方法:句柄是跨进程写租约把守的唯一入口。读取会返回调用方独占的外层 slice,以及由生产者建立的 event value 别名状态。一种句柄类型同时服务两种访问——在 `read` 句柄上执行修改是运行时的 `SessionReadOnlyError`,而非类型层面的拆分——而进程内单写者所有权使得在已有活跃持有者时第二次 `open(id, 'write')` 以 `SessionAlreadyOwnedError` 拒绝。
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
- 已创建的会话自 `create` 完成之刻起即可在本进程内被观察到,而后端可以把物理实体化(纯粹的优化)推迟到第一次 `append` 或 `flush`;其他进程只能看到已实体化的会话,一个在崩溃前从未实体化的会话等于从未存在。
107
-
108
- ## flush 检查点
109
-
110
- `session/event` 是一个*同步*通知;挂载的后端按会话 id 把它路由进活跃写句柄的有界 write-behind 窗口,而不阻塞生产方(后端一次性安装这些监听器,因为持久化已保证每个 id 只有一个活跃写句柄)。第一个待处理事件会开启固定的内部批处理窗口,后续事件会加入但不会重置其截止时间。窗口到期后会通过该会话的写句柄启动一次持久化 `append`;该次写入期间接纳的事件会获得自己的截止时间,并形成后续批次。`session/flush` 会取消等待并排空至完全停稳,因此循环仍将其用作在领取下一个普通轮次之前的顺序与错误观察检查点。后台写入被拒绝时会按序保留对应事件、暂停自动路径,并通过 logger 报告;下一次显式 flush 会重试,并向其调用方响亮地拒绝。`session/disposed` 会执行同样的最终排空并关闭句柄,而 `close()` 本身会经由仍然打开的存储排空已路由的缓冲,因此后端 teardown 的关闭清扫不丢任何数据。该窗口只限制有意的批处理等待,不限制事件循环调度或后端完成持久化的延迟。
111
-
112
- ## 崩溃恢复保留被中断的轮次
113
-
114
- 一个在轮次中途崩溃的日志以打开的 `turn/start` 而无 `turn/end` 结束。持久化**不会**截断或修复它:在长周期任务中,单个轮次可能非常庞大(许多步骤、大量工具输出),而这些事件在崩溃前已被持久追加。它返回物理上有效的连续日志;只有撕裂物理尾部——属于一次从未完成的 append——中不完整的碎片会被丢弃:从中恢复的完整记录(JSONL 后端会部分解码撕裂的 Zstandard 帧)由写路径在句柄的第一次新 append 之前持久重写。修复是读方的职责:resume(agent-loop)通过其写句柄读取已存储的日志,计算 `interruptedTurnClosers`——缺失的工具错误、任何未闭合的 `step/end`,以及一个合成的 `turn/end { reason: { kind: 'interrupted' } }`——并在发布 Session 之前把它们作为普通批次通过同一句柄追加。`interrupted` 是唯一一个不由循环发出的 `TurnEndReason`(见 [session.md](./session.md#why-a-turn-ended-turnendreasonmap))。
115
-
116
- 因此修复只在写所有权之下写入:活跃会话的写句柄由其生命周期所有者持有,故并发的 `open(id, 'write')` 会以 `SessionAlreadyOwnedError` 拒绝,而不是让修复与活跃轮次竞速。只读观察方(session-query)仅在内存中用同样的闭合事件配平被中断的冷日志,不回写任何内容。
117
-
118
- 只读观察即 `open(id, 'read')`:句柄提供经过验证的连续前缀切片,绝不返回撕裂尾部,且同一句柄上的重复读取绝不会观察到比先前读取更旧的状态。持久化侧不存在已准备 Session 缓存:session-query 拥有自己的冷读缓存,按 `stat().revision` 变更令牌为每个 id 缓存一个已配平的冷 Session,仅在令牌变化时重新读取。该生命周期由[基于句柄的持久化 Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.zh.md)定义;已归档的 [Session 准备阶段记录](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/archived/architecture/2026-08-05-session-preparation.md)记载了发布边界 `SessionPreparation` 最初的决策。
119
-
120
- ## `SessionLocation`——每一 Session 的产物元数据
121
-
122
- `SessionLocation` 用于诊断与导出的观察元数据;日志访问仍走会话句柄的 `read`。已物化的持久化快照携带选中产物路径,`SessionFormatUnsupportedError` 也能指出本构建拒绝解读的原始日志。JSONL 提供其项目/会话目录内 transcript(文本记录)的绝对路径;没有逐会话工件的后端则不提供。
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`:日志旁的元数据
142
-
143
- 每个会话的元数据与事件日志**分开**存储:header 携带格式版本、cwd 与 `isSeeded` 谱系 bit,含正文的存储值则在其旁边单独携带精确 inherited cut。二者都不进入 `SessionEventMap`,也不会到达 `deriveMessages()`。logical header 通过 `session.header` 附加,Session 则以 `inheritedEventCount` 暴露其 cut。
144
-
145
- 源码:[`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
- ## 格式拒绝:本构建无法可靠读取的日志
196
-
197
- 后端用 `SessionFormatUnsupportedError` 拒绝无法可靠解读的日志,它与 `SessionPersistenceCorruptionError` 区分,因为数据没有损坏。`stat` 与 `list` 会对最高规范 generation 分类,并在不读取或改变正文的前提下转换受支持的历史 header。历史 `open` 会共享每个 Session 唯一的一次 migration preparation,再返回当前逻辑值,并保持每个源路径、字节与 inode 不变。JSONL provider 直接从该内存结果返回读句柄而不发布;写 open 则在持有单写者 claim 与文件 lease 时复用 preparation、排他发布最终 current generation,随后才返回可写句柄。即使仍有较旧的可读 generation,最高的未来 generation 仍会导致拒绝。当前格式恢复会保留已安装扩展和带 `ignorable: true` 的未知事件;历史 v0/v1/v2 迁移则会拒绝未知类型,即使它带有 ignorable 标记。后端为每个会话保留独立文件时,消息附上选定的原始日志路径。仓库外后端必须在自己的物理格式入口提供等价的仅当前句柄值与方向感知拒绝。[已发布格式迁移决策](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md)负责迁移链与不可变发布规则。
198
-
199
- ## `CreateSessionOptions`:seed 与元数据
200
-
201
- 通过 store 创建 `Session` 时会接收 `seed`(初始回放或 fork 历史)、可选的精确 `inheritedEventCount` 与 `meta`(store 整合进 `SessionHeader` 的存储层字段)。store 填充 `version`/`id` 并为 `createdAt` 提供默认值;调用方可以提供已校验的绝对 `cwd`、`parentSession` 谱系、`isSeeded` 谱系标记、可选的粗粒度 `origin`、`delegationDepth`、用于组装该 agent(智能体)的 `agentPreset` 以及已有的 `createdAt`。seeded 创建必须显式提供与 inherited prefix 完全相等的 seed 和精确 cut;constructor 会先在该 cut 追加 child-owned tagged end-seed marker,setup 再添加 child-owned event。`origin: 'subagent'` 让产品导航能够隐藏重复的 child 行;它不证明描述符有效,也不证明 child 可以恢复。
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
- 因此,回放/fork 的调用方式为 `ctx.agents.create({ sessionId, seed, meta })`——fork 还会随 `meta.isSeeded: true` 提供 `inheritedEventCount`,且只有经 agent-loop 发布的会话才会持久化,且循环会在发布之前通过新会话的写句柄存储 seed;将一个*持久化*会话恢复为活跃 agent 的调用方式为 `ctx.agents.resume({ resumeSessionId })`。
235
-
236
- ## 准备与恢复所有权
237
-
238
- `SessionStore.prepare()` 接收普通创建选项,或通过 `RestoredSessionOptions` 接收可直接接管的 seed。它的 `eventState` 表明 event value 是独占对象,还是只有深度冻结后的共享对象;生产者负责建立该状态,slice 不会根据结果长度推断其他状态。恢复流程会校验并直接接管这些值,不再复制或冻结。`SessionPreparation` 随后持有该精确的未发布 Session,直至发布或回滚;dispose 是同步且幂等的。agent-loop 的 resume 通过该会话的写句柄读取这份结果,并在准备之前追加独占的 `interruptedTurnClosers`。
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
- ## 轻量源修订号
303
-
304
- 派生读取模型的消费方会在加载完整事件日志之前比较一个低开销的不透明修订号。该修订号是来自 `stat`/`list` 的逐后端实例变更令牌:修订号相等可视为日志未变;不相等则不作任何承诺,且写所有权的变动绝不会改变修订号。session-query 以它为键管理冷读缓存;该令牌在 open、read 或 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
- 可选的 `eventCount`/`sizeBytes` 字段仍是供明确需要它们的 consumer 使用的低成本 backend observation。Session 列表不借助这两个字段打开冷日志,只读取 header 与经过 identity 校验的 projection cache hint,因此 cache 或 Session format 升级不会把启动变成 body scan。
340
-
341
- ## 后端
342
-
343
- 随产品交付的 provider 实现抽象 `SessionPersistence` 约定(`create`/`open`/`stat`/`list`,逐会话 `SessionHandle` 承载 `read`/`append`/`flush`/`close`,全程可选支持取消),并通过共享的持久化契约套件:
344
-
345
- - **[dsh-session-persistence-jsonl](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence-jsonl)**——逐会话仅追加的逻辑 JSONL 日志,默认存储为带 checksum 的连续 Zstandard frame,也可配置为原始行;具备崩溃安全的原子实体化、逐批 `fsync` 的 append,以及在第一次新 append 之前截断撕裂尾部。`stat`/`list` 携带 `sizeBytes` 与尽力而为的、由 `fs.stat` 派生的修订号。
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.zh.md"
3
+ rawMarkdownPath: "reference/subsystems/persistence.md"
4
+ outline: [2,3]
5
+ ---
6
+
7
+ # 会话持久化
8
+
9
+ 事件日志的**持久性 seam**。[session.md](./session.md) 描述了内存中的 `Session`:仅追加的 `SessionEvent` 日志即为真源。本页描述如何使该日志持久化:抽象的 `SessionPersistence` 服务、它的提供方模型与随产品交付的 JSONL 后端、flush 检查点、崩溃恢复,以及随日志一同存储的元数据头。日志承载的事件词汇在生成的[持久化日志事件目录](../persistence-catalog.md)中逐项列举。
10
+
11
+ 该 seam 是一个[能力 seam](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md):一个抽象服务([dsh-session-persistence](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence),`ctx.sessionPersistence`)在现有 `SessionEvent` 上暴露 `create`/`open`/`stat`/`list`——**没有平行的持久化事件类型**——其中 `create` 与 `open` 返回逐会话的 `SessionHandle`(`read`/`append`/`flush`/`close`),它承载全部日志访问与单写者所有权。仓库随产品交付 [dsh-session-persistence-jsonl](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence-jsonl) 作为其 provider;仓库外 provider 可以实现同一服务约定。见[基于句柄的持久化 Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.zh.md)与 [session-persistence reference](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/session/session-persistence/README.zh.md)。
12
+
13
+ ## `SessionHandle`——通向已存储会话的一条打开通道
14
+
15
+ 每一次日志读写都经由句柄流动,绝不经由按 id 寻址的服务方法:句柄是跨进程写租约把守的唯一入口。读取会返回调用方独占的外层 slice,以及由生产者建立的 event value 别名状态。一种句柄类型同时服务两种访问——在 `read` 句柄上执行修改是运行时的 `SessionReadOnlyError`,而非类型层面的拆分——而进程内单写者所有权使得在已有活跃持有者时第二次 `open(id, 'write')` 以 `SessionAlreadyOwnedError` 拒绝。
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
+ 已创建的会话自 `create` 完成之刻起即可在本进程内被观察到,而后端可以把物理实体化(纯粹的优化)推迟到第一次 `append` 或 `flush`;其他进程只能看到已实体化的会话,一个在崩溃前从未实体化的会话等于从未存在。
107
+
108
+ ## flush 检查点
109
+
110
+ `session/event` 是一个*同步*通知;挂载的后端按会话 id 把它路由进活跃写句柄的有界 write-behind 窗口,而不阻塞生产方(后端一次性安装这些监听器,因为持久化已保证每个 id 只有一个活跃写句柄)。第一个待处理事件会开启固定的内部批处理窗口,后续事件会加入但不会重置其截止时间。窗口到期后会通过该会话的写句柄启动一次持久化 `append`;该次写入期间接纳的事件会获得自己的截止时间,并形成后续批次。`session/flush` 会取消等待并排空至完全停稳,因此循环仍将其用作在领取下一个普通轮次之前的顺序与错误观察检查点。后台写入被拒绝时会按序保留对应事件、暂停自动路径,并通过 logger 报告;下一次显式 flush 会重试,并向其调用方响亮地拒绝。`session/disposed` 会执行同样的最终排空并关闭句柄,而 `close()` 本身会经由仍然打开的存储排空已路由的缓冲,因此后端 teardown 的关闭清扫不丢任何数据。该窗口只限制有意的批处理等待,不限制事件循环调度或后端完成持久化的延迟。
111
+
112
+ ## 崩溃恢复保留被中断的轮次
113
+
114
+ 一个在轮次中途崩溃的日志以打开的 `turn/start` 而无 `turn/end` 结束。持久化**不会**截断或修复它:在长周期任务中,单个轮次可能非常庞大(许多步骤、大量工具输出),而这些事件在崩溃前已被持久追加。它返回物理上有效的连续日志;只有撕裂物理尾部——属于一次从未完成的 append——中不完整的碎片会被丢弃:从中恢复的完整记录(JSONL 后端会部分解码撕裂的 Zstandard 帧)由写路径在句柄的第一次新 append 之前持久重写。修复是读方的职责:resume(agent-loop)通过其写句柄读取已存储的日志,计算 `interruptedTurnClosers`——缺失的工具错误、任何未闭合的 `step/end`,以及一个合成的 `turn/end { reason: { kind: 'interrupted' } }`——并在发布 Session 之前把它们作为普通批次通过同一句柄追加。`interrupted` 是唯一一个不由循环发出的 `TurnEndReason`(见 [session.md](./session.md#why-a-turn-ended-turnendreasonmap))。
115
+
116
+ 因此修复只在写所有权之下写入:活跃会话的写句柄由其生命周期所有者持有,故并发的 `open(id, 'write')` 会以 `SessionAlreadyOwnedError` 拒绝,而不是让修复与活跃轮次竞速。只读观察方(session-query)仅在内存中用同样的闭合事件配平被中断的冷日志,不回写任何内容。
117
+
118
+ 只读观察即 `open(id, 'read')`:句柄提供经过验证的连续前缀切片,绝不返回撕裂尾部,且同一句柄上的重复读取绝不会观察到比先前读取更旧的状态。持久化侧不存在已准备 Session 缓存:session-query 拥有自己的冷读缓存,按 `stat().revision` 变更令牌为每个 id 缓存一个已配平的冷 Session,仅在令牌变化时重新读取。该生命周期由[基于句柄的持久化 Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.zh.md)定义;已归档的 [Session 准备阶段记录](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/archived/architecture/2026-08-05-session-preparation.md)记载了发布边界 `SessionPreparation` 最初的决策。
119
+
120
+ ## `SessionLocation`——每一 Session 的产物元数据
121
+
122
+ `SessionLocation` 用于诊断与导出的观察元数据;日志访问仍走会话句柄的 `read`。已物化的持久化快照携带选中产物路径,`SessionFormatUnsupportedError` 也能指出本构建拒绝解读的原始日志。JSONL 提供其项目/会话目录内 transcript(文本记录)的绝对路径;没有逐会话工件的后端则不提供。
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`:日志旁的元数据
142
+
143
+ 每个会话的元数据与事件日志**分开**存储:header 携带格式版本、cwd 与 `isSeeded` 谱系 bit,含正文的存储值则在其旁边单独携带精确 inherited cut。二者都不进入 `SessionEventMap`,也不会到达 `deriveMessages()`。logical header 通过 `session.header` 附加,Session 则以 `inheritedEventCount` 暴露其 cut。
144
+
145
+ 源码:[`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
+ ## 格式拒绝:本构建无法可靠读取的日志
196
+
197
+ 后端用 `SessionFormatUnsupportedError` 拒绝无法可靠解读的日志,它与 `SessionPersistenceCorruptionError` 区分,因为数据没有损坏。`stat` 与 `list` 会对最高规范 generation 分类,并在不读取或改变正文的前提下转换受支持的历史 header。历史 `open` 会共享每个 Session 唯一的一次 migration preparation,再返回当前逻辑值,并保持每个源路径、字节与 inode 不变。JSONL provider 直接从该内存结果返回读句柄而不发布;写 open 则在持有单写者 claim 与文件 lease 时复用 preparation、排他发布最终 current generation,随后才返回可写句柄。即使仍有较旧的可读 generation,最高的未来 generation 仍会导致拒绝。当前格式恢复会保留已安装扩展和带 `ignorable: true` 的未知事件;历史 v0/v1/v2 迁移则会拒绝未知类型,即使它带有 ignorable 标记。后端为每个会话保留独立文件时,消息附上选定的原始日志路径。仓库外后端必须在自己的物理格式入口提供等价的仅当前句柄值与方向感知拒绝。[已发布格式迁移决策](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md)负责迁移链与不可变发布规则。
198
+
199
+ ## `CreateSessionOptions`:seed 与元数据
200
+
201
+ 通过 store 创建 `Session` 时会接收 `seed`(初始回放或 fork 历史)、可选的精确 `inheritedEventCount` 与 `meta`(store 整合进 `SessionHeader` 的存储层字段)。store 填充 `version`/`id` 并为 `createdAt` 提供默认值;调用方可以提供已校验的绝对 `cwd`、`parentSession` 谱系、`isSeeded` 谱系标记、可选的粗粒度 `origin`、`delegationDepth`、用于组装该 agent(智能体)的 `agentPreset` 以及已有的 `createdAt`。seeded 创建必须显式提供与 inherited prefix 完全相等的 seed 和精确 cut;constructor 会先在该 cut 追加 child-owned tagged end-seed marker,setup 再添加 child-owned event。`origin: 'subagent'` 让产品导航能够隐藏重复的 child 行;它不证明描述符有效,也不证明 child 可以恢复。
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
+ 因此,回放/fork 的调用方式为 `ctx.agents.create({ sessionId, seed, meta })`——fork 还会随 `meta.isSeeded: true` 提供 `inheritedEventCount`,且只有经 agent-loop 发布的会话才会持久化,且循环会在发布之前通过新会话的写句柄存储 seed;将一个*持久化*会话恢复为活跃 agent 的调用方式为 `ctx.agents.resume({ resumeSessionId })`。
235
+
236
+ ## 准备与恢复所有权
237
+
238
+ `SessionStore.prepare()` 接收普通创建选项,或通过 `RestoredSessionOptions` 接收可直接接管的 seed。它的 `eventState` 表明 event value 是独占对象,还是只有深度冻结后的共享对象;生产者负责建立该状态,slice 不会根据结果长度推断其他状态。恢复流程会校验并直接接管这些值,不再复制或冻结。`SessionPreparation` 随后持有该精确的未发布 Session,直至发布或回滚;dispose 是同步且幂等的。agent-loop 的 resume 通过该会话的写句柄读取这份结果,并在准备之前追加独占的 `interruptedTurnClosers`。
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
+ ## 轻量源修订号
303
+
304
+ 派生读取模型的消费方会在加载完整事件日志之前比较一个低开销的不透明修订号。该修订号是来自 `stat`/`list` 的逐后端实例变更令牌:修订号相等可视为日志未变;不相等则不作任何承诺,且写所有权的变动绝不会改变修订号。session-query 以它为键管理冷读缓存;该令牌在 open、read 或 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
+ 可选的 `eventCount`/`sizeBytes` 字段仍是供明确需要它们的 consumer 使用的低成本 backend observation。Session 列表不借助这两个字段打开冷日志,只读取 header 与经过 identity 校验的 projection cache hint,因此 cache 或 Session format 升级不会把启动变成 body scan。
340
+
341
+ ## 后端
342
+
343
+ 随产品交付的 provider 实现抽象 `SessionPersistence` 约定(`create`/`open`/`stat`/`list`,逐会话 `SessionHandle` 承载 `read`/`append`/`flush`/`close`,全程可选支持取消),并通过共享的持久化契约套件:
344
+
345
+ - **[dsh-session-persistence-jsonl](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence-jsonl)**——逐会话仅追加的逻辑 JSONL 日志,默认存储为带 checksum 的连续 Zstandard frame,也可配置为原始行;具备崩溃安全的原子实体化、逐批 `fsync` 的 append,以及在第一次新 append 之前截断撕裂尾部。`stat`/`list` 携带 `sizeBytes` 与尽力而为的、由 `fs.stat` 派生的修订号。
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 -->