dsh-plugin-dev-kb 1.6.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 (622) hide show
  1. package/CHANGELOG.md +186 -141
  2. package/README.en.md +172 -155
  3. package/README.md +183 -163
  4. package/kb/INDEX.md +236 -234
  5. package/kb/README.md +88 -83
  6. package/kb/extra/AGENTS.md +77 -77
  7. package/kb/extra/cli-help.md +15 -7
  8. package/kb/extra/cli-help.zh.md +15 -7
  9. package/kb/extra/cookbook/adding-a-plugin-preview.md +18 -25
  10. package/kb/extra/cookbook/adding-a-plugin-preview.zh.md +18 -25
  11. package/kb/extra/cookbook/reviewing-persistence-type-changes.md +1 -1
  12. package/kb/extra/cookbook/reviewing-persistence-type-changes.zh.md +1 -1
  13. package/kb/extra/cookbook/reviewing-ui-design.md +166 -0
  14. package/kb/extra/cookbook/reviewing-ui-design.zh.md +166 -0
  15. package/kb/extra/deepseek-llm-api-wire-extensions.md +162 -162
  16. package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +162 -162
  17. package/kb/extra/defensive-patterns/conversation-and-session.md +2 -2
  18. package/kb/extra/defensive-patterns/conversation-and-session.zh.md +2 -2
  19. package/kb/extra/defensive-patterns/general.md +2 -0
  20. package/kb/extra/defensive-patterns/general.zh.md +2 -0
  21. package/kb/extra/defensive-patterns/package-surface.md +233 -231
  22. package/kb/extra/defensive-patterns/package-surface.zh.md +233 -231
  23. package/kb/extra/defensive-patterns/remote-and-api.md +117 -115
  24. package/kb/extra/defensive-patterns/remote-and-api.zh.md +117 -115
  25. package/kb/extra/development.md +221 -214
  26. package/kb/extra/development.zh.md +228 -221
  27. package/kb/extra/event-producer-consumer.md +133 -110
  28. package/kb/extra/event-producer-consumer.zh.md +135 -112
  29. package/kb/extra/i18n/README.md +2 -0
  30. package/kb/extra/i18n/README.zh.md +2 -0
  31. package/kb/extra/module-graph.md +1961 -1776
  32. package/kb/extra/module-graph.zh.md +1963 -1778
  33. package/kb/extra/persistence-changes/2026-10-13-liveshare-share-viewer-principal.md +60 -0
  34. package/kb/extra/persistence-changes/2026-10-13-liveshare-share-viewer-principal.zh.md +60 -0
  35. package/kb/extra/persistence-changes/2026-10-19-session-team-mode.md +48 -0
  36. package/kb/extra/persistence-changes/2026-10-19-session-team-mode.zh.md +48 -0
  37. package/kb/extra/persistence-changes/2026-10-22-action-card-status.md +60 -0
  38. package/kb/extra/persistence-changes/2026-10-22-action-card-status.zh.md +60 -0
  39. package/kb/extra/persistence-changes/2026-10-25-skill-catalog-incomplete.md +60 -0
  40. package/kb/extra/persistence-changes/2026-10-25-skill-catalog-incomplete.zh.md +60 -0
  41. package/kb/extra/persistence-changes/2026-10-27-retry-control-input.md +60 -0
  42. package/kb/extra/persistence-changes/2026-10-27-retry-control-input.zh.md +60 -0
  43. package/kb/extra/persistence-changes/2026-10-27-subagent-settlement-attribution.md +60 -0
  44. package/kb/extra/persistence-changes/2026-10-27-subagent-settlement-attribution.zh.md +60 -0
  45. package/kb/extra/persistence-changes/2026-11-02-subagent-catalog-names.md +48 -0
  46. package/kb/extra/persistence-changes/2026-11-02-subagent-catalog-names.zh.md +48 -0
  47. package/kb/extra/persistence-changes/2026-11-02-team-task-owner-names.md +48 -0
  48. package/kb/extra/persistence-changes/2026-11-02-team-task-owner-names.zh.md +48 -0
  49. package/kb/extra/persistence-changes/2026-11-05-timeline-priority-notice.md +60 -0
  50. package/kb/extra/persistence-changes/2026-11-05-timeline-priority-notice.zh.md +60 -0
  51. package/kb/extra/persistence-changes/README.md +92 -90
  52. package/kb/extra/persistence-changes/README.zh.md +92 -90
  53. package/kb/extra/persistence-changes/historical-formats/README.md +75 -75
  54. package/kb/extra/persistence-changes/historical-formats/README.zh.md +75 -75
  55. package/kb/extra/persistence-changes/releases/README.md +103 -103
  56. package/kb/extra/persistence-changes/releases/README.zh.md +103 -103
  57. package/kb/extra/rescope.md +55 -53
  58. package/kb/extra/rescope.zh.md +55 -53
  59. package/kb/extra/subsystems/agent-team.md +38 -75
  60. package/kb/extra/subsystems/agent-team.zh.md +38 -75
  61. package/kb/extra/subsystems/attachment.md +36 -13
  62. package/kb/extra/subsystems/attachment.zh.md +36 -13
  63. package/kb/extra/subsystems/boot.md +631 -447
  64. package/kb/extra/subsystems/boot.zh.md +631 -447
  65. package/kb/extra/subsystems/computer-use.md +179 -59
  66. package/kb/extra/subsystems/computer-use.zh.md +179 -59
  67. package/kb/extra/subsystems/deliverables.md +2 -0
  68. package/kb/extra/subsystems/deliverables.zh.md +2 -0
  69. package/kb/extra/subsystems/extensions.md +387 -384
  70. package/kb/extra/subsystems/extensions.zh.md +387 -384
  71. package/kb/extra/subsystems/feedback.md +418 -400
  72. package/kb/extra/subsystems/feedback.zh.md +418 -400
  73. package/kb/extra/subsystems/forge.md +2169 -0
  74. package/kb/extra/subsystems/forge.zh.md +2171 -0
  75. package/kb/extra/subsystems/liveshare.md +167 -0
  76. package/kb/extra/subsystems/liveshare.zh.md +167 -0
  77. package/kb/extra/subsystems/mcp.md +237 -139
  78. package/kb/extra/subsystems/mcp.zh.md +237 -139
  79. package/kb/extra/subsystems/office-kit.md +122 -0
  80. package/kb/extra/subsystems/office-kit.zh.md +122 -0
  81. package/kb/extra/subsystems/working-directory.md +95 -64
  82. package/kb/extra/subsystems/working-directory.zh.md +95 -64
  83. package/kb/extra/subsystems/worktrees.md +187 -76
  84. package/kb/extra/subsystems/worktrees.zh.md +187 -76
  85. package/kb/extra/testing.md +1 -1
  86. package/kb/extra/testing.zh.md +1 -1
  87. package/kb/extra/ui-design-review-cases.md +445 -0
  88. package/kb/extra/ui-design-review-cases.zh.md +445 -0
  89. package/kb/extra/ui-radius.md +3 -1
  90. package/kb/extra/ui-radius.zh.md +3 -1
  91. package/kb/extra/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.md +21 -21
  92. package/kb/extra/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.zh.md +21 -21
  93. package/kb/extra/upgrade-guide/v0.2.1-alpha.1/team-direct-inbox/guide.md +1 -1
  94. package/kb/extra/upgrade-guide/v0.2.1-alpha.1/team-direct-inbox/guide.zh.md +1 -1
  95. package/kb/extra/upgrade-guide/v0.2.1-alpha.2/cordis-timer-wrappers/guide.md +24 -0
  96. package/kb/extra/upgrade-guide/v0.2.1-alpha.2/cordis-timer-wrappers/guide.zh.md +24 -0
  97. package/kb/extra/upgrade-guide/v0.2.1-alpha.2/plugin-manager-view-fields/guide.md +25 -0
  98. package/kb/extra/upgrade-guide/v0.2.1-alpha.2/plugin-manager-view-fields/guide.zh.md +25 -0
  99. package/kb/extra/upgrade-guide/v0.2.1-alpha.4/model-switch-compaction/guide.md +30 -0
  100. package/kb/extra/upgrade-guide/v0.2.1-alpha.4/model-switch-compaction/guide.zh.md +30 -0
  101. package/kb/extra/upgrade-guide/v0.2.1-alpha.4/session-model-selection/guide.md +28 -0
  102. package/kb/extra/upgrade-guide/v0.2.1-alpha.4/session-model-selection/guide.zh.md +28 -0
  103. package/kb/extra/upgrade-guide/v0.2.1-rc.1/agent-instructions-chain-budgets/guide.md +24 -0
  104. package/kb/extra/upgrade-guide/v0.2.1-rc.1/agent-instructions-chain-budgets/guide.zh.md +24 -0
  105. package/kb/extra/upgrade-guide/v0.2.1-rc.1/subagent-control-tools/guide.md +23 -0
  106. package/kb/extra/upgrade-guide/v0.2.1-rc.1/subagent-control-tools/guide.zh.md +23 -0
  107. package/kb/extra/upgrade-guide/v0.2.1-rc.2/agent-runtime-owner/guide.md +20 -0
  108. package/kb/extra/upgrade-guide/v0.2.1-rc.2/agent-runtime-owner/guide.zh.md +20 -0
  109. package/kb/extra/upgrade-guide/v0.2.1-rc.2/agent-team-subagents/guide.md +31 -0
  110. package/kb/extra/upgrade-guide/v0.2.1-rc.2/agent-team-subagents/guide.zh.md +31 -0
  111. package/kb/extra/upgrade-guide/v0.2.1-rc.2/approval-projection-dependency/guide.md +34 -0
  112. package/kb/extra/upgrade-guide/v0.2.1-rc.2/approval-projection-dependency/guide.zh.md +34 -0
  113. package/kb/extra/upgrade-guide/v0.2.1-rc.2/auto-review-shutdown/guide.md +20 -0
  114. package/kb/extra/upgrade-guide/v0.2.1-rc.2/auto-review-shutdown/guide.zh.md +20 -0
  115. package/kb/extra/upgrade-guide/v0.2.1-rc.2/auto-review-user-sources/guide.md +18 -0
  116. package/kb/extra/upgrade-guide/v0.2.1-rc.2/auto-review-user-sources/guide.zh.md +18 -0
  117. package/kb/extra/upgrade-guide/v0.2.1-rc.2/built-in-japanese/guide.md +18 -0
  118. package/kb/extra/upgrade-guide/v0.2.1-rc.2/built-in-japanese/guide.zh.md +18 -0
  119. package/kb/extra/upgrade-guide/v0.2.1-rc.2/conditional-tools/guide.md +25 -0
  120. package/kb/extra/upgrade-guide/v0.2.1-rc.2/conditional-tools/guide.zh.md +25 -0
  121. package/kb/extra/upgrade-guide/v0.2.1-rc.2/context-menu-services/guide.md +23 -0
  122. package/kb/extra/upgrade-guide/v0.2.1-rc.2/context-menu-services/guide.zh.md +23 -0
  123. package/kb/extra/upgrade-guide/v0.2.1-rc.2/cua-consent-storage/guide.md +22 -0
  124. package/kb/extra/upgrade-guide/v0.2.1-rc.2/cua-consent-storage/guide.zh.md +22 -0
  125. package/kb/extra/upgrade-guide/v0.2.1-rc.2/cua-preset-scope/guide.md +18 -0
  126. package/kb/extra/upgrade-guide/v0.2.1-rc.2/cua-preset-scope/guide.zh.md +18 -0
  127. package/kb/extra/upgrade-guide/v0.2.1-rc.2/cua-relative-coordinates/guide.md +17 -0
  128. package/kb/extra/upgrade-guide/v0.2.1-rc.2/cua-relative-coordinates/guide.zh.md +17 -0
  129. package/kb/extra/upgrade-guide/v0.2.1-rc.2/file-drag-callbacks/guide.md +19 -0
  130. package/kb/extra/upgrade-guide/v0.2.1-rc.2/file-drag-callbacks/guide.zh.md +19 -0
  131. package/kb/extra/upgrade-guide/v0.2.1-rc.2/headless-policy-composition/guide.md +27 -0
  132. package/kb/extra/upgrade-guide/v0.2.1-rc.2/headless-policy-composition/guide.zh.md +27 -0
  133. package/kb/extra/upgrade-guide/v0.2.1-rc.2/model-badge-package-removal/guide.md +18 -0
  134. package/kb/extra/upgrade-guide/v0.2.1-rc.2/model-badge-package-removal/guide.zh.md +18 -0
  135. package/kb/extra/upgrade-guide/v0.2.1-rc.2/new-session-blank-reuse/guide.md +22 -0
  136. package/kb/extra/upgrade-guide/v0.2.1-rc.2/new-session-blank-reuse/guide.zh.md +22 -0
  137. package/kb/extra/upgrade-guide/v0.2.1-rc.2/node-24/guide.md +17 -0
  138. package/kb/extra/upgrade-guide/v0.2.1-rc.2/node-24/guide.zh.md +17 -0
  139. package/kb/extra/upgrade-guide/v0.2.1-rc.2/official-bundle-lists/guide.md +18 -0
  140. package/kb/extra/upgrade-guide/v0.2.1-rc.2/official-bundle-lists/guide.zh.md +18 -0
  141. package/kb/extra/upgrade-guide/v0.2.1-rc.2/persistent-terminal-controls/guide.md +18 -0
  142. package/kb/extra/upgrade-guide/v0.2.1-rc.2/persistent-terminal-controls/guide.zh.md +18 -0
  143. package/kb/extra/upgrade-guide/v0.2.1-rc.2/pi-ai-default-capacities/guide.md +18 -0
  144. package/kb/extra/upgrade-guide/v0.2.1-rc.2/pi-ai-default-capacities/guide.zh.md +18 -0
  145. package/kb/extra/upgrade-guide/v0.2.1-rc.2/pi-ai-model-catalog/guide.md +53 -0
  146. package/kb/extra/upgrade-guide/v0.2.1-rc.2/pi-ai-model-catalog/guide.zh.md +53 -0
  147. package/kb/extra/upgrade-guide/v0.2.1-rc.2/pi-ai-protocol-replay/guide.md +30 -0
  148. package/kb/extra/upgrade-guide/v0.2.1-rc.2/pi-ai-protocol-replay/guide.zh.md +30 -0
  149. package/kb/extra/upgrade-guide/v0.2.1-rc.2/ptc-script-format/guide.md +20 -0
  150. package/kb/extra/upgrade-guide/v0.2.1-rc.2/ptc-script-format/guide.zh.md +20 -0
  151. package/kb/extra/upgrade-guide/v0.2.1-rc.2/sdk-desktop-context/guide.md +18 -0
  152. package/kb/extra/upgrade-guide/v0.2.1-rc.2/sdk-desktop-context/guide.zh.md +18 -0
  153. package/kb/extra/upgrade-guide/v0.2.1-rc.2/sdk-minimal-checkpoints/guide.md +18 -0
  154. package/kb/extra/upgrade-guide/v0.2.1-rc.2/sdk-minimal-checkpoints/guide.zh.md +18 -0
  155. package/kb/extra/upgrade-guide/v0.2.1-rc.2/session-projection-handles/guide.md +24 -0
  156. package/kb/extra/upgrade-guide/v0.2.1-rc.2/session-projection-handles/guide.zh.md +24 -0
  157. package/kb/extra/upgrade-guide/v0.2.1-rc.2/session-store-writes/guide.md +25 -0
  158. package/kb/extra/upgrade-guide/v0.2.1-rc.2/session-store-writes/guide.zh.md +25 -0
  159. package/kb/extra/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.md +22 -0
  160. package/kb/extra/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.zh.md +22 -0
  161. package/kb/extra/upgrade-guide/v0.2.1-rc.2/subagent-activation-options/guide.md +18 -0
  162. package/kb/extra/upgrade-guide/v0.2.1-rc.2/subagent-activation-options/guide.zh.md +18 -0
  163. package/kb/extra/upgrade-guide/v0.2.1-rc.2/subagent-service-methods/guide.md +19 -0
  164. package/kb/extra/upgrade-guide/v0.2.1-rc.2/subagent-service-methods/guide.zh.md +19 -0
  165. package/kb/extra/upgrade-guide/v0.2.1-rc.2/subagent-user-controls/guide.md +26 -0
  166. package/kb/extra/upgrade-guide/v0.2.1-rc.2/subagent-user-controls/guide.zh.md +26 -0
  167. package/kb/extra/upgrade-guide/v0.2.1-rc.2/tool-executor-plugins/guide.md +35 -0
  168. package/kb/extra/upgrade-guide/v0.2.1-rc.2/tool-executor-plugins/guide.zh.md +35 -0
  169. package/kb/extra/upgrade-guide/v0.2.1-rc.2/web-fetch-private-destinations/guide.md +21 -0
  170. package/kb/extra/upgrade-guide/v0.2.1-rc.2/web-fetch-private-destinations/guide.zh.md +21 -0
  171. package/kb/extra/upgrade-guide/v0.2.1-rc.2/webserver-index-peer/guide.md +17 -0
  172. package/kb/extra/upgrade-guide/v0.2.1-rc.2/webserver-index-peer/guide.zh.md +17 -0
  173. package/kb/extra/upgrade-guide/v0.2.1-rc.3/attachment-read-access/guide.md +23 -0
  174. package/kb/extra/upgrade-guide/v0.2.1-rc.3/attachment-read-access/guide.zh.md +23 -0
  175. package/kb/extra/upgrade-guide/v0.2.1-rc.3/computer-use-helper-permissions/guide.md +21 -0
  176. package/kb/extra/upgrade-guide/v0.2.1-rc.3/computer-use-helper-permissions/guide.zh.md +21 -0
  177. package/kb/extra/upgrade-guide/v0.2.1-rc.3/cua-bundle-id-consent/guide.md +20 -0
  178. package/kb/extra/upgrade-guide/v0.2.1-rc.3/cua-bundle-id-consent/guide.zh.md +20 -0
  179. package/kb/extra/upgrade-guide/v0.2.1-rc.3/generated-worktree-names/guide.md +20 -0
  180. package/kb/extra/upgrade-guide/v0.2.1-rc.3/generated-worktree-names/guide.zh.md +20 -0
  181. package/kb/extra/upgrade-guide/v0.2.1-rc.3/inbox-only-agent-wait/guide.md +19 -0
  182. package/kb/extra/upgrade-guide/v0.2.1-rc.3/inbox-only-agent-wait/guide.zh.md +19 -0
  183. package/kb/extra/upgrade-guide/v0.2.1-rc.3/independent-session-forks/guide.md +23 -0
  184. package/kb/extra/upgrade-guide/v0.2.1-rc.3/independent-session-forks/guide.zh.md +23 -0
  185. package/kb/extra/upgrade-guide/v0.2.1-rc.3/ptc-caller-cancellation/guide.md +20 -0
  186. package/kb/extra/upgrade-guide/v0.2.1-rc.3/ptc-caller-cancellation/guide.zh.md +20 -0
  187. package/kb/extra/upgrade-guide/v0.2.1-rc.3/session-follow-activation/guide.md +21 -0
  188. package/kb/extra/upgrade-guide/v0.2.1-rc.3/session-follow-activation/guide.zh.md +21 -0
  189. package/kb/extra/upgrade-guide/v0.2.1-rc.3/subagent-runtime-ownership/guide.md +21 -0
  190. package/kb/extra/upgrade-guide/v0.2.1-rc.3/subagent-runtime-ownership/guide.zh.md +21 -0
  191. package/kb/extra/upgrade-guide/v0.2.1-rc.3/team-mode-menu/guide.md +18 -0
  192. package/kb/extra/upgrade-guide/v0.2.1-rc.3/team-mode-menu/guide.zh.md +18 -0
  193. package/kb/extra/upgrade-guide/v0.2.1-rc.3/team-projection/guide.md +20 -0
  194. package/kb/extra/upgrade-guide/v0.2.1-rc.3/team-projection/guide.zh.md +20 -0
  195. package/kb/extra/upgrade-guide/v0.2.1-rc.3/web-file-downloads/guide.md +19 -0
  196. package/kb/extra/upgrade-guide/v0.2.1-rc.3/web-file-downloads/guide.zh.md +19 -0
  197. package/kb/extra/upgrade-guide/v0.2.1-rc.3/web-search-defaults/guide.md +18 -0
  198. package/kb/extra/upgrade-guide/v0.2.1-rc.3/web-search-defaults/guide.zh.md +18 -0
  199. package/kb/extra/upgrade-guide/v0.2.1-rc.4/model-listing-refresh/guide.md +22 -0
  200. package/kb/extra/upgrade-guide/v0.2.1-rc.4/model-listing-refresh/guide.zh.md +22 -0
  201. package/kb/extra/upgrade-guide/v0.2.1-rc.4/reasoning-body-props/guide.md +20 -0
  202. package/kb/extra/upgrade-guide/v0.2.1-rc.4/reasoning-body-props/guide.zh.md +20 -0
  203. package/kb/extra/upgrade-guide/v0.2.1-rc.4/sandbox-unsupported-policy-error/guide.md +18 -0
  204. package/kb/extra/upgrade-guide/v0.2.1-rc.4/sandbox-unsupported-policy-error/guide.zh.md +18 -0
  205. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/context-menu-target/guide.md +19 -0
  206. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/context-menu-target/guide.zh.md +19 -0
  207. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/cordis-loader-entry-trees/guide.md +24 -0
  208. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/cordis-loader-entry-trees/guide.zh.md +24 -0
  209. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/css-anchored-menus/guide.md +21 -0
  210. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/css-anchored-menus/guide.zh.md +21 -0
  211. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/cua-source-dev-helper/guide.md +19 -0
  212. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/cua-source-dev-helper/guide.zh.md +19 -0
  213. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/desktop-stable-channel/guide.md +18 -0
  214. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/desktop-stable-channel/guide.zh.md +18 -0
  215. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/forge-git-permissions/guide.md +21 -0
  216. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/forge-git-permissions/guide.zh.md +21 -0
  217. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/forge-worktree-entry/guide.md +29 -0
  218. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/forge-worktree-entry/guide.zh.md +29 -0
  219. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/fs-observation-policy-default-off/guide.md +29 -0
  220. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/fs-observation-policy-default-off/guide.zh.md +29 -0
  221. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/llm-error-builtin-codes/guide.md +18 -0
  222. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/llm-error-builtin-codes/guide.zh.md +18 -0
  223. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/llm-request-purpose-declarations/guide.md +29 -0
  224. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/llm-request-purpose-declarations/guide.zh.md +29 -0
  225. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/pi-ai-request-image-count/guide.md +18 -0
  226. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/pi-ai-request-image-count/guide.zh.md +18 -0
  227. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/projection-event-frames/guide.md +32 -0
  228. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/projection-event-frames/guide.zh.md +32 -0
  229. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/projection-released-checkpoint/guide.md +30 -0
  230. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/projection-released-checkpoint/guide.zh.md +30 -0
  231. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/projection-snapshot-scope/guide.md +19 -0
  232. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/projection-snapshot-scope/guide.zh.md +19 -0
  233. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/retained-ptc-timeout/guide.md +21 -0
  234. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/retained-ptc-timeout/guide.zh.md +21 -0
  235. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/schedule-agent-tools/guide.md +37 -0
  236. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/schedule-agent-tools/guide.zh.md +37 -0
  237. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/schedule-remote-host-tasks/guide.md +22 -0
  238. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/schedule-remote-host-tasks/guide.zh.md +22 -0
  239. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/sdk-run-commands/guide.md +24 -0
  240. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/sdk-run-commands/guide.zh.md +24 -0
  241. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/session-header-menu/guide.md +20 -0
  242. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/session-header-menu/guide.zh.md +20 -0
  243. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/subagent-explicit-reasoning/guide.md +20 -0
  244. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/subagent-explicit-reasoning/guide.zh.md +20 -0
  245. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/web-search-credential-modes/guide.md +17 -0
  246. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/web-search-credential-modes/guide.zh.md +17 -0
  247. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/web-search-default-provider/guide.md +19 -0
  248. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/web-search-default-provider/guide.zh.md +19 -0
  249. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/chat-process-activity-context/guide.md +18 -0
  250. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/chat-process-activity-context/guide.zh.md +18 -0
  251. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-kit-package/guide.md +34 -0
  252. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-kit-package/guide.zh.md +34 -0
  253. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-operation-tools/guide.md +49 -0
  254. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-operation-tools/guide.zh.md +49 -0
  255. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-skill-resources/guide.md +36 -0
  256. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-skill-resources/guide.zh.md +36 -0
  257. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/subagent-names/guide.md +29 -0
  258. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/subagent-names/guide.zh.md +29 -0
  259. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-notification-fields/guide.md +26 -0
  260. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-notification-fields/guide.zh.md +26 -0
  261. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-remote-provider/guide.md +22 -0
  262. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-remote-provider/guide.zh.md +22 -0
  263. package/kb/extra/upgrade-guide/v0.2.2-rc.1/archive-worktrees/guide.md +21 -0
  264. package/kb/extra/upgrade-guide/v0.2.2-rc.1/archive-worktrees/guide.zh.md +21 -0
  265. package/kb/extra/upgrade-guide/v0.2.2-rc.1/attachment-error-codes/guide.md +23 -0
  266. package/kb/extra/upgrade-guide/v0.2.2-rc.1/attachment-error-codes/guide.zh.md +23 -0
  267. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-ax-read-limits/guide.md +20 -0
  268. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-ax-read-limits/guide.zh.md +20 -0
  269. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-hover-ax-only/guide.md +18 -0
  270. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-hover-ax-only/guide.zh.md +18 -0
  271. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-native-platform/guide.md +23 -0
  272. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-native-platform/guide.zh.md +23 -0
  273. package/kb/extra/upgrade-guide/v0.2.2-rc.1/dsh-doc-skill/guide.md +35 -0
  274. package/kb/extra/upgrade-guide/v0.2.2-rc.1/dsh-doc-skill/guide.zh.md +35 -0
  275. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-change-request-overview/guide.md +26 -0
  276. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-change-request-overview/guide.zh.md +26 -0
  277. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-pinned-diff/guide.md +26 -0
  278. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-pinned-diff/guide.zh.md +26 -0
  279. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-review-reads/guide.md +19 -0
  280. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-review-reads/guide.zh.md +19 -0
  281. package/kb/extra/upgrade-guide/v0.2.2-rc.1/package-export-cleanup/guide.md +26 -0
  282. package/kb/extra/upgrade-guide/v0.2.2-rc.1/package-export-cleanup/guide.zh.md +26 -0
  283. package/kb/extra/upgrade-guide/v0.2.2-rc.1/plugin-update-method/guide.md +22 -0
  284. package/kb/extra/upgrade-guide/v0.2.2-rc.1/plugin-update-method/guide.zh.md +22 -0
  285. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-bootstrap-protocol/guide.md +25 -0
  286. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-bootstrap-protocol/guide.zh.md +25 -0
  287. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-inspect-output/guide.md +48 -0
  288. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-inspect-output/guide.zh.md +48 -0
  289. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-scoped-repl-default/guide.md +45 -0
  290. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-scoped-repl-default/guide.zh.md +45 -0
  291. package/kb/extra/upgrade-guide/v0.2.2-rc.1/runtime-context-reminders/guide.md +19 -0
  292. package/kb/extra/upgrade-guide/v0.2.2-rc.1/runtime-context-reminders/guide.zh.md +19 -0
  293. package/kb/extra/upgrade-guide/v0.2.2-rc.1/session-append-overloads/guide.md +17 -0
  294. package/kb/extra/upgrade-guide/v0.2.2-rc.1/session-append-overloads/guide.zh.md +17 -0
  295. package/kb/extra/upgrade-guide/v0.2.2-rc.1/subagent-interrupt-default/guide.md +27 -0
  296. package/kb/extra/upgrade-guide/v0.2.2-rc.1/subagent-interrupt-default/guide.zh.md +27 -0
  297. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-backend-cleanup-error/guide.md +20 -0
  298. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-backend-cleanup-error/guide.zh.md +20 -0
  299. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-bundle-sidebar-backend/guide.md +19 -0
  300. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-bundle-sidebar-backend/guide.zh.md +19 -0
  301. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-fetch-transport-error-codes/guide.md +18 -0
  302. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-fetch-transport-error-codes/guide.zh.md +18 -0
  303. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-search-billing/guide.md +41 -0
  304. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-search-billing/guide.zh.md +41 -0
  305. package/kb/extra/upgrade-guide/v0.2.2-rc.1/window-drag-regions/guide.md +21 -0
  306. package/kb/extra/upgrade-guide/v0.2.2-rc.1/window-drag-regions/guide.zh.md +21 -0
  307. package/kb/extra/upgrade-guide/v0.2.2-rc.2/client-module-metadata/guide.md +29 -0
  308. package/kb/extra/upgrade-guide/v0.2.2-rc.2/client-module-metadata/guide.zh.md +29 -0
  309. package/kb/extra/upgrade-guide/v0.2.2-rc.2/connection-page-peer/guide.md +25 -0
  310. package/kb/extra/upgrade-guide/v0.2.2-rc.2/connection-page-peer/guide.zh.md +25 -0
  311. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cordis-root-and-intercept-metadata/guide.md +31 -0
  312. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cordis-root-and-intercept-metadata/guide.zh.md +31 -0
  313. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-agents-without-preset/guide.md +24 -0
  314. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-agents-without-preset/guide.zh.md +24 -0
  315. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-macos-provider/guide.md +24 -0
  316. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-macos-provider/guide.zh.md +24 -0
  317. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-partial-observations/guide.md +19 -0
  318. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-partial-observations/guide.zh.md +19 -0
  319. package/kb/extra/upgrade-guide/v0.2.2-rc.2/deepseek-files-timeout/guide.md +15 -0
  320. package/kb/extra/upgrade-guide/v0.2.2-rc.2/deepseek-files-timeout/guide.zh.md +15 -0
  321. package/kb/extra/upgrade-guide/v0.2.2-rc.2/experimental-tool-error-codes/guide.md +28 -0
  322. package/kb/extra/upgrade-guide/v0.2.2-rc.2/experimental-tool-error-codes/guide.zh.md +28 -0
  323. package/kb/extra/upgrade-guide/v0.2.2-rc.2/package-export-cleanup/guide.md +36 -0
  324. package/kb/extra/upgrade-guide/v0.2.2-rc.2/package-export-cleanup/guide.zh.md +36 -0
  325. package/kb/extra/upgrade-guide/v0.2.2-rc.2/python-ptc-removal/guide.md +22 -0
  326. package/kb/extra/upgrade-guide/v0.2.2-rc.2/python-ptc-removal/guide.zh.md +22 -0
  327. package/kb/extra/upgrade-guide/v0.2.2-rc.2/session-status-package/guide.md +31 -0
  328. package/kb/extra/upgrade-guide/v0.2.2-rc.2/session-status-package/guide.zh.md +31 -0
  329. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shared-search-filter/guide.md +18 -0
  330. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shared-search-filter/guide.zh.md +18 -0
  331. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shell-fs-tool-error-codes/guide.md +30 -0
  332. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shell-fs-tool-error-codes/guide.zh.md +30 -0
  333. package/kb/extra/web-styling.md +57 -39
  334. package/kb/extra/web-styling.zh.md +57 -39
  335. package/kb/meta/search-index.json +4508 -3806
  336. package/kb/meta/source.json +18 -14
  337. package/kb/site/develop/cordis-tutorial/04-events.md +1 -1
  338. package/kb/site/develop/cordis-tutorial/07-into-the-harness.md +5 -4
  339. package/kb/site/develop/framework/events.md +1 -1
  340. package/kb/site/develop/practice/dynamic-cordis.md +1 -1
  341. package/kb/site/develop/practice/llm-adapter.md +192 -192
  342. package/kb/site/en/develop/cordis-tutorial/04-events.md +1 -1
  343. package/kb/site/en/develop/cordis-tutorial/07-into-the-harness.md +5 -4
  344. package/kb/site/en/develop/framework/events.md +1 -1
  345. package/kb/site/en/develop/practice/dynamic-cordis.md +1 -1
  346. package/kb/site/en/develop/practice/llm-adapter.md +192 -192
  347. package/kb/site/en/guide/desktop-uninstall-cleanup.md +63 -0
  348. package/kb/site/en/guide/network-proxy.md +1 -1
  349. package/kb/site/en/guide/plugin-market.md +153 -151
  350. package/kb/site/en/guide/providers.md +234 -230
  351. package/kb/site/en/guide/python-sdk.md +17 -2
  352. package/kb/site/en/guide/quickstart.md +35 -34
  353. package/kb/site/en/guide/schedule.md +45 -26
  354. package/kb/site/en/reference/agent-lifecycle.md +3 -1
  355. package/kb/site/en/reference/api-gateway.md +4 -4
  356. package/kb/site/en/reference/capability-seams.md +789 -719
  357. package/kb/site/en/reference/config-catalog.md +5432 -4806
  358. package/kb/site/en/reference/cookbook/adding-a-package.md +175 -173
  359. package/kb/site/en/reference/cookbook/adding-a-tool.md +105 -104
  360. package/kb/site/en/reference/cookbook/extension-cookbook.md +178 -135
  361. package/kb/site/en/reference/cordis-api/context.md +373 -369
  362. package/kb/site/en/reference/cordis-api/events.md +9 -9
  363. package/kb/site/en/reference/cordis-api/fiber.md +457 -380
  364. package/kb/site/en/reference/cordis-api/inherited.md +44 -43
  365. package/kb/site/en/reference/cordis-api/service.md +118 -107
  366. package/kb/site/en/reference/experimental-persistence-catalog.md +36 -20
  367. package/kb/site/en/reference/index.md +174 -168
  368. package/kb/site/en/reference/persistence-catalog.md +9453 -9146
  369. package/kb/site/en/reference/subsystems/client-modules.md +19 -11
  370. package/kb/site/en/reference/subsystems/compaction.md +288 -288
  371. package/kb/site/en/reference/subsystems/conversation.md +2 -0
  372. package/kb/site/en/reference/subsystems/core.md +1172 -1117
  373. package/kb/site/en/reference/subsystems/credentials.md +526 -497
  374. package/kb/site/en/reference/subsystems/filesystem.md +708 -534
  375. package/kb/site/en/reference/subsystems/index.md +82 -80
  376. package/kb/site/en/reference/subsystems/llm-streaming.md +1290 -1200
  377. package/kb/site/en/reference/subsystems/lsp.md +230 -206
  378. package/kb/site/en/reference/subsystems/persistence.md +451 -422
  379. package/kb/site/en/reference/subsystems/plan.md +91 -91
  380. package/kb/site/en/reference/subsystems/ptc-runtime.md +295 -254
  381. package/kb/site/en/reference/subsystems/sandbox.md +250 -225
  382. package/kb/site/en/reference/subsystems/schedule.md +194 -430
  383. package/kb/site/en/reference/subsystems/session-projection.md +186 -44
  384. package/kb/site/en/reference/subsystems/session-query.md +525 -515
  385. package/kb/site/en/reference/subsystems/session-reference.md +231 -228
  386. package/kb/site/en/reference/subsystems/session.md +1478 -1343
  387. package/kb/site/en/reference/subsystems/sidebar-right.md +165 -164
  388. package/kb/site/en/reference/subsystems/skills.md +356 -356
  389. package/kb/site/en/reference/subsystems/slots.md +235 -204
  390. package/kb/site/en/reference/subsystems/storage.md +26 -9
  391. package/kb/site/en/reference/subsystems/subagent.md +119 -102
  392. package/kb/site/en/reference/subsystems/system-prompt.md +13 -11
  393. package/kb/site/en/reference/subsystems/terminal.md +188 -188
  394. package/kb/site/en/reference/subsystems/token-meter.md +45 -0
  395. package/kb/site/en/reference/subsystems/tools.md +902 -751
  396. package/kb/site/en/reference/subsystems/typert.md +21 -11
  397. package/kb/site/en/reference/subsystems/user-questions.md +330 -311
  398. package/kb/site/en/reference/subsystems/web-client.md +161 -104
  399. package/kb/site/en/reference/subsystems/web-server.md +434 -306
  400. package/kb/site/en/reference/subsystems/web.md +268 -210
  401. package/kb/site/en/reference/subsystems/workflow.md +287 -282
  402. package/kb/site/en/reference/subsystems/workspace.md +743 -651
  403. package/kb/site/en/reference/tool-catalog.md +2867 -2829
  404. package/kb/site/en/reference/tool-execution-pipeline.md +5 -4
  405. package/kb/site/guide/desktop-uninstall-cleanup.md +63 -0
  406. package/kb/site/guide/network-proxy.md +1 -1
  407. package/kb/site/guide/plugin-market.md +153 -151
  408. package/kb/site/guide/providers.md +234 -230
  409. package/kb/site/guide/python-sdk.md +17 -2
  410. package/kb/site/guide/quickstart.md +35 -34
  411. package/kb/site/guide/schedule.md +45 -26
  412. package/kb/site/reference/agent-lifecycle.md +3 -1
  413. package/kb/site/reference/api-gateway.md +4 -4
  414. package/kb/site/reference/capability-seams.md +789 -719
  415. package/kb/site/reference/config-catalog.md +5432 -4806
  416. package/kb/site/reference/cookbook/adding-a-package.md +175 -173
  417. package/kb/site/reference/cookbook/adding-a-tool.md +107 -106
  418. package/kb/site/reference/cookbook/extension-cookbook.md +182 -139
  419. package/kb/site/reference/cordis-api/context.md +373 -369
  420. package/kb/site/reference/cordis-api/events.md +9 -9
  421. package/kb/site/reference/cordis-api/fiber.md +459 -380
  422. package/kb/site/reference/cordis-api/inherited.md +44 -43
  423. package/kb/site/reference/cordis-api/service.md +118 -107
  424. package/kb/site/reference/experimental-persistence-catalog.md +36 -20
  425. package/kb/site/reference/index.md +178 -172
  426. package/kb/site/reference/persistence-catalog.md +9453 -9146
  427. package/kb/site/reference/subsystems/client-modules.md +19 -11
  428. package/kb/site/reference/subsystems/compaction.md +288 -288
  429. package/kb/site/reference/subsystems/conversation.md +4 -0
  430. package/kb/site/reference/subsystems/core.md +1184 -1129
  431. package/kb/site/reference/subsystems/credentials.md +526 -497
  432. package/kb/site/reference/subsystems/filesystem.md +708 -534
  433. package/kb/site/reference/subsystems/index.md +82 -80
  434. package/kb/site/reference/subsystems/llm-streaming.md +1296 -1206
  435. package/kb/site/reference/subsystems/lsp.md +230 -206
  436. package/kb/site/reference/subsystems/persistence.md +451 -422
  437. package/kb/site/reference/subsystems/plan.md +91 -91
  438. package/kb/site/reference/subsystems/ptc-runtime.md +295 -254
  439. package/kb/site/reference/subsystems/sandbox.md +250 -225
  440. package/kb/site/reference/subsystems/schedule.md +197 -433
  441. package/kb/site/reference/subsystems/session-projection.md +186 -44
  442. package/kb/site/reference/subsystems/session-query.md +525 -515
  443. package/kb/site/reference/subsystems/session-reference.md +231 -228
  444. package/kb/site/reference/subsystems/session.md +1482 -1347
  445. package/kb/site/reference/subsystems/sidebar-right.md +165 -164
  446. package/kb/site/reference/subsystems/skills.md +356 -356
  447. package/kb/site/reference/subsystems/slots.md +239 -208
  448. package/kb/site/reference/subsystems/storage.md +26 -9
  449. package/kb/site/reference/subsystems/subagent.md +119 -102
  450. package/kb/site/reference/subsystems/system-prompt.md +13 -11
  451. package/kb/site/reference/subsystems/terminal.md +188 -188
  452. package/kb/site/reference/subsystems/token-meter.md +45 -0
  453. package/kb/site/reference/subsystems/tools.md +902 -751
  454. package/kb/site/reference/subsystems/typert.md +21 -11
  455. package/kb/site/reference/subsystems/user-questions.md +330 -311
  456. package/kb/site/reference/subsystems/web-client.md +161 -104
  457. package/kb/site/reference/subsystems/web-server.md +434 -306
  458. package/kb/site/reference/subsystems/web.md +268 -210
  459. package/kb/site/reference/subsystems/workflow.md +287 -282
  460. package/kb/site/reference/subsystems/workspace.md +743 -651
  461. package/kb/site/reference/tool-catalog.md +2872 -2835
  462. package/kb/site/reference/tool-execution-pipeline.md +4 -3
  463. package/lib/index.js +359 -244
  464. package/lib/kb-version.js +118 -0
  465. package/package.json +68 -66
  466. package/scripts/refresh-kb.mjs +153 -0
  467. package/scripts/selfcheck.mjs +18 -0
  468. package/scripts/smoke.mjs +77 -0
  469. package/skills/dsh-plugin-dev-kb.md +71 -69
  470. package/skills/dsh-plugin-starter.md +65 -0
  471. package/skills/dsh-upgrade-assistant.md +62 -0
  472. package/kb/extra/agent-lifecycle.md +0 -91
  473. package/kb/extra/agent-lifecycle.zh.md +0 -93
  474. package/kb/extra/api-gateway.md +0 -172
  475. package/kb/extra/api-gateway.zh.md +0 -178
  476. package/kb/extra/architecture.md +0 -165
  477. package/kb/extra/architecture.zh.md +0 -169
  478. package/kb/extra/capability-seams.md +0 -714
  479. package/kb/extra/capability-seams.zh.md +0 -716
  480. package/kb/extra/config-catalog.md +0 -4801
  481. package/kb/extra/config-catalog.zh.md +0 -4803
  482. package/kb/extra/cookbook/adding-a-package.md +0 -170
  483. package/kb/extra/cookbook/adding-a-package.zh.md +0 -170
  484. package/kb/extra/cookbook/adding-a-settings-card.md +0 -72
  485. package/kb/extra/cookbook/adding-a-settings-card.zh.md +0 -72
  486. package/kb/extra/cookbook/adding-a-tool.md +0 -101
  487. package/kb/extra/cookbook/adding-a-tool.zh.md +0 -103
  488. package/kb/extra/cookbook/adding-an-llm-adapter.md +0 -43
  489. package/kb/extra/cookbook/adding-an-llm-adapter.zh.md +0 -43
  490. package/kb/extra/cookbook/extension-cookbook.md +0 -132
  491. package/kb/extra/cookbook/extension-cookbook.zh.md +0 -136
  492. package/kb/extra/cordis-api/context.md +0 -364
  493. package/kb/extra/cordis-api/context.zh.md +0 -366
  494. package/kb/extra/cordis-api/events.md +0 -207
  495. package/kb/extra/cordis-api/events.zh.md +0 -209
  496. package/kb/extra/cordis-api/fiber.md +0 -375
  497. package/kb/extra/cordis-api/fiber.zh.md +0 -377
  498. package/kb/extra/cordis-api/inherited.md +0 -38
  499. package/kb/extra/cordis-api/registry.md +0 -152
  500. package/kb/extra/cordis-api/registry.zh.md +0 -154
  501. package/kb/extra/cordis-api/service.md +0 -102
  502. package/kb/extra/cordis-api/service.zh.md +0 -104
  503. package/kb/extra/cordis-tutorial/01-first-plugin.md +0 -95
  504. package/kb/extra/cordis-tutorial/01-first-plugin.zh.md +0 -95
  505. package/kb/extra/cordis-tutorial/02-lifecycle-and-effects.md +0 -98
  506. package/kb/extra/cordis-tutorial/02-lifecycle-and-effects.zh.md +0 -98
  507. package/kb/extra/cordis-tutorial/03-services.md +0 -98
  508. package/kb/extra/cordis-tutorial/03-services.zh.md +0 -98
  509. package/kb/extra/cordis-tutorial/04-events.md +0 -144
  510. package/kb/extra/cordis-tutorial/04-events.zh.md +0 -144
  511. package/kb/extra/cordis-tutorial/05-config.md +0 -111
  512. package/kb/extra/cordis-tutorial/05-config.zh.md +0 -111
  513. package/kb/extra/cordis-tutorial/06-composition-and-hmr.md +0 -113
  514. package/kb/extra/cordis-tutorial/06-composition-and-hmr.zh.md +0 -113
  515. package/kb/extra/cordis-tutorial/07-into-the-harness.md +0 -108
  516. package/kb/extra/cordis-tutorial/07-into-the-harness.zh.md +0 -108
  517. package/kb/extra/cordis-tutorial/index.md +0 -60
  518. package/kb/extra/cordis-tutorial/index.zh.md +0 -62
  519. package/kb/extra/experimental-persistence-catalog.md +0 -17
  520. package/kb/extra/experimental-persistence-catalog.zh.md +0 -17
  521. package/kb/extra/persistence-catalog.md +0 -9140
  522. package/kb/extra/persistence-catalog.zh.md +0 -9142
  523. package/kb/extra/subsystems/README.md +0 -76
  524. package/kb/extra/subsystems/README.zh.md +0 -76
  525. package/kb/extra/subsystems/approval.md +0 -170
  526. package/kb/extra/subsystems/approval.zh.md +0 -170
  527. package/kb/extra/subsystems/client-modules.md +0 -198
  528. package/kb/extra/subsystems/client-modules.zh.md +0 -198
  529. package/kb/extra/subsystems/client-resources.md +0 -92
  530. package/kb/extra/subsystems/client-resources.zh.md +0 -92
  531. package/kb/extra/subsystems/commands.md +0 -223
  532. package/kb/extra/subsystems/commands.zh.md +0 -223
  533. package/kb/extra/subsystems/compaction.md +0 -284
  534. package/kb/extra/subsystems/compaction.zh.md +0 -284
  535. package/kb/extra/subsystems/conversation.md +0 -301
  536. package/kb/extra/subsystems/conversation.zh.md +0 -303
  537. package/kb/extra/subsystems/core.md +0 -1113
  538. package/kb/extra/subsystems/core.zh.md +0 -1125
  539. package/kb/extra/subsystems/credentials.md +0 -493
  540. package/kb/extra/subsystems/credentials.zh.md +0 -493
  541. package/kb/extra/subsystems/filesystem.md +0 -530
  542. package/kb/extra/subsystems/filesystem.zh.md +0 -530
  543. package/kb/extra/subsystems/goal.md +0 -313
  544. package/kb/extra/subsystems/goal.zh.md +0 -313
  545. package/kb/extra/subsystems/jobs.md +0 -554
  546. package/kb/extra/subsystems/jobs.zh.md +0 -554
  547. package/kb/extra/subsystems/llm-streaming.md +0 -1196
  548. package/kb/extra/subsystems/llm-streaming.zh.md +0 -1202
  549. package/kb/extra/subsystems/lsp.md +0 -202
  550. package/kb/extra/subsystems/lsp.zh.md +0 -202
  551. package/kb/extra/subsystems/office-to-pdf.md +0 -87
  552. package/kb/extra/subsystems/office-to-pdf.zh.md +0 -87
  553. package/kb/extra/subsystems/permission-presets.md +0 -167
  554. package/kb/extra/subsystems/permission-presets.zh.md +0 -167
  555. package/kb/extra/subsystems/persistence.md +0 -418
  556. package/kb/extra/subsystems/persistence.zh.md +0 -418
  557. package/kb/extra/subsystems/plan.md +0 -87
  558. package/kb/extra/subsystems/plan.zh.md +0 -87
  559. package/kb/extra/subsystems/ptc-runtime.md +0 -250
  560. package/kb/extra/subsystems/ptc-runtime.zh.md +0 -250
  561. package/kb/extra/subsystems/sandbox.md +0 -221
  562. package/kb/extra/subsystems/sandbox.zh.md +0 -221
  563. package/kb/extra/subsystems/schedule.md +0 -550
  564. package/kb/extra/subsystems/schedule.zh.md +0 -550
  565. package/kb/extra/subsystems/scope.md +0 -59
  566. package/kb/extra/subsystems/scope.zh.md +0 -61
  567. package/kb/extra/subsystems/session-projection.md +0 -356
  568. package/kb/extra/subsystems/session-projection.zh.md +0 -356
  569. package/kb/extra/subsystems/session-query.md +0 -511
  570. package/kb/extra/subsystems/session-query.zh.md +0 -511
  571. package/kb/extra/subsystems/session-reference.md +0 -224
  572. package/kb/extra/subsystems/session-reference.zh.md +0 -224
  573. package/kb/extra/subsystems/session-telemetry.md +0 -212
  574. package/kb/extra/subsystems/session-telemetry.zh.md +0 -212
  575. package/kb/extra/subsystems/session-title.md +0 -213
  576. package/kb/extra/subsystems/session-title.zh.md +0 -213
  577. package/kb/extra/subsystems/session.md +0 -1339
  578. package/kb/extra/subsystems/session.zh.md +0 -1343
  579. package/kb/extra/subsystems/settings.md +0 -155
  580. package/kb/extra/subsystems/settings.zh.md +0 -155
  581. package/kb/extra/subsystems/shell.md +0 -314
  582. package/kb/extra/subsystems/shell.zh.md +0 -314
  583. package/kb/extra/subsystems/sidebar-right.md +0 -160
  584. package/kb/extra/subsystems/sidebar-right.zh.md +0 -160
  585. package/kb/extra/subsystems/skills.md +0 -352
  586. package/kb/extra/subsystems/skills.zh.md +0 -352
  587. package/kb/extra/subsystems/slots.md +0 -200
  588. package/kb/extra/subsystems/slots.zh.md +0 -204
  589. package/kb/extra/subsystems/spill.md +0 -124
  590. package/kb/extra/subsystems/spill.zh.md +0 -124
  591. package/kb/extra/subsystems/storage.md +0 -259
  592. package/kb/extra/subsystems/storage.zh.md +0 -259
  593. package/kb/extra/subsystems/subagent.md +0 -686
  594. package/kb/extra/subsystems/subagent.zh.md +0 -688
  595. package/kb/extra/subsystems/subprocess.md +0 -321
  596. package/kb/extra/subsystems/subprocess.zh.md +0 -321
  597. package/kb/extra/subsystems/system-prompt.md +0 -254
  598. package/kb/extra/subsystems/system-prompt.zh.md +0 -254
  599. package/kb/extra/subsystems/terminal.md +0 -184
  600. package/kb/extra/subsystems/terminal.zh.md +0 -184
  601. package/kb/extra/subsystems/token-meter.md +0 -106
  602. package/kb/extra/subsystems/token-meter.zh.md +0 -106
  603. package/kb/extra/subsystems/tools.md +0 -747
  604. package/kb/extra/subsystems/tools.zh.md +0 -747
  605. package/kb/extra/subsystems/typert.md +0 -690
  606. package/kb/extra/subsystems/typert.zh.md +0 -690
  607. package/kb/extra/subsystems/user-questions.md +0 -307
  608. package/kb/extra/subsystems/user-questions.zh.md +0 -307
  609. package/kb/extra/subsystems/web-client.md +0 -100
  610. package/kb/extra/subsystems/web-client.zh.md +0 -100
  611. package/kb/extra/subsystems/web-server.md +0 -302
  612. package/kb/extra/subsystems/web-server.zh.md +0 -302
  613. package/kb/extra/subsystems/web.md +0 -206
  614. package/kb/extra/subsystems/web.zh.md +0 -206
  615. package/kb/extra/subsystems/workflow.md +0 -278
  616. package/kb/extra/subsystems/workflow.zh.md +0 -278
  617. package/kb/extra/subsystems/workspace.md +0 -647
  618. package/kb/extra/subsystems/workspace.zh.md +0 -647
  619. package/kb/extra/tool-catalog.md +0 -2824
  620. package/kb/extra/tool-catalog.zh.md +0 -2832
  621. package/kb/extra/tool-execution-pipeline.md +0 -65
  622. package/kb/extra/tool-execution-pipeline.zh.md +0 -67
@@ -1,1343 +1,1478 @@
1
- ---
2
- editSource: "docs/subsystems/session.md"
3
- rawMarkdownPath: "en/reference/subsystems/session.md"
4
- outline: [2,3]
5
- ---
6
-
7
- # Sessions
8
-
9
- The in-memory, event-sourced model of [dsh-session](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/core/session). A `Session` is an **append-only log** of typed `SessionEvent`s — the single source of truth for an agent's whole interaction history. The LLM message history is *derived* from the log, never stored separately; replay is re-derivation from the same events. How the log is made **durable** (the persistence seam, backends, crash recovery) is the sibling concern on [persistence.md](./persistence.md).
10
-
11
- Source: [`packages/core/session/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
12
-
13
- ## `SessionEventMap` — the event vocabulary
14
-
15
- The append-only event types. Merge-extensible: a plugin declares extra event types via declaration merging — e.g. the [compaction seam](./compaction.md) adds `compaction/start` / `compaction/summary` / `compaction/end`, and `@deepseek-ai/dsh-hook-protocol` adds log-only `hook/invoked` / `hook/result` records for a hook bridge. Like `compaction/*`, these are NOT `SurfaceEventType`s (no `surfaceOp`). The generated [persistence log event catalog](../persistence-catalog.md) enumerates every member — core and merged — with its payload, surface badge, and declaration site.
16
-
17
- ```ts type-equiv
18
- /** A user-role specialization of the shared message representation. */
19
- interface UserMessage extends MessageBase {
20
- readonly role: 'user'
21
- }
22
- ```
23
-
24
- ```ts type-equiv
25
- /**
26
- * The merge-extensible, append-only source of truth for an agent interaction.
27
- * Message history is derived from this log. Every event is lossless JSON and
28
- * sequence numbers stay contiguous. Assistant attempt events embed their exact
29
- * compact raw streams so persistence stores one durable settlement per attempt.
30
- */
31
- interface SessionEventMap {
32
- /**
33
- * Opens turn `turn` before the loop claims queued input or runs pre-step.
34
- * Rejection, empty input, cancellation, or failure may close it with no
35
- * step; otherwise the following identified `user/message` event or batch
36
- * records the messages entering the step.
37
- */
38
- 'turn/start': { turn: number }
39
- /**
40
- * Closes turn `turn` with the {@link TurnEndReason} that ended it. A turn
41
- * with no entered step has no `step/start` or `step/end`. The loop does not await a
42
- * flush at turn boundaries: `dsh-session-checkpoint-policy` owns the
43
- * per-request durability checkpoint, and consumers that read storage after
44
- * `whenIdle()` flush themselves. Success commits the turn; rejection is
45
- * reported live and does not prevent later work.
46
- */
47
- 'turn/end': { turn: number; reason: TurnEndReason }
48
- /** Opens step `step` of turn `turn` — one model call plus the tool executions it requested. */
49
- 'step/start': { turn: number; step: number }
50
- /** Closes step `step` of turn `turn`. */
51
- 'step/end': { turn: number; step: number }
52
- /**
53
- * A user-role message on the model-visible surface: a direct human prompt
54
- * (the queued message claimed for this turn), a synthetic `agent.inject()`
55
- * context (file-change notices, subdir AGENTS.md, skill content, cron
56
- * notifications, …), or an entered goal continuation round. All three
57
- * project their `content` verbatim; `source` tells them apart.
58
- */
59
- 'user/message': UserMessage
60
- /** An incremental agent session change admitted at the named turn and step. */
61
- 'developer/message': {
62
- turn: number
63
- step: number
64
- message: DeveloperMessage
65
- /** Earlier request/header defining every tool addition; required exactly when additions are present. */
66
- headerSeq?: SessionSeq
67
- }
68
- /**
69
- * The rendered system prompt on the model-visible surface. The loop appends
70
- * the first one as surface node 0 before the step's first `user/message`.
71
- * A prepared in-history route can append nonempty changes in a continuing
72
- * series. An incapable route or new series normalizes text to the first system
73
- * node. Normalization empties nonempty later nodes, then rewrites the head if
74
- * needed, through logged per-node replacements. An empty rendering always
75
- * clears all active system nodes, leaving no older instructions model-visible.
76
- * Empty later nodes are dormant and project to no message; an empty head with
77
- * no active later node records "no system prompt". Restored nonempty text follows
78
- * the same route and series rule; empty nodes never restore older text.
79
- */
80
- 'system/message': { turn: number; step: number; message: SystemMessage }
81
- /**
82
- * Assembled assistant message for one step (derived history uses this).
83
- * Carries the step's `usage` when the adapter reported token accounting, so
84
- * the model output and its accounting travel together (there is no separate
85
- * usage record). `usage` is absent when the adapter reported none. A turn
86
- * cancelled mid-stream finalizes its delivered text/reasoning prefix as this
87
- * event with `interrupted: true`; undispatched tool calls are absent. The
88
- * marker distinguishes that prefix without re-deriving interruption from turn
89
- * boundaries. An aborted turn with no such event streamed no visible content.
90
- */
91
- 'assistant/message': {
92
- turn: number
93
- step: number
94
- message: AssistantMessage
95
- /** Exact timed model stream, compacted without joining delta boundaries. */
96
- stream: AssistantStreamRecord[]
97
- usage?: TokenUsage
98
- interrupted?: true
99
- }
100
- /**
101
- * One model attempt that committed no surface message. The embedded stream
102
- * preserves a failed, retried, cancelled, or stream-error attempt that
103
- * reached settlement without fabricating model-visible history.
104
- */
105
- 'assistant/attempt': { turn: number; step: number; stream: AssistantStreamRecord[] }
106
- /**
107
- * The model requested one tool invocation: `name` with the raw `arguments`
108
- * JSON string exactly as the model produced it (unparsed). `callId` pairs the
109
- * call with its `tool/result`.
110
- */
111
- 'tool/call': { turn: number; step: number; callId: ToolCallId; name: string; arguments: string }
112
- /**
113
- * A completed tool call's model-facing result, optional internal failure
114
- * identity and user-facing reason, and optional tool-private `meta`
115
- * presentation payload. The reason remains outside the model-facing message.
116
- * `meta` is
117
- * opaque to the core (the producing tool owns its shape and reads it back in
118
- * `presentResult`) but MUST be JSON-serializable: `Session.append`
119
- * runtime-validates all event data with `isJsonValue`, so a non-serializable
120
- * `meta` is rejected at the source, and the durable log reproduces the
121
- * identical card on replay. Absent
122
- * unless the tool attaches one (e.g. `dsh-tool-fs` carries its result-time
123
- * contextual diff here).
124
- */
125
- 'tool/result': {
126
- turn: number
127
- step: number
128
- message: ToolResultMessage
129
- /**
130
- * Optional failure identity and raw user-facing reason, outside model content;
131
- * allowed only when the message has `isError: true`.
132
- */
133
- error?: { name: string; code: string; reason?: string }
134
- meta?: JsonValue
135
- }
136
- /**
137
- * Full header for the next request, appended inside its step before dispatch.
138
- * It is log-only; the latest snapshot reconstructs the request header.
139
- */
140
- 'request/header': {
141
- header: EpochHeader
142
- reason: RequestHeaderReason
143
- /** This request begins a distinct model-message series, independently of the header reason. */
144
- startsSeries?: true
145
- }
146
- /**
147
- * Route metadata for the next request, logged only when the route, capacity,
148
- * or system prompt update mode changes. It does not participate in request
149
- * reconstruction or header equality. Prompt admission uses the bound prepared
150
- * call's capability, not this snapshot from an earlier request.
151
- */
152
- 'request/context': RequestContext
153
- /**
154
- * Separates inherited or restored history from later lifecycle-owned work.
155
- * This log-only marker need not be at {@link Session.firstLiveSeq}: a fork
156
- * seed can already contain its tagged marker and child-owned synthetic
157
- * closers before construction.
158
- *
159
- * A fresh fork child owns one `{ inherited: true }` marker at its exact
160
- * inherited-prefix cut, even when that prefix ends in an ancestor marker.
161
- * `buildForkSeed` appends that marker before any synthetic closers; the
162
- * `Session` constructor supplies it when given only the inherited prefix.
163
- * The last tagged marker is the current Session's cut; untagged markers
164
- * keep ordinary restore and replay lifecycle boundaries.
165
- *
166
- * Only the `Session` constructor and `buildForkSeed` may create this marker.
167
- * Session append does not reject other writers, so a plugin appending one
168
- * would silently classify every live bracket before it as seed history.
169
- *
170
- * An owner of a standalone open/close bracket (`compaction/start` …
171
- * `compaction/end`) reads it because seed history and live work are otherwise
172
- * byte-identical: an unmatched opening marker before this event belongs to
173
- * an ended lifecycle, whatever ended it. NOT a liveness signal about other
174
- * writers — a concurrently live session holds its own boundary elsewhere,
175
- * so tolerating concurrent writers needs a signal beyond the log.
176
- */
177
- 'session/end-seed': { inherited?: true }
178
- }
179
- ```
180
-
181
- `UserMessage` is the identified, frozen user-role value shared by ordinary prompts, injected context, steering, and live inbox events. Event wrappers add only event-local position or outcome facts; the loop adds only driver-owned routing state while an item remains pending.
182
-
183
- <a id="the-request-header-event-requestheader"></a>
184
-
185
- ### The request header event: `request/header`
186
-
187
- The request envelope — the `EpochHeader` (call config + markers for adapter-supplied defaults + assembled tool schemas) — is logged session state, so every conversation request is a pure function of the log (the reconstructability Agent Note). The rendered system prompt is not part of the header: it is derived history, the `system/message` event at surface node 0 and any later in-history system node ([decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md)), so a prompt change replaces or appends a system node and leaves the header unchanged. A full `request/header` snapshot with reason `'initial'` or `'resume'` records each loop-instance boundary; a changed request appends a snapshot with reason `'change'`; and an unchanged envelope beginning an explicitly declared message series or following a surface replacement appends a snapshot with reason `'series'`. An `'initial'`, `'resume'`, or `'change'` snapshot carries `startsSeries: true` when that request also begins a series; reason `'series'` already records that fact. Ordinary append-only later Turns, further Steps, and retries in the same model-message series inherit the latest snapshot. `foldRequestHeader(events)` reconstructs the header by selecting the latest snapshot. The event is not a `SurfaceEventType`: it produces no LLM message.
188
-
189
- ```ts type-equiv
190
- /**
191
- * Logged request state outside derived history: call config and tools. The
192
- * system prompt is derived history — surface node 0, a `system/message` event.
193
- * The latest full `request/header` snapshot reconstructs the header; canonical
194
- * empty optional fields are absent.
195
- */
196
- interface EpochHeader {
197
- /** The conversation's call configuration (provider, model, reasoning effort, and sampling scalars). */
198
- config: LlmCallConfig
199
- /** Effective config fields materialized from the exact adapter rather than proposed by a caller. */
200
- adapterDefaults?: LlmCallConfigAdapterDefaults
201
- /** Assembled tool schemas; absent for a tool-less request. */
202
- tools?: ToolSchema[]
203
- /** Retired request text; system prompts belong to system/message events.
204
- * @persistenceReserved
205
- */
206
- system?: never
207
- }
208
- ```
209
-
210
- Current event acceptance requires canonical `request/header.header`: any `system` field is forbidden, and `tools: []` and `adapterDefaults: {}` must be omitted. Whitespace-only system-message content, `config.stop: []`, and nested extensions remain unchanged. Seed, append, and current persistence reads reject noncanonical headers rather than silently normalizing them; [the V3 envelope decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-09-06-v3-canonical-session-envelopes.md) owns historical conversion. Legacy v0 logs containing `request/header-delta` or its full-snapshot `fallback` reason are rejected rather than replayed incompletely.
211
-
212
- ### The route capacity event: `request/context`
213
-
214
- The context metadata of the route a request resolved to is separate logged state, appended beside `request/header` inside the same step and only when the provider, model, capacity, or `systemPromptUpdate` mode differs from the previous record. It stays outside `EpochHeader` because that type is the reconstruction contract compared field-wise by `headerEquals`: capacity and the update mode describe a route, not a request input, so folding them in would let a route change register as a request-envelope `change` and would pull adapter metadata into the loop's request reconstruction. Like `request/header`, it is not a `SurfaceEventType` and produces no LLM message. `session.requestContext()` folds the latest record incrementally; the agent loop reads that record's `systemPromptUpdate` when it decides whether a changed system prompt replaces the latest system node or is appended after the cached history ([decision rule](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/agent-loop/README.md#understand-the-implementation)). A route whose adapter advertises no capacity is recorded with `contextWindow` absent, so the new record clears an older route's capacity; a route without a declared update mode likewise clears an older route's `systemPromptUpdate`.
215
-
216
- ```ts type-equiv
217
- /** Registration-bound metadata for one resolved model route. */
218
- interface RequestContext {
219
- /** Registered provider route the metadata belongs to. */
220
- provider: string
221
- /** Provider-owned model id the metadata belongs to. */
222
- model: string
223
- /** Maximum combined request and response context in tokens, when advertised. */
224
- contextWindow?: number
225
- /** `'in-history'` when the route reads the latest `system` message at any position as the effective system prompt. */
226
- systemPromptUpdate?: SystemPromptUpdate
227
- }
228
- ```
229
-
230
- ## `SessionEvent<T>` — one log entry
231
-
232
- A proper discriminated union over `type` (not independent `type`/`data` unions), so `switch (event.type)` narrows `event.data` without casts. `seq` is the monotonic position in the log (`seq = log.length`); `time` is epoch ms.
233
-
234
- ```ts type-equiv
235
- /** Sequence number of one existing event in a Session log. */
236
- type SessionSeq = BrandedNumber<'SessionSeq'>
237
- ```
238
-
239
- ```ts type-equiv
240
- /** A Session log gap, prefix length, or read offset, which may equal the event count. */
241
- type SessionLogOffset = BrandedNumber<'SessionLogOffset'>
242
- ```
243
-
244
- ```ts type-equiv
245
- /** Inclusive Session event watermark, or `-1` before any event exists. */
246
- type SessionSeqCursor = SessionSeq | -1
247
- ```
248
-
249
- ```ts type-equiv
250
- /** One existing Session event position, or explicit absence. */
251
- type OptionalSessionSeq = SessionSeq | null
252
- ```
253
-
254
- `SessionSeq(value)` and `SessionLogOffset(value)` admit only non-negative safe integers and reject negative zero. They add compile-time brands without changing the serialized number; arithmetic returns an ordinary `number` that callers must admit again through the constructor for its intended role.
255
-
256
- ```ts type-equiv
257
- /**
258
- * One immutable entry in the session log.
259
- *
260
- * A proper discriminated union over `type` (not independent `type`/`data`
261
- * unions), so `switch (event.type)` narrows `event.data` without casts.
262
- *
263
- * The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional:
264
- * they only exist on {@link SurfaceEventType} variants (`system/message`, `user/message`,
265
- * `assistant/message`, `tool/result`).
266
- * Non-surface events (boundary markers, attempts, errors) never carry
267
- * surface metadata — the compiler enforces this at `Session.append()`
268
- * call sites.
269
- */
270
- type SessionEvent<T extends SessionEventType = SessionEventType> = {
271
- [K in SessionEventType]: {
272
- type: K
273
- /** Monotonic sequence number within the session. */
274
- seq: SessionSeq
275
- /** Unix epoch milliseconds. */
276
- time: number
277
- data: SessionEventMap[K]
278
- /**
279
- * Marks an event a reader may safely skip when it does not recognize
280
- * `type`. Absent means required: a reader meeting an unrecognized type
281
- * without this marker MUST refuse to reconstruct the session instead of
282
- * silently dropping the event, because an unrecognized required event may
283
- * change how the rest of the log is interpreted. A writer sets `true` only
284
- * on purely informational records whose loss cannot affect reconstruction;
285
- * defaulting to required means a forgotten marker over-refuses (an
286
- * inconvenience) rather than silently resuming a gutted session.
287
- */
288
- ignorable?: true
289
- } & (K extends SurfaceEventType ? SurfaceIntent<K> : {
290
- surfaceOp?: never
291
- sourceEventSeqs?: never
292
- })
293
- }[T]
294
- ```
295
-
296
- `SessionEventType = keyof SessionEventMap`. Because `SessionEventMap` is merge-extensible, switches over `SessionEvent` must NOT use `assertNever` — a plugin-added variant is a valid unknown value; handle the known cases and fall through `default`.
297
-
298
- Every surface event requires `surfaceOp`; known log-only events forbid both surface metadata fields. Native unknown or obsolete ignorable envelopes remain opaque. `assistant/message` embeds its provider stream and forbids `sourceEventSeqs`. System, user, and tool surface events may cite a complete non-empty set of unique earlier events when source attribution or replacement coverage requires it. A `tool/result` may carry `data.error` only when its message has `isError: true`; failure identity remains optional for failed results.
299
-
300
- ## Surface types
301
-
302
- The message-producing types (`SurfaceEventType` — `system/message`, `developer/message`, `user/message`, `assistant/message`, `tool/result`) carry surface metadata declaring how they join the ordered derived surface. `system/message` holds the rendered system prompt: the loop appends the first one as surface node 0 and, when the prompt changes, replaces exactly the latest system node or appends a new one on an in-history route; the surface fold rejects any other replacement covering a `system/message` at node 0, while a later system node is ordinary history that a compaction replacement may shadow. See the [session surface Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-06-18-session-surface.md).
303
-
304
- ### `SurfaceEventType` — the message-producing subset of event types
305
-
306
- ```ts type-equiv
307
- /**
308
- * The subset of {@link SessionEventType} values whose events produce LLM
309
- * messages and are eligible to appear on the ordered surface. Only these
310
- * event types may carry {@link SurfaceOp}; system, user, and tool events may also cite
311
- * earlier sources through {@link SessionEvent.sourceEventSeqs}.
312
- */
313
- type SurfaceEventType =
314
- | 'system/message'
315
- | 'developer/message'
316
- | 'user/message'
317
- | 'assistant/message'
318
- | 'tool/result'
319
- ```
320
-
321
- ### `SurfaceOp` — how an event entered the surface
322
-
323
- ```ts type-equiv
324
- /**
325
- * How a session event entered the ordered surface. Only valid on
326
- * {@link SurfaceEventType} events.
327
- *
328
- * - `'append'`: added to the tail — normal path for user/assistant/tool
329
- * messages.
330
- * - `{ op: 'replace', startSeq, endSeq }`: replaces surface nodes from `startSeq`
331
- * (inclusive) through `endSeq` (inclusive) with this node. Both must exist as
332
- * surface nodes in the current surface. `startSeq === endSeq` replaces a single
333
- * node. The node's {@link SessionEvent.sourceEventSeqs} must include every
334
- * shadowed surface node. Used by compaction; any surface-replacing producer
335
- * may use it.
336
- */
337
- type SurfaceOp =
338
- | 'append'
339
- | { op: 'replace'; startSeq: SessionSeq; endSeq: SessionSeq }
340
- ```
341
-
342
- `'append'` is the normal tail-append path. `replace` contains exactly `op`, `startSeq`, and `endSeq`, with no aliases or extra keys. It shadows the inclusive span between those current surface event sequences and inserts the new event in their place; equal endpoints replace one entry. Endpoints must precede the replacing event, but their relative order is surface order, not numeric sequence order.
343
-
344
- ### `SurfaceIntent` — the parameter to `session.append()`
345
-
346
- ```ts type-equiv
347
- /**
348
- * Surface placement and cited source-event seqs for {@link Session.append}. Required on
349
- * message-producing events and forbidden on log-only events.
350
- */
351
- type SurfaceIntent<T extends SurfaceEventType = SurfaceEventType> = {
352
- surfaceOp: SurfaceOp
353
- } & (T extends 'assistant/message' ? {
354
- /** Assistant messages embed their provider stream instead of citing source events. */
355
- sourceEventSeqs?: never
356
- } : {
357
- /** Complete non-empty set of known earlier source-event seqs. */
358
- sourceEventSeqs?: SessionSeq[]
359
- })
360
- ```
361
-
362
- Required for `SurfaceEventType` events — every message-producing event must declare how it joins the surface, the sole source of derived model history. A human-facing transcript is the other projection and reads the log's append-origin events instead, because the surface deliberately shadows the ranges a replacement summarizes (`isAppendSurfaceEvent` in [dsh-session](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/README.md)). Non-surface types reject it at compile time.
363
-
364
- `assistant/message` cannot carry `sourceEventSeqs`; its `stream` owns exact provider evidence. Other surface events omit the field when they cite no earlier event and use a complete non-empty list when they do.
365
-
366
- <a id="plugin-owned-message-projections"></a>
367
- ### Plugin-owned message projections
368
-
369
- Content-changing plugins mark their event declaration with `@messageProjection` and register a pure definition through `ctx.sessions.registerMessageProjection()`. Session validates the complete decision through that definition before commit, applies its immutable message updates, and advances `contentGeneration`. It rejects missing interpreters, including on restore and detached folding; unloading a used definition blocks cached reads. Detached readers pass explicit definitions to `foldSurface(events, projections)` and apply `projectedMessages` through `deriveEventMessage()`. The installed format catalog assembles first-party definitions for offline readers. The [image-offload plugin](./compaction.md#image-offload) owns its image-specific event and interpretation.
370
-
371
- ```ts type-equiv
372
- /** Readonly history immediately before a message-projection event. */
373
- interface SessionMessageProjectionContext {
374
- /** Current message-producing event sequences in model-visible order. */
375
- nodes: readonly SessionSeq[]
376
- /** Contiguous event window; entries at or beyond the candidate seq are not committed inputs. */
377
- events: readonly SessionEvent[]
378
- /** Absolute sequence of the window's first event. */
379
- baseSeq: SessionLogOffset
380
- /** Previously projected messages keyed by their original event sequences. */
381
- messages: ReadonlyMap<SessionSeq, Message>
382
- }
383
- ```
384
-
385
- ```ts type-equiv
386
- /** Pure interpretation of one plugin-owned event that changes existing message content. */
387
- interface SessionMessageProjection<T extends SessionEventType = SessionEventType> {
388
- /** Event interpreted by this definition; declare it with `@messageProjection` in SessionEventMap. */
389
- type: T
390
- /**
391
- * Validate the complete durable decision before returning any updates. Preserve
392
- * message identities and publish immutable copies without mutating the input.
393
- * @param event - candidate event, not yet applied to the supplied history.
394
- * @param context - history preceding this decision.
395
- * @returns changed current messages keyed by their original sequences.
396
- * @throws when the durable decision cannot be applied to this history.
397
- */
398
- project(event: SessionEvent<T>, context: SessionMessageProjectionContext): ReadonlyMap<SessionSeq, Message>
399
- }
400
- ```
401
-
402
- ### `SessionSurface` — the live readonly surface projection
403
-
404
- `Session.surface` returns the session's stable `SessionSurface` view. The same incremental manager validates append candidates before commit and advances this projection from committed events; callers can observe membership and replacement generation but cannot invoke validation.
405
-
406
- `SurfaceManager(log, baseSeq?, projections?)` can instead fold a contiguous loaded window whose first event has the absolute sequence `baseSeq`. Every event remains contiguous in that absolute sequence space, and a replacement that crosses the window head fails because its declared range is absent.
407
-
408
- ```ts type-equiv
409
- /** Readonly live projection of the message-producing session events. */
410
- interface SessionSurface {
411
- /** Current surface event sequences in model-visible order. */
412
- readonly nodes: readonly SessionSeq[]
413
- /** Monotonic count of committed positional replacements. */
414
- readonly replaceGeneration: number
415
- /** Monotonic count of committed replacements and plugin-owned message changes. */
416
- readonly contentGeneration: number
417
- }
418
- ```
419
-
420
- ### `SurfaceFoldReplacement` and `SurfaceFoldResult` — a complete surface replay
421
-
422
- `foldSurface(events, projections)` returns detached current event sequences together with the actual sequences shadowed by each declared replacement range. The live manager uses the same transitions without retaining replacement history. Its `replaceGeneration` increments for each committed replacement so incremental consumers can distinguish pure tail growth from a rewrite.
423
-
424
- ```ts type-equiv
425
- /** One replacement operation observed while folding a session surface. */
426
- interface SurfaceFoldReplacement {
427
- /** Seq of the event that replaced the prior surface range. */
428
- seq: SessionSeq
429
- /** Declared inclusive start seq of the replaced surface range. */
430
- start: SessionSeq
431
- /** Declared inclusive end seq of the replaced surface range. */
432
- end: SessionSeq
433
- /** Actual surface entries removed by the operation, in surface order. */
434
- shadowedSeqs: SessionSeq[]
435
- }
436
- ```
437
-
438
- ```ts type-equiv
439
- /** Complete result of replaying the surface operations in a session log. */
440
- interface SurfaceFoldResult {
441
- /** Current surface event sequences in model-visible order. */
442
- nodes: SessionSeq[]
443
- /** Replacement operations in event order. */
444
- replacements: SurfaceFoldReplacement[]
445
- /** Immutable projected messages, keyed by their original event sequences. */
446
- projectedMessages: ReadonlyMap<SessionSeq, Message>
447
- }
448
- ```
449
-
450
- ## `Session` public API
451
-
452
- The body-stripped declaration keeps the plain class's detached factory, state accessors, append method, and history projections synchronized with source. Store operations remain in the generated [`ctx.sessions` section](#ctxsessions--sessionstore).
453
-
454
- ```ts public-api
455
- /**
456
- * An event-sourced session: an append-only log of {@link SessionEvent}s.
457
- *
458
- * Plain class (not a Service) — create live instances via
459
- * `ctx.sessions.create()` and detached instances via {@link create}.
460
- * Seeding with an existing event log replays/forks a session.
461
- * @typert object
462
- */
463
- declare class Session {
464
- /** The ordered surface over this session's event log. */
465
- get surface(): SessionSurface;
466
- /**
467
- * Detached, deep-frozen creation metadata (format version, cwd, lineage,
468
- * and whether fork history exists). Supplied by the store via `ctx.sessions.create()`. When a
469
- * `Session` is created without a store-owned header, a minimal header is
470
- * synthesized (stamped with the current {@link SESSION_FORMAT_VERSION}) so
471
- * `session.header` is always present. Kept out of the event log — it is a
472
- * storage concern, not replayable conversation state.
473
- */
474
- readonly header: SessionHeader;
475
- /** Number of leading events inherited from this Session's fork parent. */
476
- readonly inheritedEventCount: SessionLogOffset;
477
- /** The session identity, derived from its durable header's single copy. */
478
- get id(): SessionId;
479
- /**
480
- * The constructor seed length (0 without one), before any marker appended
481
- * during construction. Seed events never publish on `session/event`. A
482
- * marker appended before the store attaches occupies this seq without
483
- * publishing either; otherwise this seq is available for the next append.
484
- *
485
- * This in-process offset is not persisted. A fork seed can already contain
486
- * the child's inherited marker and synthetic closers, so its child-owned
487
- * history starts at {@link inheritedEventCount}, before this offset. A
488
- * resumed Session's seed contains its full stored log, while its inherited
489
- * count keeps the durable fork cut. Consumers needing complete canonical
490
- * history start at seq 0.
491
- */
492
- readonly firstLiveSeq: SessionLogOffset;
493
- /**
494
- * First event produced for this object lifecycle. A new fork includes its
495
- * child-owned seed marker and closers; a restored Session starts after its
496
- * complete stored prefix. This in-process capture offset is not persisted.
497
- */
498
- readonly firstLifecycleSeq: SessionLogOffset;
499
-
500
- /**
501
- * Create a detached session by validating and snapshotting borrowed seed
502
- * events and storage metadata.
503
- * @param id - session identity.
504
- * @param seed - optional borrowed replay or fork events.
505
- * @param header - optional borrowed storage metadata.
506
- * @param inheritedEventCount - exact fork-inherited prefix length for a seeded header.
507
- * @param projections - pure interpreters for plugin-owned message changes.
508
- * @returns a detached session.
509
- * @throws when a seed event requires a missing message interpreter or fails validation.
510
- */
511
- static create(
512
- id: SessionId,
513
- seed?: readonly SessionEvent[],
514
- header?: SessionHeader,
515
- inheritedEventCount?: SessionLogOffset,
516
- projections?: readonly SessionMessageProjection[],
517
- ): Session;
518
- /**
519
- * Restore a detached session by adopting an independently owned or deeply frozen seed.
520
- * Runtime-required event fields, event envelopes, sequence continuity, surface
521
- * transitions, and header fields are validated without copying or freezing events.
522
- * Embedded Assistant streams remain opaque until a stream consumer or storage
523
- * verifier reads them.
524
- * @param id - restored session identity.
525
- * @param seed - independently owned or deeply frozen events.
526
- * @param header - independently owned storage metadata.
527
- * @param inheritedEventCount - exact fork-inherited prefix length decoded from storage.
528
- * @param eventState - aliasing state carried from the operation that produced the seed.
529
- * @param projections - pure interpreters for plugin-owned message changes.
530
- * @returns a restored detached session.
531
- * @throws when a seed event requires a missing message interpreter or fails validation.
532
- */
533
- static fromRestore(
534
- id: SessionId,
535
- seed: readonly SessionEvent[],
536
- header: SessionHeader,
537
- inheritedEventCount: SessionLogOffset,
538
- eventState: SessionSeedEventState,
539
- projections?: readonly SessionMessageProjection[],
540
- ): Session;
541
- /**
542
- * Return the immutable event stored at one exact sequence number.
543
- * @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
544
- * See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
545
- * @param seq - event sequence number.
546
- * @returns the accepted event, or undefined when the log does not contain it.
547
- */
548
- eventAt(seq: SessionSeq): SessionEvent | undefined;
549
- /**
550
- * Materialize an immutable snapshot of a half-open event sequence range.
551
- * A full current snapshot is reused until the next append; every previously
552
- * returned snapshot remains stable after later appends.
553
- * @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
554
- * See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
555
- * @param fromSeq - non-negative inclusive sequence number; defaults to the log start.
556
- * @param toSeqExclusive - non-negative exclusive sequence number; defaults to the current end.
557
- * @returns a frozen array of the selected deeply frozen events.
558
- */
559
- snapshotEvents(
560
- fromSeq: SessionLogOffset = SessionLogOffset(0),
561
- toSeqExclusive: SessionLogOffset = this.seq,
562
- ): readonly SessionEvent[];
563
- /**
564
- * Return this Session's events after its fork-inherited prefix.
565
- * @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
566
- * See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
567
- * @returns a fresh array containing child-owned events in log order.
568
- */
569
- ownEvents(): readonly SessionEvent[];
570
- /**
571
- * Whether one existing event position is outside the fork-inherited prefix.
572
- * @param seq - event position in this Session.
573
- * @returns true when the event belongs to this Session rather than its parent.
574
- */
575
- isOwnSeq(seq: SessionSeq): boolean;
576
- /** The next event's sequence number — always the log length (the `seq = log.length` contiguity contract). */
577
- get seq(): SessionLogOffset;
578
- /**
579
- * Append one typed event to the log and synchronously notify observers via
580
- * the store-owned, module-private publication hooks. The hot path never blocks
581
- * on I/O — persistence plugins buffer asynchronously. Once the event enters
582
- * the log, the append is committed: observer failures are logged and
583
- * contained per listener, so they do not change the return value or prevent
584
- * later listeners from observing the same accepted event.
585
- *
586
- * @param type - The event type (key of {@link SessionEventMap}).
587
- * @param data - The event payload; must be JSON-serializable.
588
- * @param opts - Surface metadata: `surfaceOp` controls how the event enters
589
- * the ordered surface; `sourceEventSeqs` lists the seq numbers of earlier
590
- * events this one derives from. REQUIRED for
591
- * {@link SurfaceEventType} events (every message-producing event must
592
- * declare how it joins the surface, the sole source of derived model
593
- * history) and
594
- * rejected by the compiler for non-surface types like `turn/start` or
595
- * `assistant/attempt`. Assistant messages embed their exact provider
596
- * stream and cannot cite top-level source events.
597
- * @returns the logged event — its assigned `seq`/`time` plus the SNAPSHOT of
598
- * `data` that entered the log, so reading `event.data` back sees the logged
599
- * value, never the caller's still-mutable input.
600
- * @throws if `data` or surface metadata is not losslessly JSON-serializable
601
- * (BigInt, function, symbol, undefined, negative zero, non-finite number,
602
- * circular reference, sparse array, or an exotic object such as
603
- * Map/Set/Date/class instance), or when the candidate violates the
604
- * request-header empty-field or tool-error consistency rules, or the
605
- * canonical surface contract (marker shape and eligibility, unique
606
- * earlier source-event references, positional replacement validity, and complete
607
- * shadowed-node coverage). One iterative pass reads, validates, and
608
- * copies each nested value once, so a stateful getter cannot supply one value
609
- * to validation and another to storage. The event log is the durable source
610
- * of truth, so a bad event fails at the append site rather than later during
611
- * a backend flush. A synchronous internal dispatch validation failure or an
612
- * append reentered while this acceptance/publication boundary is open also
613
- * rejects before the log changes.
614
- */
615
- append<T extends SessionEventType>(
616
- type: T,
617
- data: SessionEventMap[T],
618
- ...opts: T extends SurfaceEventType ? [opts: SurfaceIntent<T>] : []
619
- ): SessionEvent<T>;
620
- /**
621
- * The {@link EpochHeader} in force after the log's last header event — the
622
- * header the NEXT request will be compared against — or undefined before
623
- * the first `request/header` snapshot. The live, incrementally-maintained
624
- * form of `foldRequestHeader(session.snapshotEvents())`: each header event is folded
625
- * once, when first seen, so a per-step read costs O(new events).
626
- * @returns the folded header, or undefined when no header event exists yet.
627
- */
628
- requestHeader(): EpochHeader | undefined;
629
- /**
630
- * Return the latest resolved route metadata, or `undefined` before the first
631
- * `request/context` event. Each event is folded once.
632
- * @returns the latest immutable route metadata.
633
- */
634
- requestContext(): RequestContext | undefined;
635
- /**
636
- * Fold unseen committed events into capability-independent tool history.
637
- * Initial access reconstructs inherited history; later reads consume only new events.
638
- * @returns an immutable snapshot for LLM request projection, including historical addition definitions.
639
- */
640
- toolHistory(): ToolHistory;
641
- /**
642
- * Derive the LLM message history by walking the ordered sequences of
643
- * message-producing events maintained by `surfaceOp` markers. The
644
- * surface is the single source of derived history: every message-producing
645
- * append records its `surfaceOp`, so a raw event with no marker (a chunk, a
646
- * turn boundary) is correctly absent, and a compaction `replace` deletes the
647
- * shadowed nodes from the derivation. The projection rules are
648
- * {@link deriveEventMessage}, with logged message projections applied
649
- * without changing node membership or message identity.
650
- *
651
- * CACHED: pure tail growth costs O(new nodes); a replacement or message projection
652
- * ({@link SessionSurface.contentGeneration}) rebuilds. The returned array is
653
- * a fresh snapshot per call (later appends never grow an array a caller
654
- * already holds); the `Message` objects in it are SHARED and **deep-frozen**.
655
- * Unchanged content reuses frozen event data; projected blocks are frozen
656
- * derived copies. Consumers cannot mutate the log through either form.
657
- * @returns a fresh array of the shared, frozen derived history.
658
- */
659
- deriveMessages(): Message[];
660
- /**
661
- * Project one event with all committed message projections applied.
662
- * The original durable event remains unchanged.
663
- * @param event - the event to project.
664
- * @returns the derived message, or null when the event produces none.
665
- */
666
- deriveEventMessage(event: SessionEvent): Message | null;
667
- }
668
- ```
669
-
670
- ## Derived history: `deriveMessages()` and `deriveEventMessage()`
671
-
672
- `Session.deriveMessages()` projects the event log into the `Message[]` the model sees — cached (each surface node projected once, when first seen; a surface rewrite rebuilds) and frozen (a fresh array per call over shared, deep-frozen messages, so mutating logged history through a projection is unrepresentable). `deriveEventMessage(event)` is the per-node pure function the fold applies — public so external reconstructors project a log prefix with exactly the same rules and cannot disagree with the cache. The projection rules:
673
-
674
- - `user/message` → a user message carrying exact `content`; an optional envelope remains log-only display metadata.
675
- - `assistant/message` → an assistant message with the provider and model that produced it plus optional adapter-private replay state. Its embedded compact stream is replay, usage, and UI evidence rather than a second message. An **empty-content** `assistant/message` is also skipped — a max-tokens step cut off with no content still records an `assistant/message` to hold its stream, usage, provider, and model, but a content-less assistant turn must not enter the provider transcript.
676
- - `tool/result` → a first-class tool-role message carrying its result content and tool-call identity.
677
- - `user/message` (injected context, i.e. non-`user` source) → a user-role message carrying its `content` verbatim at its chronological position; its typed source names the producer and carries any producer-specific data.
678
-
679
- Everything else (`turn/*`, `step/*`, `assistant/attempt`, plugin-owned `llm/retry`) is structural and does not project into a message. Token accounting expands the embedded stream on each `assistant/message` or `assistant/attempt`, while the message's top-level `usage` remains the committed-message authority when present. A failed model-request attempt therefore retains its provider usage without fabricating an assistant message. Current logical validation rejects request headers and assistant messages that omit provider/model instead of guessing a route; supported historical representations are normalized and validated by their adjacent format edge before a current Session exists.
680
-
681
- ## Live-session fork API
682
-
683
- `ctx.sessions.create(id, { seed, meta })` is the low-level replay/fork primitive. For ordinary live-session forks, `SessionStore` exposes one policy API:
684
-
685
- - `fork(source, boundary?, childSessionId?)` accepts a live `Session` object or live `SessionId`, selects source events through the inclusive `SessionSeq` boundary (default: current last event), requires the selected prefix to end outside an open turn, then creates a live child session with deep-cloned seed events, `parentSession`, `isSeeded: true`, the exact `inheritedEventCount`, and inherited `cwd`.
686
-
687
- An explicit `boundary` lets callers fork from any stable between-turn position, including a previous `turn/end` or a later standalone log-only event, even if the source has newer events or an open current turn. The API rejects a prefix that ends inside an open turn instead of clipping silently. `dsh-subagent-fork-in-process` keeps its completed-prefix clipping because tool-time delegation usually starts while the parent turn is open; ordinary session branching should make the requested boundary explicit.
688
-
689
- ## Why a turn ended: `TurnEndReasonMap`
690
-
691
- `turn/start` has no trigger field. The entered `user/message` batch records what entered each step, `llm/retry` records request recovery, and idle injection remains pending until a waking delivery reaches a later pre-step. Live turns retain the typed [`AgentCancelCause`](./core.md#the-agent-handle) that stopped the driver; persistence uses the additional `{ kind: 'legacy' }` cause only when importing a supported coarse cancellation record that did not store its caller.
692
-
693
- ```ts type-equiv
694
- /** Durable cancellation cause, including imports whose original coarse record carried no cause. */
695
- type TurnEndCancelCause = AgentCancelCause | { readonly kind: 'legacy' }
696
- ```
697
-
698
- ```ts type-equiv
699
- /**
700
- * Why a turn ended. Merge-extensible sum type.
701
- */
702
- interface TurnEndReasonMap {
703
- completed: { kind: 'completed' }
704
- /** A cancellation request interrupted the live turn. */
705
- aborted: { kind: 'aborted'; reason: TurnEndCancelCause }
706
-
707
- blocked: { kind: 'blocked' }
708
- /**
709
- * The turn failed. `error` is always a structured failure: the `LlmError`
710
- * facts verbatim, or `{ message: errorChain(error), code: 'UNKNOWN' }`
711
- * flattened from any other error.
712
- */
713
- error: { kind: 'error'; error: LlmFailure }
714
- /** At least one step reached its output-token ceiling, even if a plugin continued the turn. */
715
- 'max-tokens': { kind: 'max-tokens' }
716
- /**
717
- * A crash-orphaned turn was closed after the fact: agent-loop resume appends
718
- * this closer for a stored log whose last turn never ended, and session-query
719
- * synthesizes it on cold reads. The loop never emits this marker live, and
720
- * the events recorded before the crash remain intact.
721
- */
722
- interrupted: { kind: 'interrupted' }
723
- /**
724
- * Fork-seed construction closed a turn that was still open at the fork
725
- * boundary in the source session. Only fork seeds carry this marker — the
726
- * loop never emits it — and the source events before the boundary remain
727
- * intact in the child.
728
- */
729
- forked: { kind: 'forked' }
730
- }
731
- ```
732
-
733
- `max-tokens` mirrors the model-call `FinishReason` of the same name: any `max-tokens` step in a turn makes the whole turn end `max-tokens` rather than `completed` (the cut-short fact wins over a later continuation), so a consumer can tell a clean stop from a truncated one. Cancellation and errors remain distinct outcomes. The loop emits neither `interrupted` nor `forked` live: crash recovery synthesizes `interrupted` (see [persistence.md](./persistence.md)), while fork-seed construction synthesizes `forked`. The map is merge-extensible.
734
-
735
- ## Execution enclosure and standalone events
736
-
737
- A turn encloses one model-loop execution, not the whole session log. AgentLoop records injected `user/message` events only from entering pre-step batches inside a turn; plugin-owned log-only events may still appear between `turn/end` and the next `turn/start`, consuming event seqs without incrementing turn numbers. Persistence admits every contiguous accepted event into a bounded durable batch, while crash repair closes only a genuinely open trailing turn. A producer that needs an immediate durability barrier explicitly awaits `ctx.sessions.flush(session)`.
738
-
739
- Merge-extensible event relations belong to the plugin that declares them, so core does not reject an unknown event merely because no turn is open. See [the standalone-event decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md).
740
-
741
- ## The end-seed boundary: `session/end-seed`
742
-
743
- `buildForkSeed` appends `session/end-seed { inherited: true }` at the exact inherited-prefix cut before adding synthetic fork closers. The `Session` constructor retains that prepared marker or appends one when given only the inherited prefix. A restore retains the tagged marker and appends an ordinary `session/end-seed {}` only when its complete stored seed does not already end in a marker. Both forms are log-only and produce no message; only the constructor and `buildForkSeed` may create them.
744
-
745
- For fork lineage, locate the LAST marker whose payload carries `inherited: true`; current-format decoding requires it exactly when `SessionHeader.isSeeded` is true and derives `inheritedEventCount` from its seq. For lifecycle ownership, locate the last `session/end-seed` of either form. Reopening a seed that already ends in any marker does not append another ordinary marker.
746
-
747
- It exists because seed history and live work are otherwise byte-identical, which defeats any plugin owning a standalone open/close bracket: an unmatched `compaction/start` reads the same whether the writer crashed mid-compaction or is compacting right now. An opening marker before `session/end-seed` came from the constructor seed and belongs to an ended lifecycle, whatever ended it (a crash, a succeeding process, or a fork out of a still-running parent), so its owner may treat it as dead. That covers only brackets *this* session inherited: a concurrently live session holding an open bracket over the same history has its own boundary elsewhere, so tolerating concurrent writers needs a liveness signal beyond the log. Core writes the boundary and reads nothing from it — a bracket's vocabulary stays with its owning plugin, which is why crash repair closes turn/step/tool boundaries and never `compaction/*`.
748
-
749
- Consumers that order Sessions by human activity exclude this boundary: picking a Session up is not work, so ordering by the log tail would float every opened Session to the top.
750
-
751
- ## Plugin-contributed log-only events
752
-
753
- A plugin may declaration-merge extra `SessionEventMap` types. These are **log-only**: NOT `SurfaceEventType`s (they carry no `surfaceOp` and contribute nothing to derived history). Their owner decides whether they belong to an open execution turn or may stand between turns. The generated [persistence log event catalog](../persistence-catalog.md) enumerates every core and plugin-contributed event; the compaction seam's `compaction/*` semantics are discussed on [compaction.md](./compaction.md).
754
-
755
- When several events in one plugin-owned family assemble into one Web Client Conversation Node, every start, update, result, resource, or interruption event in that family carries or independently derives the same stable business id. This requirement applies to correlated Node families, not to every Session event; it lets the client group each event without guessing from adjacency or scanning history. See the [Conversation subsystem](./conversation.md).
756
-
757
- The hook bridges' `hook/invoked` / `hook/result` pairs (from `@deepseek-ai/dsh-hook-protocol`) correlate by `handlerId`. `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, and `Stop` fire inside the loop's open turn, so their `hook/*` records are turn-enclosed by construction. `SessionStart` gets no `hook/*` record because it runs before turn 1; its context remains pending in the inbox until a waking delivery opens a turn.
758
-
759
- ### Plugin records
760
-
761
- Experimental packages declare plugin-owned state in `PluginRecordMap`: `appendPluginRecord()` uses each declaration to type its name and payload, and appends an event marked `ignorable: true` in the `plugin:` namespace. `pluginRecordOf()` reads stored records with unknown payloads regardless of current declarations. The [experimental persistence catalog](../experimental-persistence-catalog.md) lists current declarations. The [package README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/README.md#write-experimental-plugin-records) owns the caller restriction, retention, and read path.
762
-
763
- ```ts type-equiv
764
- /**
765
- * Event type of an experimental plugin record: `plugin:` followed by
766
- * slash-separated segments of lowercase letters, digits, `.`, `_`, and `-`,
767
- * each starting with a letter or digit. A record type is never a
768
- * {@link SessionEventMap} member.
769
- */
770
- type PluginRecordType = `plugin:${string}`
771
- ```
772
-
773
- ```ts type-equiv
774
- /**
775
- * Payloads written by experimental packages, keyed by their `plugin:` record
776
- * names. Packages augment this map to type writes and enter the current plugin
777
- * record catalog; these declarations do not enter {@link SessionEventMap} or
778
- * released persistence schemas. Stored records still require owner validation.
779
- */
780
- interface PluginRecordMap {}
781
- ```
782
-
783
- ```ts type-equiv
784
- /**
785
- * One committed experimental plugin record, read from the log by
786
- * `pluginRecordOf`. The record's owner validates `data` before use, because a
787
- * restored record carries whatever JSON an earlier build of its owner wrote,
788
- * or a V3 event that a format migration renamed into the `plugin:` namespace held.
789
- */
790
- interface PluginRecord {
791
- /** The record type, chosen by the owning plugin. */
792
- readonly type: PluginRecordType
793
- /** The record's position in the Session log. */
794
- readonly seq: SessionSeq
795
- /** Unix epoch milliseconds at append. */
796
- readonly time: number
797
- /** The JSON payload as committed. */
798
- readonly data: unknown
799
- }
800
- ```
801
-
802
- ## Durability contract
803
-
804
- What a persistence backend relies on: the durable log persists every event losslessly, and every Assistant attempt is one `assistant/message` or `assistant/attempt` whose embedded compact stream preserves the original timed chunks. `seq` stays contiguous across these settlements and all interleaved events. A backend may choose its own storage framing for an event batch as long as a handle's `read()` returns the exact appended events; current JSONL writes one row per event (see [persistence.md](./persistence.md)). All `event.data` must be JSON-serializable; `Session.append` enforces this at the source (throwing on non-serializable data), so a bad event never enters the log and `session.snapshotEvents()` always equals what a backend can persist. Adding an event type that carries non-serializable data, corrupts core execution nesting, or violates its owner's declared relation is a breaking change to the on-disk format.
805
-
806
- The backends that consume this contract are on [persistence.md](./persistence.md).
807
-
808
- ## Remote catalog and workspace opening
809
-
810
- `ModelCatalog` is the Host-generation model directory returned by `session/modelCatalog`: it carries the deployment default, routable provider ids, successful provider groups, and isolated provider failures. It is not derived from one Session and remains separate from Session projections.
811
-
812
- `SessionOpenWorkspacePathRequest` carries an absolute or workspace-resolved `path`; optional `action: "reveal"` selects file-manager navigation instead of default-application opening. `SessionOpenWorkspacePathValue` confirms that the Host accepted the native handoff. A Session-aware Client resolves relative paths against its current Session cwd when known; the controller hands the path to the opener unchanged and reports invalid requests, cancellation, and opener failures through the Session Remote error vocabulary.
813
-
814
- The optional `application` selects a registered file handler without changing the system default; `workspacePathApplications({ path })` returns current handlers, names, icons, and default selection after Host path verification.
815
-
816
- <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
817
-
818
- <a id="cordis-surface"></a>
819
-
820
- ## Cordis API
821
-
822
- 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).
823
-
824
- <a id="ctxsessioncontroller--sessioncontroller"></a>
825
-
826
- ### `ctx.sessionController` — `SessionController`
827
-
828
- Host service backing the generated `ctx.remote.session` namespace.
829
-
830
- ```ts cordis-catalog
831
- /**
832
- * Resolve or resume one ordinary Session for another Host API domain.
833
- * @param sessionId - Session identity whose Agent owns the operation.
834
- * @returns the live Agent or the stable Session-domain failure.
835
- */
836
- resolveAgent(sessionId: SessionId): Promise<ApiSessionAgentResult>
837
-
838
- /**
839
- * Inspect one attached or persisted Session without activating its Agent.
840
- * @param sessionId - durable Session identity.
841
- * @param signal - optional caller cancellation for persistence reads.
842
- * @returns the current attached state or persisted header and event prefix.
843
- */
844
- inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise<SessionInspection>
845
-
846
- /**
847
- * Read all visible Session rows without resuming an Agent.
848
- * @param _request - reserved empty list request.
849
- * @param signal - cancellation for persistence reads and summary generation.
850
- * @returns visible Session summaries ordered by activity.
851
- */
852
- @Remote('list') async list(_request: SessionListRequest, signal: AbortSignal): Promise<SessionListValue>
853
-
854
- /**
855
- * Search visible Session content without resuming an Agent. A non-owner
856
- * receives only the hits whose Session `allows()` admits; `hasMore`
857
- * describes the bounded result before that filtering.
858
- * @param request - literal message-content query.
859
- * @param signal - cancellation for list and search reads.
860
- * @returns authorized bounded Session search results.
861
- */
862
- @Remote('search') async search(request: SessionSearchRequest, signal: AbortSignal): Promise<SessionSearchValue>
863
-
864
- /**
865
- * Create one ordinary Session for a new conversation, reusing the named blank Session while it is reusable.
866
- * @param request - optional blank Session to reuse, location, and Agent preset.
867
- * @returns the created or reused Session identity and its preset when configured.
868
- */
869
- @Remote('create') create(request: SessionCreateRequest): Promise<SessionCreateValue>
870
-
871
- /**
872
- * Select one Session-local model after explicitly resuming the Session; save the default in the background.
873
- * @param request - Session identity and requested model selection.
874
- * @returns the normalized selection installed for the Session, without waiting for default persistence.
875
- */
876
- @Remote('selectModel') selectModel(request: SessionSelectModelRequest): Promise<SessionSelectModelValue>
877
-
878
- /**
879
- * Select the first available account model after login when no provider API key is configured.
880
- * @returns after saving the first available model or retaining the existing default.
881
- */
882
- @Remote async initializeDefaultModel(): Promise<void>
883
-
884
- /**
885
- * Describe every currently routable model for Host-generation selectors.
886
- * @returns provider-grouped models, the deployment default, and isolated provider failures.
887
- */
888
- @Remote('modelCatalog') modelCatalog(): Promise<ModelCatalog>
889
-
890
- /**
891
- * Report whether this deployment can hand a Session workspace path to a native desktop.
892
- * @returns true when the matching open operation is available.
893
- */
894
- @Remote canOpenWorkspacePath(): boolean
895
-
896
- /**
897
- * Describe the serving desktop for authenticated file-action routes.
898
- * @returns Host name, configured availability, and platform-specific file-manager behavior.
899
- */
900
- workspaceDesktop(): { name: string; available: boolean; fileManager: 'finder' | 'explorer' | 'directory' | null }
901
-
902
- /**
903
- * Verify one path through the composed filesystem and open it on the Host desktop.
904
- * @param request - path after best-effort Session workspace resolution.
905
- * @param signal - caller lifetime; abort terminates the native command.
906
- * @returns confirmation after the native opener accepts the path.
907
- * @throws RemoteError when the request is invalid, has no verified Host mapping, is cancelled, or the opener fails.
908
- */
909
- @Remote('openWorkspacePath') async openWorkspacePath( request: SessionOpenWorkspacePathRequest, signal: AbortSignal, ): Promise<SessionOpenWorkspacePathValue>
910
-
911
- /**
912
- * Query current file handlers on the serving desktop without activating an Agent.
913
- * @param request - file path in Host filesystem syntax.
914
- * @param signal - caller lifetime, propagated to filesystem and desktop queries.
915
- * @returns OS application names, icons, and default selection; empty when desktop opening is unavailable.
916
- * @throws RemoteError when the path is invalid, the query is cancelled, or native discovery fails.
917
- */
918
- @Remote('workspacePathApplications') async workspacePathApplications( request: { readonly path: string }, signal: AbortSignal, ): Promise<readonly SessionWorkspacePathApplication[]>
919
-
920
- /**
921
- * Rename one Session after explicitly resuming it.
922
- * @param request - Session identity and proposed title.
923
- * @returns the accepted title and durable event sequence.
924
- */
925
- @Remote('rename') rename(request: SessionRenameRequest): Promise<SessionRenameValue>
926
-
927
- /**
928
- * Fork one cold-readable exact event prefix into a new Session. An omitted
929
- * boundary selects the latest completed-turn prefix; an open cut receives
930
- * synthetic fork closers.
931
- * @param request - source Session, optional exact inclusive event boundary, and migration preflight choice.
932
- * @returns the new Session identity.
933
- */
934
- @Remote('fork') async fork(request: SessionForkRequest): Promise<SessionForkValue>
935
-
936
- /**
937
- * Admit one prompt after explicitly resuming its Session.
938
- * @param request - Session identity, prompt content, source metadata, and delivery mode.
939
- * @param signal - caller cancellation before prompt admission begins.
940
- * @returns acknowledgement that the Agent accepted the prompt.
941
- */
942
- @Remote('prompt') async prompt(request: SessionPromptRequest, signal: AbortSignal): Promise<SessionPromptValue>
943
-
944
- /**
945
- * Read one image proven reachable from the addressed Session log.
946
- * @param request - Session and attachment identities used for authorization.
947
- * @returns the durable attachment reference and base64-encoded bytes.
948
- */
949
- @Remote('attachment') attachment(request: SessionAttachmentRequest): Promise<SessionAttachmentValue>
950
-
951
- /**
952
- * Mutate one still-pending queue occurrence, resuming a cold Agent first.
953
- * @param request - Session, queue item, and requested mutation.
954
- * @returns acknowledgement that the queue mutation was applied.
955
- */
956
- @Remote('updateQueue') updateQueue(request: SessionUpdateQueueRequest): Promise<SessionUpdateQueueValue>
957
-
958
- /**
959
- * Cancel one active Agent turn without dropping its pending inbox.
960
- * @param request - Session whose active Agent turn is cancelled.
961
- * @returns acknowledgement that cancellation was requested.
962
- */
963
- @Remote('cancel') cancel(request: SessionCancelRequest): SessionCancelValue
964
-
965
- /**
966
- * Read one cold-safe, message-aligned Session history page.
967
- * @param request - durable address, backward cursor, and page budget.
968
- * @param signal - cancellation for persistence reads.
969
- * @returns one chronological page.
970
- */
971
- @Remote({ name: 'page', resources: { session: addressedSession } }) page(request: SessionPageRequest, signal: AbortSignal): Promise<SessionPage>
972
-
973
- /**
974
- * Follow one Session log from its opening or resume cursor.
975
- * @param request - durable address and last committed sequence already held by the caller.
976
- * @param signal - cancellation owned by the Remote stream carrier.
977
- * @returns a complete opening snapshot followed by gap-free durable event
978
- * frames and optional cursorless assistant-stream frames.
979
- */
980
- @Remote({ mode: 'stream', resources: { session: addressedSession } }) follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>
981
-
982
- /**
983
- * Read exact projections, returning cached hints only when migration is required.
984
- * @param request - Session whose current values are required.
985
- * @param signal - cancellation for the Session read.
986
- * @returns a sequenced baseline, cached hints when migration is deferred, or null when absent.
987
- */
988
- @Remote('projections') async projections(request: SessionProjectionsRequest, signal: AbortSignal): Promise<SessionProjectionsValue>
989
-
990
- /**
991
- * Stream a complete live-control baseline followed by replacement frames.
992
- * @param signal - cancellation owned by the Remote stream carrier.
993
- * @returns one complete baseline followed by live replacement frames.
994
- */
995
- @Remote({ mode: 'stream' }) async *control(signal: AbortSignal): AsyncIterable<SessionControlFrame>
996
- ```
997
-
998
- Types: [SessionId](./core.md) · [SessionInspection](./persistence.md) · [SessionSearchRequest](./session-query.md)
999
-
1000
- Source: [`packages/api/session-controller/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/index.ts)
1001
-
1002
- <a id="ctxsessions--sessionstore"></a>
1003
-
1004
- ### `ctx.sessions` — `SessionStore`
1005
-
1006
- In-memory session store (`ctx.sessions`).
1007
-
1008
- Persistence is intentionally not implemented here — the agent lifecycle attaches a session-log writer to each published session's write handle; a session published outside that lifecycle persists nothing.
1009
-
1010
- ```ts cordis-catalog
1011
- /**
1012
- * Register one event interpreter for live creation, restore, and fork.
1013
- * Disposing the contribution makes sessions that used it refuse further derivation.
1014
- * @param projection - pure definition owned by the event's plugin.
1015
- * @returns the fiber-owned disposer.
1016
- * @throws when another definition already owns this event type.
1017
- */
1018
- registerMessageProjection(projection: SessionMessageProjection): () => Promise<void>
1019
-
1020
- /**
1021
- * Create a session owned by the calling fiber: disposing that fiber stops
1022
- * event notification and removes the session from the store. `options.seed`
1023
- * populates the session with a copy of those events (replay/fork);
1024
- * `options.meta` attaches creation metadata (validated absolute `cwd`, seed
1025
- * and parent lineage, and delegation depth) as the immutable
1026
- * {@link SessionHeader} (the store fills `version`/`id`/`createdAt`).
1027
- *
1028
- * For an agent whose session must be torn down IN ORDER with its loop (so the
1029
- * loop's final events are published before the store attachment ends), do NOT use this
1030
- * — fold the session lifecycle into the agent's own effect via
1031
- * {@link prepare} + {@link enter} + {@link announce} (see
1032
- * `dsh-agent-loop`'s creation transaction).
1033
- *
1034
- * @param id - the session id; omitted, the store mints `session-<n>`.
1035
- * @param options - seed events and/or creation metadata for the header.
1036
- * @returns the live session, already entered and announced.
1037
- * @throws if a session with `id` already exists, metadata is not a plain
1038
- * lossless-JSON record with valid scalar fields, or `meta.cwd` is a
1039
- * non-absolute path (storage backends key directories off it).
1040
- */
1041
- create(id?: SessionId, options?: CreateSessionOptions): Session
1042
-
1043
- /**
1044
- * Build a session WITHOUT entering it into the store — validate the id/cwd and
1045
- * construct the {@link Session} (with its immutable {@link SessionHeader}).
1046
- * Pairs with {@link enter} + {@link announce}: a caller that owns a composite
1047
- * `ctx.effect` (the agent factory) folds the session lifecycle into that ONE
1048
- * effect so a fiber unload tears the session + agent down as a single ORDERED
1049
- * chain rather than as racing sibling effects — which would remove the publication hooks
1050
- * before the driver's closing events commit, dropping them.
1051
- *
1052
- * @param id - the session id; omitted, the store mints `session-<n>`.
1053
- * @param options - seed events and/or creation metadata for the header. With
1054
- * `eventState`, every seed event is either independently owned or any
1055
- * shared value is deeply frozen; {@link Session.fromRestore} validates and
1056
- * adopts those values without copying or freezing them.
1057
- * @returns the constructed session, NOT yet in the store.
1058
- * @throws if a session with `id` already exists, metadata is not a plain
1059
- * lossless-JSON record with valid scalar fields, or `meta.cwd` is a
1060
- * non-absolute path.
1061
- */
1062
- prepare(id?: SessionId, options?: PrepareSessionOptions): Session
1063
-
1064
- /**
1065
- * Enter a {@link prepare}d session into the store: install the module-private
1066
- * append publication hooks and add it to the store. Returns the DETACH
1067
- * disposer (hooks + store removal). Does NOT emit `session/created` —
1068
- * the caller yields this disposer inside its effect and THEN calls
1069
- * {@link announce}, so a throwing `session/created` listener rolls the attach
1070
- * back instead of leaking it.
1071
- *
1072
- * Re-checks the id for a duplicate: `prepare` and `enter` are public
1073
- * cross-package primitives and a caller may interleave arbitrary work (or
1074
- * another create) between them, so a stale prepared session must NOT overwrite
1075
- * a live store entry of the same id — its detach disposer would later delete
1076
- * the REAL session. The {@link create} convenience and the agent factory call
1077
- * the two back-to-back so they never trip this, but the public API cannot
1078
- * assume that.
1079
- *
1080
- * @param session - a {@link prepare}d session not yet in the store.
1081
- * @returns the detach disposer (publication hooks + store removal). When called from
1082
- * a synchronous `session/created` listener, removal and disposal wait until
1083
- * that creation dispatch unwinds.
1084
- * @throws if a session with this id is already in the store.
1085
- */
1086
- enter(session: Session): () => void
1087
-
1088
- /** Emit `session/created` exactly once for an {@link enter}ed session (with
1089
- * the carrier {@link enter} captured). Separate from {@link enter} so the
1090
- * caller can yield the detach disposer first (rollback safety — see
1091
- * {@link enter}).
1092
- * @param session - the entered session to announce to listeners.
1093
- * @throws if the session is not live or its announcement already began,
1094
- * including a reentrant call from a creation listener. */
1095
- announce(session: Session): void
1096
-
1097
- /**
1098
- * Dispatch the awaited `session/flush` durability checkpoint for `session`,
1099
- * with the carrier captured at {@link enter}. THE flush entry point: the
1100
- * store owns the carrier, so callers (the checkpoint policy's per-request
1101
- * barrier, goal-round-driver's idle checkpoint, teardown drains, and consumers
1102
- * that flush themselves before reading storage) must come through here
1103
- * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner
1104
- * and one spelling.
1105
- * @param session - the session whose buffered events must reach durable storage.
1106
- * @returns whether at least one durability listener participated, after every
1107
- * listener has settled successfully.
1108
- * @throws the first registered listener failure after every listener settles.
1109
- */
1110
- async flush(session: Session): Promise<boolean>
1111
-
1112
- /**
1113
- * Look up a live session.
1114
- * @param id - the session id to look up.
1115
- * @returns the session, or undefined when no live session has that id.
1116
- */
1117
- get(id: SessionId): Session | undefined
1118
-
1119
- /**
1120
- * All live sessions, in creation order.
1121
- * @returns a fresh array; mutating it does not affect the store.
1122
- */
1123
- list(): Session[]
1124
-
1125
- /**
1126
- * Create a live child session from an exact prefix of a live source.
1127
- * `boundary` is an inclusive source event seq; omitted means the source's
1128
- * current last event. An open tail receives synthetic tool results and
1129
- * step/turn closers with the forked cause. Closed steps and turns remain
1130
- * unchanged, including any failed tool calls already missing results.
1131
- * `inheritedEventCount` counts only copied source events, excluding these closers.
1132
- *
1133
- * @param source - Live source session object or id.
1134
- * @param boundary - Inclusive source event seq to fork through; omitted means
1135
- * the source's current last event, and omitted on an empty source forks an
1136
- * empty child.
1137
- * @param childSessionId - Optional child session id; omitted delegates to
1138
- * `SessionStore`'s id policy.
1139
- * @returns The created live child session.
1140
- */
1141
- fork(source: SessionForkSource, boundary?: SessionSeq, childSessionId?: SessionId): Session
1142
- ```
1143
-
1144
- Types: [CreateSessionOptions](./persistence.md) · [PrepareSessionOptions](./persistence.md) · [SessionId](./core.md)
1145
-
1146
- Source: [`packages/core/session/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/index.ts)
1147
-
1148
- <a id="api-session-events"></a>
1149
-
1150
- ### `api-session/*` events
1151
-
1152
- <a id="api-sessionactivity--emit"></a>
1153
-
1154
- #### `api-session/activity` — emit
1155
-
1156
- One user-authored durable message advanced Session list activity.
1157
-
1158
- ```ts cordis-catalog
1159
- /**
1160
- * One user-authored durable message advanced Session list activity.
1161
- * @mode emit
1162
- * @param sessionId - addressed Session identity.
1163
- * @param updatedAt - durable message time used for list ordering.
1164
- */
1165
- 'api-session/activity'(sessionId: SessionId, updatedAt: number): void
1166
- ```
1167
-
1168
- Types: [SessionId](./core.md)
1169
-
1170
- Source: [`packages/api/session-controller/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/types.ts)
1171
-
1172
- <a id="api-sessionadded--emit"></a>
1173
-
1174
- #### `api-session/added` — emit
1175
-
1176
- A Session became visible or its Agent was created or disposed. Consumers upsert the summary and replace its current running and availability state.
1177
-
1178
- ```ts cordis-catalog
1179
- /**
1180
- * A Session became visible or its Agent was created or disposed.
1181
- * Consumers upsert the summary and replace its current running and availability state.
1182
- * @mode emit
1183
- * @param summary - current list row for the Session.
1184
- */
1185
- 'api-session/added'(summary: SessionSummary): void
1186
- ```
1187
-
1188
- Source: [`packages/api/session-controller/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/types.ts)
1189
-
1190
- <a id="api-sessionerror--emit"></a>
1191
-
1192
- #### `api-session/error` — emit
1193
-
1194
- One Agent failed outside a durable turn position.
1195
-
1196
- ```ts cordis-catalog
1197
- /**
1198
- * One Agent failed outside a durable turn position.
1199
- * @mode emit
1200
- * @param sessionId - Agent and Session identity.
1201
- * @param message - user-safe failure chain.
1202
- */
1203
- 'api-session/error'(sessionId: SessionId, message: string): void
1204
- ```
1205
-
1206
- Types: [SessionId](./core.md)
1207
-
1208
- Source: [`packages/api/session-controller/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/types.ts)
1209
-
1210
- <a id="api-sessionremoved--emit"></a>
1211
-
1212
- #### `api-session/removed` — emit
1213
-
1214
- A Session left the live Host registry.
1215
-
1216
- ```ts cordis-catalog
1217
- /**
1218
- * A Session left the live Host registry.
1219
- * @mode emit
1220
- * @param sessionId - removed Session identity.
1221
- */
1222
- 'api-session/removed'(sessionId: SessionId): void
1223
- ```
1224
-
1225
- Types: [SessionId](./core.md)
1226
-
1227
- Source: [`packages/api/session-controller/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/types.ts)
1228
-
1229
- <a id="api-sessionstatus--emit"></a>
1230
-
1231
- #### `api-session/status` — emit
1232
-
1233
- One Agent changed running state.
1234
-
1235
- ```ts cordis-catalog
1236
- /**
1237
- * One Agent changed running state.
1238
- * @mode emit
1239
- * @param sessionId - Agent and Session identity.
1240
- * @param running - whether the Agent is running.
1241
- */
1242
- 'api-session/status'(sessionId: SessionId, running: boolean): void
1243
- ```
1244
-
1245
- Types: [SessionId](./core.md)
1246
-
1247
- Source: [`packages/api/session-controller/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/types.ts)
1248
-
1249
- <a id="session-events"></a>
1250
-
1251
- ### `session/*` events
1252
-
1253
- <a id="sessioncreated--emit"></a>
1254
-
1255
- #### `session/created` — emit
1256
-
1257
- Creation announcement during session publication. A synchronous throw vetoes and rolls back with a paired disposal; detach requested during dispatch is deferred. A returned-promise rejection is logged but cannot retroactively veto this synchronous boundary. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only sessions entered through that agent's context.
1258
-
1259
- ```ts cordis-catalog
1260
- /**
1261
- * Creation announcement during session publication. A synchronous throw vetoes and rolls
1262
- * back with a paired disposal; detach requested during dispatch is deferred.
1263
- * A returned-promise rejection is logged but cannot retroactively veto this
1264
- * synchronous boundary.
1265
- * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners
1266
- * receive only sessions entered through that agent's context.
1267
- * @param session - the session just entered and announced.
1268
- * @mode emit
1269
- */
1270
- 'session/created'(this: Scoped<Session>, session: Session): void
1271
- ```
1272
-
1273
- Types: [Scoped](./scope.md)
1274
-
1275
- Source: [`packages/core/session/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/index.ts)
1276
-
1277
- <a id="sessiondisposed--emit"></a>
1278
-
1279
- #### `session/disposed` — emit
1280
-
1281
- Emitted once when an announced session leaves the store, including publication rollback, but never for an entry whose creation announcement did not begin. Listener failures are logged and contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope.
1282
-
1283
- ```ts cordis-catalog
1284
- /**
1285
- * Emitted once when an announced session leaves the store, including
1286
- * publication rollback, but never for an entry whose creation announcement
1287
- * did not begin. Listener failures are logged and contained.
1288
- * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope.
1289
- * @param session - the session that is no longer live in the store.
1290
- * @mode emit
1291
- */
1292
- 'session/disposed'(this: Scoped<Session>, session: Session): void
1293
- ```
1294
-
1295
- Types: [Scoped](./scope.md)
1296
-
1297
- Source: [`packages/core/session/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/index.ts)
1298
-
1299
- <a id="sessionevent--emit"></a>
1300
-
1301
- #### `session/event` — emit
1302
-
1303
- Post-commit, fire-and-forget append feed. The listener snapshot resolves before the log push, but callbacks run after it; observer failures are logged and contained without making the committed append fail. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only events from sessions entered through that agent's context.
1304
-
1305
- ```ts cordis-catalog
1306
- /**
1307
- * Post-commit, fire-and-forget append feed. The listener snapshot resolves
1308
- * before the log push, but callbacks run after it; observer failures are
1309
- * logged and contained without making the committed append fail.
1310
- * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners
1311
- * receive only events from sessions entered through that agent's context.
1312
- * @param session - the session whose log grew.
1313
- * @param event - the appended event, exactly as recorded.
1314
- * @mode emit
1315
- */
1316
- 'session/event'(this: Scoped<Session>, session: Session, event: SessionEvent): void
1317
- ```
1318
-
1319
- Types: [Scoped](./scope.md)
1320
-
1321
- Source: [`packages/core/session/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/index.ts)
1322
-
1323
- <a id="sessionflush--parallel"></a>
1324
-
1325
- #### `session/flush` — parallel
1326
-
1327
- Awaited parallel durability checkpoint: every listener runs and the caller awaits all of them, with no waterfall veto. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the session's owner scope.
1328
-
1329
- ```ts cordis-catalog
1330
- /**
1331
- * Awaited parallel durability checkpoint: every listener runs and the
1332
- * caller awaits all of them, with no waterfall veto. Scope-filtered dispatch
1333
- * (`@deepseek-ai/dsh-scope`) reuses the session's owner scope.
1334
- * @param session - the session whose buffered events must reach durable storage.
1335
- * @mode parallel
1336
- */
1337
- 'session/flush'(this: Scoped<Session>, session: Session): Promise<void> | void
1338
- ```
1339
-
1340
- Types: [Scoped](./scope.md)
1341
-
1342
- Source: [`packages/core/session/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/index.ts)
1343
- <!-- END GENERATED cordis-surface -->
1
+ ---
2
+ editSource: "docs/subsystems/session.md"
3
+ rawMarkdownPath: "en/reference/subsystems/session.md"
4
+ outline: [2,3]
5
+ ---
6
+
7
+ # Sessions
8
+
9
+ The in-memory, event-sourced model of [dsh-session](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/core/session). A `Session` is an **append-only log** of typed `SessionEvent`s — the single source of truth for an agent's whole interaction history. The LLM message history is *derived* from the log, never stored separately; replay is re-derivation from the same events. How the log is made **durable** (the persistence seam, backends, crash recovery) is the sibling concern on [persistence.md](./persistence.md).
10
+
11
+ Source: [`packages/core/session/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
12
+
13
+ ## `SessionEventMap` — the event vocabulary
14
+
15
+ The append-only event types. Merge-extensible: a plugin declares extra event types via declaration merging — e.g. the [compaction seam](./compaction.md) adds `compaction/start` / `compaction/summary` / `compaction/end`, and `@deepseek-ai/dsh-hook-protocol` adds log-only `hook/invoked` / `hook/result` records for a hook bridge. Like `compaction/*`, these are NOT `SurfaceEventType`s (no `surfaceOp`). The generated [persistence log event catalog](../persistence-catalog.md) enumerates every member — core and merged — with its payload, surface badge, and declaration site.
16
+
17
+ ```ts type-equiv
18
+ /** A user-role specialization of the shared message representation. */
19
+ interface UserMessage extends MessageBase {
20
+ readonly role: 'user'
21
+ }
22
+ ```
23
+
24
+ ```ts type-equiv
25
+ /**
26
+ * The merge-extensible, append-only source of truth for an agent interaction.
27
+ * Message history is derived from this log. Every event is lossless JSON and
28
+ * sequence numbers stay contiguous. Assistant attempt events embed their exact
29
+ * compact raw streams so persistence stores one durable settlement per attempt.
30
+ */
31
+ interface SessionEventMap {
32
+ /**
33
+ * Opens turn `turn` before the loop claims queued input or runs pre-step.
34
+ * Rejection, empty input, cancellation, or failure may close it with no
35
+ * step; otherwise the following identified `user/message` event or batch
36
+ * records the messages entering the step.
37
+ */
38
+ 'turn/start': { turn: number }
39
+ /**
40
+ * Closes turn `turn` with the {@link TurnEndReason} that ended it. A turn
41
+ * with no entered step has no `step/start` or `step/end`. The loop does not await a
42
+ * flush at turn boundaries: `dsh-session-checkpoint-policy` owns the
43
+ * per-request durability checkpoint, and consumers that read storage after
44
+ * `whenIdle()` flush themselves. Success commits the turn; rejection is
45
+ * reported live and does not prevent later work.
46
+ */
47
+ 'turn/end': { turn: number; reason: TurnEndReason }
48
+ /** Opens step `step` of turn `turn` — one model call plus the tool executions it requested. */
49
+ 'step/start': { turn: number; step: number }
50
+ /** Closes step `step` of turn `turn`. */
51
+ 'step/end': { turn: number; step: number }
52
+ /**
53
+ * A user-role message on the model-visible surface: a direct human prompt
54
+ * (the queued message claimed for this turn), a synthetic `agent.inject()`
55
+ * context (file-change notices, subdir AGENTS.md, skill content, cron
56
+ * notifications, …), or an entered goal continuation round. All three
57
+ * project their `content` verbatim; `source` tells them apart.
58
+ */
59
+ 'user/message': UserMessage
60
+ /** An incremental agent session change admitted at the named turn and step. */
61
+ 'developer/message': {
62
+ turn: number
63
+ step: number
64
+ message: DeveloperMessage
65
+ /** Earlier request/header defining every tool addition; required exactly when additions are present. */
66
+ headerSeq?: SessionSeq
67
+ }
68
+ /**
69
+ * The rendered system prompt on the model-visible surface. The loop appends
70
+ * the first one as surface node 0 before the step's first `user/message`.
71
+ * A prepared in-history route can append nonempty changes in a continuing
72
+ * series. An incapable route, or a new series whose effective text differs,
73
+ * normalizes text to the first system node; a series start whose latest
74
+ * surviving node already holds the rendering leaves it in place.
75
+ * Normalization empties nonempty later nodes, then rewrites the head if
76
+ * needed, through logged per-node replacements. An empty rendering always
77
+ * clears all active system nodes, leaving no older instructions model-visible.
78
+ * Empty later nodes are dormant and project to no message; an empty head with
79
+ * no active later node records "no system prompt". Restored nonempty text follows
80
+ * the same route and series rule; empty nodes never restore older text.
81
+ */
82
+ 'system/message': { turn: number; step: number; message: SystemMessage }
83
+ /**
84
+ * Assembled assistant message for one step (derived history uses this).
85
+ * Carries the step's `usage` when the adapter reported token accounting, so
86
+ * the model output and its accounting travel together (there is no separate
87
+ * usage record). `usage` is absent when the adapter reported none. A turn
88
+ * cancelled mid-stream finalizes its delivered text/reasoning prefix as this
89
+ * event with `interrupted: true`; undispatched tool calls are absent. The
90
+ * marker distinguishes that prefix without re-deriving interruption from turn
91
+ * boundaries. An aborted turn with no such event streamed no visible content.
92
+ */
93
+ 'assistant/message': {
94
+ turn: number
95
+ step: number
96
+ message: AssistantMessage
97
+ /** Exact timed model stream, compacted without joining delta boundaries. */
98
+ stream: AssistantStreamRecord[]
99
+ usage?: TokenUsage
100
+ interrupted?: true
101
+ }
102
+ /**
103
+ * One model attempt that committed no surface message. The embedded stream
104
+ * preserves a failed, retried, cancelled, or stream-error attempt that
105
+ * reached settlement without fabricating model-visible history.
106
+ */
107
+ 'assistant/attempt': { turn: number; step: number; stream: AssistantStreamRecord[] }
108
+ /**
109
+ * The model requested one tool invocation: `name` with the raw `arguments`
110
+ * JSON string exactly as the model produced it (unparsed). `callId` pairs the
111
+ * call with its `tool/result`.
112
+ */
113
+ 'tool/call': { turn: number; step: number; callId: ToolCallId; name: string; arguments: string }
114
+ /**
115
+ * A completed tool call's model-facing result, optional internal failure
116
+ * identity and user-facing reason, and optional tool-private `meta`
117
+ * presentation payload. The reason remains outside the model-facing message.
118
+ * `meta` is
119
+ * opaque to the core (the producing tool owns its shape and reads it back in
120
+ * `presentResult`) but MUST be JSON-serializable: `Session.append`
121
+ * runtime-validates all event data with `isJsonValue`, so a non-serializable
122
+ * `meta` is rejected at the source, and the durable log reproduces the
123
+ * identical card on replay. Absent
124
+ * unless the tool attaches one (e.g. `dsh-tool-fs` carries its result-time
125
+ * contextual diff here).
126
+ */
127
+ 'tool/result': {
128
+ turn: number
129
+ step: number
130
+ message: ToolResultMessage
131
+ /**
132
+ * Optional failure identity and raw user-facing reason, outside model content;
133
+ * allowed only when the message has `isError: true`.
134
+ */
135
+ error?: { name: string; code: string; reason?: string }
136
+ meta?: JsonValue
137
+ }
138
+ /**
139
+ * Full header for the next request, appended inside its step before dispatch.
140
+ * It is log-only; the latest snapshot reconstructs the request header.
141
+ */
142
+ 'request/header': {
143
+ header: EpochHeader
144
+ reason: RequestHeaderReason
145
+ /** This request begins a distinct model-message series, independently of the header reason. */
146
+ startsSeries?: true
147
+ }
148
+ /**
149
+ * Route metadata for the next request, logged only when the route, capacity,
150
+ * or system prompt update mode changes. It does not participate in request
151
+ * reconstruction or header equality. Prompt admission uses the bound prepared
152
+ * call's capability, not this snapshot from an earlier request.
153
+ */
154
+ 'request/context': RequestContext
155
+ /**
156
+ * Separates inherited or restored history from later lifecycle-owned work.
157
+ * This log-only marker need not be at {@link Session.firstLiveSeq}: a fork
158
+ * seed can already contain its tagged marker and child-owned synthetic
159
+ * closers before construction.
160
+ *
161
+ * A fresh fork child owns one `{ inherited: true }` marker at its exact
162
+ * inherited-prefix cut, even when that prefix ends in an ancestor marker.
163
+ * `buildForkSeed` appends that marker before any synthetic closers; the
164
+ * `Session` constructor supplies it when given only the inherited prefix.
165
+ * The last tagged marker is the current Session's cut; untagged markers
166
+ * keep ordinary restore and replay lifecycle boundaries.
167
+ *
168
+ * Only the `Session` constructor and `buildForkSeed` may create this marker.
169
+ * Session append does not reject other writers, so a plugin appending one
170
+ * would silently classify every live bracket before it as seed history.
171
+ *
172
+ * An owner of a standalone open/close bracket (`compaction/start` …
173
+ * `compaction/end`) reads it because seed history and live work are otherwise
174
+ * byte-identical: an unmatched opening marker before this event belongs to
175
+ * an ended lifecycle, whatever ended it. NOT a liveness signal about other
176
+ * writers — a concurrently live session holds its own boundary elsewhere,
177
+ * so tolerating concurrent writers needs a signal beyond the log.
178
+ */
179
+ 'session/end-seed': { inherited?: true }
180
+ }
181
+ ```
182
+
183
+ `UserMessage` is the identified, frozen user-role value shared by ordinary prompts, injected context, steering, and live inbox events. Event wrappers add only event-local position or outcome facts; the loop adds only driver-owned routing state while an item remains pending.
184
+
185
+ <a id="the-request-header-event-requestheader"></a>
186
+
187
+ ### The request header event: `request/header`
188
+
189
+ The request envelope — the `EpochHeader` (call config + markers for adapter-supplied defaults + assembled tool schemas) — is logged session state, so every conversation request is a pure function of the log (the reconstructability Agent Note). The rendered system prompt is not part of the header: it is derived history, the `system/message` event at surface node 0 and any later in-history system node ([decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md)), so a prompt change replaces or appends a system node and leaves the header unchanged. A full `request/header` snapshot with reason `'initial'` or `'resume'` records each loop-instance boundary; a changed request appends a snapshot with reason `'change'`; and an unchanged envelope beginning an explicitly declared message series or following a surface replacement appends a snapshot with reason `'series'`. An `'initial'`, `'resume'`, or `'change'` snapshot carries `startsSeries: true` when that request also begins a series; reason `'series'` already records that fact. Ordinary append-only later Turns, further Steps, and retries in the same model-message series inherit the latest snapshot. `foldRequestHeader(events)` reconstructs the header by selecting the latest snapshot. The event is not a `SurfaceEventType`: it produces no LLM message.
190
+
191
+ ```ts type-equiv
192
+ /**
193
+ * Logged request state outside derived history: call config and tools. The
194
+ * system prompt is derived history — surface node 0, a `system/message` event.
195
+ * The latest full `request/header` snapshot reconstructs the header; canonical
196
+ * empty optional fields are absent.
197
+ */
198
+ interface EpochHeader {
199
+ /** The conversation's call configuration (provider, model, reasoning effort, and sampling scalars). */
200
+ config: LlmCallConfig
201
+ /** Effective config fields materialized from the exact adapter rather than proposed by a caller. */
202
+ adapterDefaults?: LlmCallConfigAdapterDefaults
203
+ /** Assembled tool schemas; absent for a tool-less request. */
204
+ tools?: ToolSchema[]
205
+ /** Retired request text; system prompts belong to system/message events.
206
+ * @persistenceReserved
207
+ */
208
+ system?: never
209
+ }
210
+ ```
211
+
212
+ Current event acceptance requires canonical `request/header.header`: any `system` field is forbidden, and `tools: []` and `adapterDefaults: {}` must be omitted. Whitespace-only system-message content, `config.stop: []`, and nested extensions remain unchanged. Seed, append, and current persistence reads reject noncanonical headers rather than silently normalizing them; [the V3 envelope decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-09-06-v3-canonical-session-envelopes.md) owns historical conversion. Legacy v0 logs containing `request/header-delta` or its full-snapshot `fallback` reason are rejected rather than replayed incompletely.
213
+
214
+ ### The route capacity event: `request/context`
215
+
216
+ The context metadata of the route a request resolved to is separate logged state, appended beside `request/header` inside the same step and only when the provider, model, capacity, or `systemPromptUpdate` mode differs from the previous record. It stays outside `EpochHeader` because that type is the reconstruction contract compared field-wise by `headerEquals`: capacity and the update mode describe a route, not a request input, so folding them in would let a route change register as a request-envelope `change` and would pull adapter metadata into the loop's request reconstruction. Like `request/header`, it is not a `SurfaceEventType` and produces no LLM message. `session.requestContext()` folds the latest record incrementally; the agent loop reads that record's `systemPromptUpdate` when it decides whether a changed system prompt replaces the latest system node or is appended after the cached history ([decision rule](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/agent-loop/README.md#understand-the-implementation)). A route whose adapter advertises no capacity is recorded with `contextWindow` absent, so the new record clears an older route's capacity; a route without a declared update mode likewise clears an older route's `systemPromptUpdate`.
217
+
218
+ ```ts type-equiv
219
+ /** Registration-bound metadata for one resolved model route. */
220
+ interface RequestContext {
221
+ /** Registered provider route the metadata belongs to. */
222
+ provider: string
223
+ /** Provider-owned model id the metadata belongs to. */
224
+ model: string
225
+ /** Maximum combined request and response context in tokens, when advertised. */
226
+ contextWindow?: number
227
+ /** `'in-history'` when the route reads the latest `system` message at any position as the effective system prompt. */
228
+ systemPromptUpdate?: SystemPromptUpdate
229
+ }
230
+ ```
231
+
232
+ ## `SessionEvent<T>` — one log entry
233
+
234
+ A proper discriminated union over `type` (not independent `type`/`data` unions), so `switch (event.type)` narrows `event.data` without casts. `seq` is the monotonic position in the log (`seq = log.length`); `time` is epoch ms.
235
+
236
+ ```ts type-equiv
237
+ /** Sequence number of one existing event in a Session log. */
238
+ type SessionSeq = BrandedNumber<'SessionSeq'>
239
+ ```
240
+
241
+ ```ts type-equiv
242
+ /** A Session log gap, prefix length, or read offset, which may equal the event count. */
243
+ type SessionLogOffset = BrandedNumber<'SessionLogOffset'>
244
+ ```
245
+
246
+ ```ts type-equiv
247
+ /** Inclusive Session event watermark, or `-1` before any event exists. */
248
+ type SessionSeqCursor = SessionSeq | -1
249
+ ```
250
+
251
+ ```ts type-equiv
252
+ /** One existing Session event position, or explicit absence. */
253
+ type OptionalSessionSeq = SessionSeq | null
254
+ ```
255
+
256
+ `SessionSeq(value)` and `SessionLogOffset(value)` admit only non-negative safe integers and reject negative zero. They add compile-time brands without changing the serialized number; arithmetic returns an ordinary `number` that callers must admit again through the constructor for its intended role.
257
+
258
+ ```ts type-equiv
259
+ /**
260
+ * One immutable entry in the session log.
261
+ *
262
+ * A proper discriminated union over `type` (not independent `type`/`data`
263
+ * unions), so `switch (event.type)` narrows `event.data` without casts.
264
+ *
265
+ * The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional:
266
+ * they only exist on {@link SurfaceEventType} variants (`system/message`, `user/message`,
267
+ * `assistant/message`, `tool/result`).
268
+ * Non-surface events (boundary markers, attempts, errors) never carry
269
+ * surface metadata — the compiler enforces this at `Session.append()`
270
+ * call sites.
271
+ */
272
+ type SessionEvent<T extends SessionEventType = SessionEventType> = {
273
+ [K in SessionEventType]: {
274
+ type: K
275
+ /** Monotonic sequence number within the session. */
276
+ seq: SessionSeq
277
+ /** Unix epoch milliseconds. */
278
+ time: number
279
+ data: SessionEventMap[K]
280
+ /**
281
+ * Marks an event a reader may safely skip when it does not recognize
282
+ * `type`. Absent means required: a reader meeting an unrecognized type
283
+ * without this marker MUST refuse to reconstruct the session instead of
284
+ * silently dropping the event, because an unrecognized required event may
285
+ * change how the rest of the log is interpreted. A writer sets `true` only
286
+ * on purely informational records whose loss cannot affect reconstruction;
287
+ * defaulting to required means a forgotten marker over-refuses (an
288
+ * inconvenience) rather than silently resuming a gutted session.
289
+ */
290
+ ignorable?: true
291
+ } & (K extends SurfaceEventType ? SurfaceIntent<K> : {
292
+ surfaceOp?: never
293
+ sourceEventSeqs?: never
294
+ })
295
+ }[T]
296
+ ```
297
+
298
+ `SessionEventType = keyof SessionEventMap`. Because `SessionEventMap` is merge-extensible, switches over `SessionEvent` must NOT use `assertNever` — a plugin-added variant is a valid unknown value; handle the known cases and fall through `default`.
299
+
300
+ Every surface event requires `surfaceOp`; known log-only events forbid both surface metadata fields. Native unknown or obsolete ignorable envelopes remain opaque. `assistant/message` embeds its provider stream and forbids `sourceEventSeqs`. System, user, and tool surface events may cite a complete non-empty set of unique earlier events when source attribution or replacement coverage requires it. A `tool/result` may carry `data.error` only when its message has `isError: true`; failure identity remains optional for failed results.
301
+
302
+ ## Surface types
303
+
304
+ The message-producing types (`SurfaceEventType` — `system/message`, `developer/message`, `user/message`, `assistant/message`, `tool/result`) carry surface metadata declaring how they join the ordered derived surface. `system/message` holds the rendered system prompt: the loop appends the first one as surface node 0 and, when the prompt changes, replaces exactly the latest system node or appends a new one on an in-history route; the surface fold rejects any other replacement covering a `system/message` at node 0, while a later system node is ordinary history that a compaction replacement may shadow. See the [session surface Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-06-18-session-surface.md).
305
+
306
+ ### `SurfaceEventType` — the message-producing subset of event types
307
+
308
+ ```ts type-equiv
309
+ /**
310
+ * The subset of {@link SessionEventType} values whose events produce LLM
311
+ * messages and are eligible to appear on the ordered surface. Only these
312
+ * event types may carry {@link SurfaceOp}; system, user, and tool events may also cite
313
+ * earlier sources through {@link SessionEvent.sourceEventSeqs}.
314
+ */
315
+ type SurfaceEventType =
316
+ | 'system/message'
317
+ | 'developer/message'
318
+ | 'user/message'
319
+ | 'assistant/message'
320
+ | 'tool/result'
321
+ ```
322
+
323
+ ### `SurfaceOp` — how an event entered the surface
324
+
325
+ ```ts type-equiv
326
+ /**
327
+ * How a session event entered the ordered surface. Only valid on
328
+ * {@link SurfaceEventType} events.
329
+ *
330
+ * - `'append'`: added to the tail — normal path for user/assistant/tool
331
+ * messages.
332
+ * - `{ op: 'replace', startSeq, endSeq }`: replaces surface nodes from `startSeq`
333
+ * (inclusive) through `endSeq` (inclusive) with this node. Both must exist as
334
+ * surface nodes in the current surface. `startSeq === endSeq` replaces a single
335
+ * node. The node's {@link SessionEvent.sourceEventSeqs} must include every
336
+ * shadowed surface node. Used by compaction; any surface-replacing producer
337
+ * may use it.
338
+ */
339
+ type SurfaceOp =
340
+ | 'append'
341
+ | { op: 'replace'; startSeq: SessionSeq; endSeq: SessionSeq }
342
+ ```
343
+
344
+ `'append'` is the normal tail-append path. `replace` contains exactly `op`, `startSeq`, and `endSeq`, with no aliases or extra keys. It shadows the inclusive span between those current surface event sequences and inserts the new event in their place; equal endpoints replace one entry. Endpoints must precede the replacing event, but their relative order is surface order, not numeric sequence order.
345
+
346
+ ### `SurfaceIntent` — the parameter to `session.append()`
347
+
348
+ ```ts type-equiv
349
+ /**
350
+ * Surface placement and cited source-event seqs for {@link Session.append}. Required on
351
+ * message-producing events and forbidden on log-only events.
352
+ */
353
+ type SurfaceIntent<T extends SurfaceEventType = SurfaceEventType> = {
354
+ surfaceOp: SurfaceOp
355
+ } & (T extends 'assistant/message' ? {
356
+ /** Assistant messages embed their provider stream instead of citing source events. */
357
+ sourceEventSeqs?: never
358
+ } : {
359
+ /** Complete non-empty set of known earlier source-event seqs. */
360
+ sourceEventSeqs?: SessionSeq[]
361
+ })
362
+ ```
363
+
364
+ Required for `SurfaceEventType` events — every message-producing event must declare how it joins the surface, the sole source of derived model history. A human-facing transcript is the other projection and reads the log's append-origin events instead, because the surface deliberately shadows the ranges a replacement summarizes (`isAppendSurfaceEvent` in [dsh-session](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/README.md)). Non-surface types reject it at compile time.
365
+
366
+ `assistant/message` cannot carry `sourceEventSeqs`; its `stream` owns exact provider evidence. Other surface events omit the field when they cite no earlier event and use a complete non-empty list when they do.
367
+
368
+ <a id="plugin-owned-message-projections"></a>
369
+ ### Plugin-owned message projections
370
+
371
+ Content-changing plugins mark their event declaration with `@messageProjection` and register a pure definition through `ctx.sessions.registerMessageProjection()`. Session validates the complete decision through that definition before commit, applies its immutable message updates, and advances `contentGeneration`. It rejects missing interpreters, including on restore and detached folding; unloading a used definition blocks cached reads. Detached readers pass explicit definitions to `foldSurface(events, projections)` and apply `projectedMessages` through `deriveEventMessage()`. The installed format catalog assembles first-party definitions for offline readers. The [image-offload plugin](./compaction.md#image-offload) owns its image-specific event and interpretation.
372
+
373
+ ```ts type-equiv
374
+ /** Readonly history immediately before a message-projection event. */
375
+ interface SessionMessageProjectionContext {
376
+ /** Current message-producing event sequences in model-visible order. */
377
+ nodes: readonly SessionSeq[]
378
+ /** Contiguous event window; entries at or beyond the candidate seq are not committed inputs. */
379
+ events: readonly SessionEvent[]
380
+ /** Absolute sequence of the window's first event. */
381
+ baseSeq: SessionLogOffset
382
+ /** Previously projected messages keyed by their original event sequences. */
383
+ messages: ReadonlyMap<SessionSeq, Message>
384
+ }
385
+ ```
386
+
387
+ ```ts type-equiv
388
+ /** Pure interpretation of one plugin-owned event that changes existing message content. */
389
+ interface SessionMessageProjection<T extends SessionEventType = SessionEventType> {
390
+ /** Event interpreted by this definition; declare it with `@messageProjection` in SessionEventMap. */
391
+ type: T
392
+ /**
393
+ * Validate the complete durable decision before returning any updates. Preserve
394
+ * message identities and publish immutable copies without mutating the input.
395
+ * @param event - candidate event, not yet applied to the supplied history.
396
+ * @param context - history preceding this decision.
397
+ * @returns changed current messages keyed by their original sequences.
398
+ * @throws when the durable decision cannot be applied to this history.
399
+ */
400
+ project(event: SessionEvent<T>, context: SessionMessageProjectionContext): ReadonlyMap<SessionSeq, Message>
401
+ }
402
+ ```
403
+
404
+ ### `SessionSurface` — the live readonly surface projection
405
+
406
+ `Session.surface` returns the session's stable `SessionSurface` view. The same incremental manager validates append candidates before commit and advances this projection from committed events; callers can observe membership and replacement generation but cannot invoke validation.
407
+
408
+ `SurfaceManager(log, baseSeq?, projections?)` can instead fold a contiguous loaded window whose first event has the absolute sequence `baseSeq`. Every event remains contiguous in that absolute sequence space, and a replacement that crosses the window head fails because its declared range is absent.
409
+
410
+ ```ts type-equiv
411
+ /** Readonly live projection of the message-producing session events. */
412
+ interface SessionSurface {
413
+ /** Current surface event sequences in model-visible order. */
414
+ readonly nodes: readonly SessionSeq[]
415
+ /** Monotonic count of committed positional replacements. */
416
+ readonly replaceGeneration: number
417
+ /** Monotonic count of committed replacements and plugin-owned message changes. */
418
+ readonly contentGeneration: number
419
+ }
420
+ ```
421
+
422
+ ### `SurfaceFoldReplacement` and `SurfaceFoldResult` — a complete surface replay
423
+
424
+ `foldSurface(events, projections)` returns detached current event sequences together with the actual sequences shadowed by each declared replacement range. The live manager uses the same transitions without retaining replacement history. Its `replaceGeneration` increments for each committed replacement so incremental consumers can distinguish pure tail growth from a rewrite.
425
+
426
+ ```ts type-equiv
427
+ /** One replacement operation observed while folding a session surface. */
428
+ interface SurfaceFoldReplacement {
429
+ /** Seq of the event that replaced the prior surface range. */
430
+ seq: SessionSeq
431
+ /** Declared inclusive start seq of the replaced surface range. */
432
+ start: SessionSeq
433
+ /** Declared inclusive end seq of the replaced surface range. */
434
+ end: SessionSeq
435
+ /** Actual surface entries removed by the operation, in surface order. */
436
+ shadowedSeqs: SessionSeq[]
437
+ }
438
+ ```
439
+
440
+ ```ts type-equiv
441
+ /** Complete result of replaying the surface operations in a session log. */
442
+ interface SurfaceFoldResult {
443
+ /** Current surface event sequences in model-visible order. */
444
+ nodes: SessionSeq[]
445
+ /** Replacement operations in event order. */
446
+ replacements: SurfaceFoldReplacement[]
447
+ /** Immutable projected messages, keyed by their original event sequences. */
448
+ projectedMessages: ReadonlyMap<SessionSeq, Message>
449
+ }
450
+ ```
451
+
452
+ ## `Session` public API
453
+
454
+ ### `SessionEventInput` — one proposed event
455
+
456
+ ```ts type-equiv
457
+ /** One positional event call proposed for append, without assigned sequence or time. */
458
+ type SessionEventInput = {
459
+ [K in SessionEventType]: readonly [type: K, data: SessionEventMap[K], ...opts: K extends SurfaceEventType ? [opts: SurfaceIntent<K>] : []]
460
+ }[SessionEventType]
461
+ ```
462
+
463
+ Each array member keeps its event type paired with its payload and requires surface intent only for message-producing events.
464
+
465
+ The body-stripped declaration keeps the plain class's detached factory, state accessors, append method, and history projections synchronized with source. Store operations remain in the generated [`ctx.sessions` section](#ctxsessions--sessionstore).
466
+
467
+ ```ts public-api
468
+ /**
469
+ * An event-sourced session: an append-only log of {@link SessionEvent}s.
470
+ *
471
+ * Plain class (not a Service) — create live instances via
472
+ * `ctx.sessions.create()` and detached instances via {@link create}.
473
+ * Seeding with an existing event log replays/forks a session.
474
+ * @typert object
475
+ */
476
+ declare class Session {
477
+ /** The ordered surface over this session's event log. */
478
+ get surface(): SessionSurface;
479
+ /**
480
+ * Detached, deep-frozen creation metadata (format version, cwd, lineage,
481
+ * and whether fork history exists). Supplied by the store via `ctx.sessions.create()`. When a
482
+ * `Session` is created without a store-owned header, a minimal header is
483
+ * synthesized (stamped with the current {@link SESSION_FORMAT_VERSION}) so
484
+ * `session.header` is always present. Kept out of the event log — it is a
485
+ * storage concern, not replayable conversation state.
486
+ */
487
+ readonly header: SessionHeader;
488
+ /** Number of leading events inherited from this Session's fork parent. */
489
+ readonly inheritedEventCount: SessionLogOffset;
490
+ /** The session identity, derived from its durable header's single copy. */
491
+ get id(): SessionId;
492
+ /**
493
+ * The constructor seed length (0 without one), before any marker appended
494
+ * during construction. Seed events never publish on `session/event`. A
495
+ * marker appended before the store attaches occupies this seq without
496
+ * publishing either; otherwise this seq is available for the next append.
497
+ *
498
+ * This in-process offset is not persisted. A fork seed can already contain
499
+ * the child's inherited marker and synthetic closers, so its child-owned
500
+ * history starts at {@link inheritedEventCount}, before this offset. A
501
+ * resumed Session's seed contains its full stored log, while its inherited
502
+ * count keeps the durable fork cut. Consumers needing complete canonical
503
+ * history start at seq 0.
504
+ */
505
+ readonly firstLiveSeq: SessionLogOffset;
506
+ /**
507
+ * First event produced for this object lifecycle. A new fork includes its
508
+ * child-owned seed marker and closers; a restored Session starts after its
509
+ * complete stored prefix. This in-process capture offset is not persisted.
510
+ */
511
+ readonly firstLifecycleSeq: SessionLogOffset;
512
+
513
+ /**
514
+ * Create a detached session by validating and snapshotting borrowed seed
515
+ * events and storage metadata.
516
+ * @param id - session identity.
517
+ * @param seed - optional borrowed replay or fork events.
518
+ * @param header - optional borrowed storage metadata.
519
+ * @param inheritedEventCount - exact fork-inherited prefix length for a seeded header.
520
+ * @param projections - pure interpreters for plugin-owned message changes.
521
+ * @returns a detached session.
522
+ * @throws when a seed event requires a missing message interpreter or fails validation.
523
+ */
524
+ static create(
525
+ id: SessionId,
526
+ seed?: readonly SessionEvent[],
527
+ header?: SessionHeader,
528
+ inheritedEventCount?: SessionLogOffset,
529
+ projections?: readonly SessionMessageProjection[],
530
+ ): Session;
531
+ /**
532
+ * Restore a detached session by adopting an independently owned or deeply frozen seed.
533
+ * Runtime-required event fields, event envelopes, sequence continuity, surface
534
+ * transitions, and header fields are validated without copying or freezing events.
535
+ * Embedded Assistant streams remain opaque until a stream consumer or storage
536
+ * verifier reads them.
537
+ * @param id - restored session identity.
538
+ * @param seed - independently owned or deeply frozen events.
539
+ * @param header - independently owned storage metadata.
540
+ * @param inheritedEventCount - exact fork-inherited prefix length decoded from storage.
541
+ * @param eventState - aliasing state carried from the operation that produced the seed.
542
+ * @param projections - pure interpreters for plugin-owned message changes.
543
+ * @returns a restored detached session.
544
+ * @throws when a seed event requires a missing message interpreter or fails validation.
545
+ */
546
+ static fromRestore(
547
+ id: SessionId,
548
+ seed: readonly SessionEvent[],
549
+ header: SessionHeader,
550
+ inheritedEventCount: SessionLogOffset,
551
+ eventState: SessionSeedEventState,
552
+ projections?: readonly SessionMessageProjection[],
553
+ ): Session;
554
+ /**
555
+ * Return the immutable event stored at one exact sequence number.
556
+ * @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
557
+ * See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
558
+ * @param seq - event sequence number.
559
+ * @returns the accepted event, or undefined when the log does not contain it.
560
+ */
561
+ eventAt(seq: SessionSeq): SessionEvent | undefined;
562
+ /**
563
+ * Materialize an immutable snapshot of a half-open event sequence range.
564
+ * A full current snapshot is reused until the next append; every previously
565
+ * returned snapshot remains stable after later appends.
566
+ * @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
567
+ * See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
568
+ * @param fromSeq - non-negative inclusive sequence number; defaults to the log start.
569
+ * @param toSeqExclusive - non-negative exclusive sequence number; defaults to the current end.
570
+ * @returns a frozen array of the selected deeply frozen events.
571
+ */
572
+ snapshotEvents(
573
+ fromSeq: SessionLogOffset = SessionLogOffset(0),
574
+ toSeqExclusive: SessionLogOffset = this.seq,
575
+ ): readonly SessionEvent[];
576
+ /**
577
+ * Return this Session's events after its fork-inherited prefix.
578
+ * @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
579
+ * See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
580
+ * @returns a fresh array containing child-owned events in log order.
581
+ */
582
+ ownEvents(): readonly SessionEvent[];
583
+ /**
584
+ * Whether one existing event position is outside the fork-inherited prefix.
585
+ * @param seq - event position in this Session.
586
+ * @returns true when the event belongs to this Session rather than its parent.
587
+ */
588
+ isOwnSeq(seq: SessionSeq): boolean;
589
+ /** The next event's sequence number — always the log length (the `seq = log.length` contiguity contract). */
590
+ get seq(): SessionLogOffset;
591
+ /** Current event while synchronously notifying a batch's observers. */
592
+ get currentBatchPublication(): SessionEvent | undefined;
593
+ /**
594
+ * Append consecutive events together after validating every candidate and dispatch.
595
+ * Observers read the complete committed log and final message surface.
596
+ * Listener registration changes during publication take effect on the next append.
597
+ * Persistence listeners retain their own durability rules.
598
+ * @param inputs - event payloads and surface intents in commit order. An empty array records no events.
599
+ * @returns immutable committed events in the same order.
600
+ * @throws if the Session cannot accept writes, including an empty append, or any candidate or synchronous dispatch validation fails.
601
+ */
602
+ append(inputs: readonly SessionEventInput[]): readonly SessionEvent[];
603
+ /**
604
+ * Append one typed event to the log and synchronously notify observers via
605
+ * the store-owned, module-private publication hooks. The hot path never blocks
606
+ * on I/O — persistence plugins buffer asynchronously. Once the event enters
607
+ * the log, the append is committed: observer failures are logged and
608
+ * contained per listener, so they do not change the return value or prevent
609
+ * later listeners from observing the same accepted event.
610
+ * Independent Sessions append without notifying store observers. Once a store
611
+ * prepares or enters the instance, writes require a completed creation
612
+ * announcement and remain disabled after removal.
613
+ *
614
+ * @param type - The event type (key of {@link SessionEventMap}).
615
+ * @param data - The event payload; must be JSON-serializable.
616
+ * @param opts - Surface metadata: `surfaceOp` controls how the event enters
617
+ * the ordered surface; `sourceEventSeqs` lists the seq numbers of earlier
618
+ * events this one derives from. REQUIRED for
619
+ * {@link SurfaceEventType} events (every message-producing event must
620
+ * declare how it joins the surface, the sole source of derived model
621
+ * history) and
622
+ * rejected by the compiler for non-surface types like `turn/start` or
623
+ * `assistant/attempt`. Assistant messages embed their exact provider
624
+ * stream and cannot cite top-level source events.
625
+ * @returns the logged event — its assigned `seq`/`time` plus the SNAPSHOT of
626
+ * `data` that entered the log, so reading `event.data` back sees the logged
627
+ * value, never the caller's still-mutable input.
628
+ * @throws if a store-owned Session is unentered or its creation announcement
629
+ * has not completed, or if `data` or surface
630
+ * metadata is not losslessly JSON-serializable
631
+ * (BigInt, function, symbol, undefined, negative zero, non-finite number,
632
+ * circular reference, sparse array, or an exotic object such as
633
+ * Map/Set/Date/class instance), or when the candidate violates the
634
+ * request-header empty-field or tool-error consistency rules, or the
635
+ * canonical surface contract (marker shape and eligibility, unique
636
+ * earlier source-event references, positional replacement validity, and complete
637
+ * shadowed-node coverage). One iterative pass reads, validates, and
638
+ * copies each nested value once, so a stateful getter cannot supply one value
639
+ * to validation and another to storage. The event log is the durable source
640
+ * of truth, so a bad event fails at the append site rather than later during
641
+ * a backend flush. A synchronous internal dispatch validation failure or an
642
+ * append reentered while this acceptance/publication boundary is open also
643
+ * rejects before the log changes.
644
+ */
645
+ append<T extends SessionEventType>(
646
+ type: T,
647
+ data: SessionEventMap[T],
648
+ ...opts: T extends SurfaceEventType ? [opts: SurfaceIntent<T>] : []
649
+ ): SessionEvent<T>;
650
+ /**
651
+ * The {@link EpochHeader} in force after the log's last header event — the
652
+ * header the NEXT request will be compared against — or undefined before
653
+ * the first `request/header` snapshot. The live, incrementally-maintained
654
+ * form of `foldRequestHeader(session.snapshotEvents())`: each header event is folded
655
+ * once, when first seen, so a per-step read costs O(new events).
656
+ * @returns the folded header, or undefined when no header event exists yet.
657
+ */
658
+ requestHeader(): EpochHeader | undefined;
659
+ /**
660
+ * Return the latest resolved route metadata, or `undefined` before the first
661
+ * `request/context` event. Each event is folded once.
662
+ * @returns the latest immutable route metadata.
663
+ */
664
+ requestContext(): RequestContext | undefined;
665
+ /**
666
+ * Fold unseen committed events into capability-independent tool history.
667
+ * Initial access reconstructs inherited history; later reads consume only new events.
668
+ * @returns an immutable snapshot for LLM request projection, including historical addition definitions.
669
+ */
670
+ toolHistory(): ToolHistory;
671
+ /**
672
+ * Derive the LLM message history by walking the ordered sequences of
673
+ * message-producing events maintained by `surfaceOp` markers. The
674
+ * surface is the single source of derived history: every message-producing
675
+ * append records its `surfaceOp`, so a raw event with no marker (a chunk, a
676
+ * turn boundary) is correctly absent, and a compaction `replace` deletes the
677
+ * shadowed nodes from the derivation. The projection rules are
678
+ * {@link deriveEventMessage}, with logged message projections applied
679
+ * without changing node membership or message identity.
680
+ *
681
+ * CACHED: pure tail growth costs O(new nodes); a replacement or message projection
682
+ * ({@link SessionSurface.contentGeneration}) rebuilds. The returned array is
683
+ * a fresh snapshot per call (later appends never grow an array a caller
684
+ * already holds); the `Message` objects in it are SHARED and **deep-frozen**.
685
+ * Unchanged content reuses frozen event data; projected blocks are frozen
686
+ * derived copies. Consumers cannot mutate the log through either form.
687
+ * @returns a fresh array of the shared, frozen derived history.
688
+ */
689
+ deriveMessages(): Message[];
690
+ /**
691
+ * Project one event with all committed message projections applied.
692
+ * The original durable event remains unchanged.
693
+ * @param event - the event to project.
694
+ * @returns the derived message, or null when the event produces none.
695
+ */
696
+ deriveEventMessage(event: SessionEvent): Message | null;
697
+ }
698
+ ```
699
+
700
+ ## Derived history: `deriveMessages()` and `deriveEventMessage()`
701
+
702
+ `Session.deriveMessages()` projects the event log into the `Message[]` the model sees — cached (each surface node projected once, when first seen; a surface rewrite rebuilds) and frozen (a fresh array per call over shared, deep-frozen messages, so mutating logged history through a projection is unrepresentable). `deriveEventMessage(event)` is the per-node pure function the fold applies — public so external reconstructors project a log prefix with exactly the same rules and cannot disagree with the cache. The projection rules:
703
+
704
+ - `user/message` → a user message carrying exact `content`; an optional envelope remains log-only display metadata.
705
+ - `assistant/message` → an assistant message with the provider and model that produced it plus optional adapter-private replay state. Its embedded compact stream is replay, usage, and UI evidence rather than a second message. An **empty-content** `assistant/message` is also skipped — a max-tokens step cut off with no content still records an `assistant/message` to hold its stream, usage, provider, and model, but a content-less assistant turn must not enter the provider transcript.
706
+ - `tool/result` → a first-class tool-role message carrying its result content and tool-call identity.
707
+ - `user/message` (injected context, i.e. non-`user` source) → a user-role message carrying its `content` verbatim at its chronological position; its typed source names the producer and carries any producer-specific data.
708
+
709
+ Everything else (`turn/*`, `step/*`, `assistant/attempt`, plugin-owned `llm/retry`) is structural and does not project into a message. Token accounting expands the embedded stream on each `assistant/message` or `assistant/attempt`, while the message's top-level `usage` remains the committed-message authority when present. A failed model-request attempt therefore retains its provider usage without fabricating an assistant message. Current logical validation rejects request headers and assistant messages that omit provider/model instead of guessing a route; supported historical representations are normalized and validated by their adjacent format edge before a current Session exists.
710
+
711
+ ## Live-session fork API
712
+
713
+ `ctx.sessions.create(id, { seed, meta })` is the low-level replay/fork primitive. For ordinary live-session forks, `SessionStore` exposes one policy API:
714
+
715
+ - `fork(source, boundary?, childSessionId?)` accepts a live `Session` object or live `SessionId`, selects source events through the inclusive `SessionSeq` boundary (default: current last event), closes any open tail, then creates an independent live session with deep-cloned seed events, `isSeeded: true`, the exact `inheritedEventCount`, and inherited `cwd`, without `parentSession`.
716
+
717
+ An explicit `boundary` lets callers fork from any stable between-turn position, including a previous `turn/end` or a later standalone log-only event, even if the source has newer events or an open current turn. The API rejects a prefix that ends inside an open turn instead of clipping silently. `dsh-subagent-fork-in-process` keeps its completed-prefix clipping because tool-time delegation usually starts while the parent turn is open; ordinary session branching should make the requested boundary explicit.
718
+
719
+ ## Why a turn ended: `TurnEndReasonMap`
720
+
721
+ `turn/start` has no trigger field. The entered `user/message` batch records what entered each step, `llm/retry` records request recovery, and idle injection remains pending until a waking delivery reaches a later pre-step. Live turns retain the typed [`AgentCancelCause`](./core.md#the-agent-handle) that stopped the driver; persistence uses the additional `{ kind: 'legacy' }` cause only when importing a supported coarse cancellation record that did not store its caller.
722
+
723
+ ```ts type-equiv
724
+ /** Durable cancellation cause, including imports whose original coarse record carried no cause. */
725
+ type TurnEndCancelCause = AgentCancelCause | { readonly kind: 'legacy' }
726
+ ```
727
+
728
+ ```ts type-equiv
729
+ /**
730
+ * Why a turn ended. Merge-extensible sum type.
731
+ */
732
+ interface TurnEndReasonMap {
733
+ completed: { kind: 'completed' }
734
+ /** A cancellation request interrupted the live turn. */
735
+ aborted: { kind: 'aborted'; reason: TurnEndCancelCause }
736
+
737
+ blocked: { kind: 'blocked' }
738
+ /**
739
+ * The turn failed. `error` is always a structured failure: the `LlmError`
740
+ * facts verbatim, or `{ message: errorChain(error), code }` flattened from
741
+ * any other error, where `code` is `TOOL_ACTIVATION_FAILED` for a failed
742
+ * tool condition and `UNKNOWN` otherwise.
743
+ */
744
+ error: { kind: 'error'; error: LlmFailure }
745
+ /** At least one step reached its output-token ceiling, even if a plugin continued the turn. */
746
+ 'max-tokens': { kind: 'max-tokens' }
747
+ /**
748
+ * A crash-orphaned turn was closed after the fact: agent-loop resume appends
749
+ * this closer for a stored log whose last turn never ended, and session-query
750
+ * synthesizes it on cold reads. The loop never emits this marker live, and
751
+ * the events recorded before the crash remain intact.
752
+ */
753
+ interrupted: { kind: 'interrupted' }
754
+ /**
755
+ * Fork-seed construction closed a turn that was still open at the fork
756
+ * boundary in the source session. Only fork seeds carry this marker — the
757
+ * loop never emits it — and the source events before the boundary remain
758
+ * intact in the child.
759
+ */
760
+ forked: { kind: 'forked' }
761
+ }
762
+ ```
763
+
764
+ `max-tokens` mirrors the model-call `FinishReason` of the same name: any `max-tokens` step in a turn makes the whole turn end `max-tokens` rather than `completed` (the cut-short fact wins over a later continuation), so a consumer can tell a clean stop from a truncated one. Cancellation and errors remain distinct outcomes. The loop emits neither `interrupted` nor `forked` live: crash recovery synthesizes `interrupted` (see [persistence.md](./persistence.md)), while fork-seed construction synthesizes `forked`. The map is merge-extensible.
765
+
766
+ ## Execution enclosure and standalone events
767
+
768
+ A turn encloses one model-loop execution, not the whole session log. AgentLoop records injected `user/message` events only from entering pre-step batches inside a turn; plugin-owned log-only events may still appear between `turn/end` and the next `turn/start`, consuming event seqs without incrementing turn numbers. Persistence admits every contiguous accepted event into a bounded durable batch, while crash repair closes only a genuinely open trailing turn. A producer that needs an immediate durability barrier explicitly awaits `ctx.sessions.flush(session)`.
769
+
770
+ Merge-extensible event relations belong to the plugin that declares them, so core does not reject an unknown event merely because no turn is open. See [the standalone-event decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md).
771
+
772
+ ## The end-seed boundary: `session/end-seed`
773
+
774
+ `buildForkSeed` appends `session/end-seed { inherited: true }` at the exact inherited-prefix cut before adding synthetic fork closers. The `Session` constructor retains that prepared marker or appends one when given only the inherited prefix. A restore retains the tagged marker and appends an ordinary `session/end-seed {}` only when its complete stored seed does not already end in a marker. Both forms are log-only and produce no message; only the constructor and `buildForkSeed` may create them.
775
+
776
+ For fork lineage, locate the LAST marker whose payload carries `inherited: true`; current-format decoding requires it exactly when `SessionHeader.isSeeded` is true and derives `inheritedEventCount` from its seq. For lifecycle ownership, locate the last `session/end-seed` of either form. Reopening a seed that already ends in any marker does not append another ordinary marker.
777
+
778
+ It exists because seed history and live work are otherwise byte-identical, which defeats any plugin owning a standalone open/close bracket: an unmatched `compaction/start` reads the same whether the writer crashed mid-compaction or is compacting right now. An opening marker before `session/end-seed` came from the constructor seed and belongs to an ended lifecycle, whatever ended it (a crash, a succeeding process, or a fork out of a still-running parent), so its owner may treat it as dead. That covers only brackets *this* session inherited: a concurrently live session holding an open bracket over the same history has its own boundary elsewhere, so tolerating concurrent writers needs a liveness signal beyond the log. Core writes the boundary and reads nothing from it — a bracket's vocabulary stays with its owning plugin, which is why crash repair closes turn/step/tool boundaries and never `compaction/*`.
779
+
780
+ Consumers that order Sessions by human activity exclude this boundary: picking a Session up is not work, so ordering by the log tail would float every opened Session to the top.
781
+
782
+ ## Plugin-contributed log-only events
783
+
784
+ A plugin may declaration-merge extra `SessionEventMap` types. These are **log-only**: NOT `SurfaceEventType`s (they carry no `surfaceOp` and contribute nothing to derived history). Their owner decides whether they belong to an open execution turn or may stand between turns. The generated [persistence log event catalog](../persistence-catalog.md) enumerates every core and plugin-contributed event; the compaction seam's `compaction/*` semantics are discussed on [compaction.md](./compaction.md).
785
+
786
+ When several events in one plugin-owned family assemble into one Web Client Conversation Node, every start, update, result, resource, or interruption event in that family carries or independently derives the same stable business id. This requirement applies to correlated Node families, not to every Session event; it lets the client group each event without guessing from adjacency or scanning history. See the [Conversation subsystem](./conversation.md).
787
+
788
+ The hook bridges' `hook/invoked` / `hook/result` pairs (from `@deepseek-ai/dsh-hook-protocol`) correlate by `handlerId`. `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, and `Stop` fire inside the loop's open turn, so their `hook/*` records are turn-enclosed by construction. `SessionStart` gets no `hook/*` record because it runs before turn 1; its context remains pending in the inbox until a waking delivery opens a turn.
789
+
790
+ ### Plugin records
791
+
792
+ Experimental packages declare plugin-owned state in `PluginRecordMap`: `appendPluginRecord()` uses each declaration to type its name and payload, and appends an event marked `ignorable: true` in the `plugin:` namespace. `pluginRecordOf()` reads stored records with unknown payloads regardless of current declarations. The [experimental persistence catalog](../experimental-persistence-catalog.md) lists current declarations. The [package README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/README.md#write-experimental-plugin-records) owns the caller restriction, retention, and read path.
793
+
794
+ ```ts type-equiv
795
+ /**
796
+ * Event type of an experimental plugin record: `plugin:` followed by
797
+ * slash-separated segments of lowercase letters, digits, `.`, `_`, and `-`,
798
+ * each starting with a letter or digit. A record type is never a
799
+ * {@link SessionEventMap} member.
800
+ */
801
+ type PluginRecordType = `plugin:${string}`
802
+ ```
803
+
804
+ ```ts type-equiv
805
+ /**
806
+ * Payloads written by experimental packages, keyed by their `plugin:` record
807
+ * names. Packages augment this map to type writes and enter the current plugin
808
+ * record catalog; these declarations do not enter {@link SessionEventMap} or
809
+ * released persistence schemas. Stored records still require owner validation.
810
+ */
811
+ interface PluginRecordMap {}
812
+ ```
813
+
814
+ ```ts type-equiv
815
+ /**
816
+ * One committed experimental plugin record, read from the log by
817
+ * `pluginRecordOf`. The record's owner validates `data` before use, because a
818
+ * restored record carries whatever JSON an earlier build of its owner wrote,
819
+ * or a V3 event that a format migration renamed into the `plugin:` namespace held.
820
+ */
821
+ interface PluginRecord {
822
+ /** The record type, chosen by the owning plugin. */
823
+ readonly type: PluginRecordType
824
+ /** The record's position in the Session log. */
825
+ readonly seq: SessionSeq
826
+ /** Unix epoch milliseconds at append. */
827
+ readonly time: number
828
+ /** The JSON payload as committed. */
829
+ readonly data: unknown
830
+ }
831
+ ```
832
+
833
+ ## Durability contract
834
+
835
+ What a persistence backend relies on: the durable log persists every event losslessly, and every Assistant attempt is one `assistant/message` or `assistant/attempt` whose embedded compact stream preserves the original timed chunks. `seq` stays contiguous across these settlements and all interleaved events. A backend may choose its own storage framing for an event batch as long as a handle's `read()` returns the exact appended events; current JSONL writes one row per event (see [persistence.md](./persistence.md)). All `event.data` must be JSON-serializable; `Session.append` enforces this at the source (throwing on non-serializable data), so a bad event never enters the log and `session.snapshotEvents()` always equals what a backend can persist. Adding an event type that carries non-serializable data, corrupts core execution nesting, or violates its owner's declared relation is a breaking change to the on-disk format.
836
+
837
+ The backends that consume this contract are on [persistence.md](./persistence.md).
838
+
839
+ ## Remote catalog and workspace opening
840
+
841
+ `ModelCatalog` is the Host-generation model directory returned by `session/modelCatalog`: it carries the deployment default, routable provider ids, successful provider groups, and isolated provider failures. It is not derived from one Session and remains separate from Session projections.
842
+
843
+ `SessionOpenWorkspacePathRequest` carries an absolute or workspace-resolved `path`; optional `action: "reveal"` selects file-manager navigation instead of default-application opening. `SessionOpenWorkspacePathValue` confirms that the Host accepted the native handoff. A Session-aware Client resolves relative paths against its current Session cwd when known; the controller hands the path to the opener unchanged and reports invalid requests, cancellation, and opener failures through the Session Remote error vocabulary.
844
+
845
+ The optional `application` selects a registered file handler without changing the system default; `workspacePathApplications({ path })` returns current handlers, names, icons, and default selection after Host path verification.
846
+
847
+ ## Session-log upload exclusions
848
+
849
+ `ctx.sessionLogDeepSeek.registerExclusion()` registers a caller-owned predicate over `SessionLogUploadRequest`. Any match omits the log contribution and its acceptance callback before reading Session events. No match preserves normal upload eligibility. The [package README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/session/session-log-deepseek/README.md#upload-exclusions) defines failure and disposal behavior.
850
+
851
+ ```ts type-equiv
852
+ /** Request identity inspected before preparing a Session-log contribution. */
853
+ interface SessionLogUploadRequest {
854
+ /** Live Session whose canonical log would be contributed. */
855
+ readonly sessionId: SessionId
856
+ /** Auxiliary request classification; absent for ordinary conversation requests. */
857
+ readonly purpose?: string
858
+ }
859
+ ```
860
+
861
+ ```ts type-equiv
862
+ /**
863
+ * Synchronous, side-effect-free upload exclusion owned by its registering plugin.
864
+ * @param request - request identity; the predicate must not mutate it.
865
+ * @returns `true` to omit the log field and acceptance callback, or `false` to keep checking eligibility.
866
+ */
867
+ type SessionLogUploadExclusion = (request: SessionLogUploadRequest) => boolean
868
+ ```
869
+
870
+ <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
871
+
872
+ <a id="cordis-surface"></a>
873
+
874
+ ## Cordis API
875
+
876
+ 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).
877
+
878
+ <a id="ctxsessioncontroller--sessioncontroller"></a>
879
+
880
+ ### `ctx.sessionController` — `SessionController`
881
+
882
+ Host service backing the generated `ctx.remote.session` namespace.
883
+
884
+ ```ts cordis-catalog
885
+ /**
886
+ * Resolve or resume one ordinary Session for another Host API domain.
887
+ * @param sessionId - Session identity whose Agent owns the operation.
888
+ * @returns the live Agent or the stable Session-domain failure.
889
+ */
890
+ resolveAgent(sessionId: SessionId): Promise<ApiSessionAgentResult>
891
+
892
+ /**
893
+ * Read all visible Session rows without resuming an Agent.
894
+ * @param _request - reserved empty list request.
895
+ * @param signal - cancellation for persistence reads and summary generation.
896
+ * @returns visible Session summaries ordered by activity.
897
+ */
898
+ @Remote('list') async list(_request: SessionListRequest, signal: AbortSignal): Promise<SessionListValue>
899
+
900
+ /**
901
+ * Search visible Session content without resuming an Agent. A non-owner
902
+ * receives only the hits whose Session `allows()` admits; `hasMore`
903
+ * describes the bounded result before that filtering.
904
+ * @param request - literal message-content query.
905
+ * @param signal - cancellation for list and search reads.
906
+ * @returns authorized bounded Session search results.
907
+ */
908
+ @Remote('search') async search(request: SessionSearchRequest, signal: AbortSignal): Promise<SessionSearchValue>
909
+
910
+ /**
911
+ * Create one ordinary Session for a new conversation, reusing the named blank Session while it is reusable.
912
+ * @param request - optional blank Session to reuse, location, and Agent preset.
913
+ * @returns the created or reused Session identity and its preset when configured.
914
+ */
915
+ @Remote('create') create(request: SessionCreateRequest): Promise<SessionCreateValue>
916
+
917
+ /**
918
+ * Select one Session-local model after explicitly resuming the Session; save the default in the background.
919
+ * @param request - Session identity and requested model selection.
920
+ * @returns the normalized selection installed for the Session, without waiting for default persistence.
921
+ */
922
+ @Remote('selectModel') selectModel(request: SessionSelectModelRequest): Promise<SessionSelectModelValue>
923
+
924
+ /**
925
+ * Select the first available account model after login when no provider API key is configured.
926
+ * @returns after saving the first available model or retaining the existing default.
927
+ */
928
+ @Remote async initializeDefaultModel(): Promise<void>
929
+
930
+ /**
931
+ * Describe every currently routable model for Host-generation selectors.
932
+ * @returns provider-grouped models, the deployment default, and isolated provider failures.
933
+ */
934
+ @Remote('modelCatalog') modelCatalog(): Promise<ModelCatalog>
935
+
936
+ /**
937
+ * Report whether this deployment can hand a Session workspace path to a native desktop.
938
+ * @returns true when the matching open operation is available.
939
+ */
940
+ @Remote canOpenWorkspacePath(): boolean
941
+
942
+ /**
943
+ * Describe the serving desktop for authenticated file-action routes.
944
+ * @returns Host name, configured availability, and platform-specific file-manager behavior.
945
+ */
946
+ workspaceDesktop(): { name: string; available: boolean; fileManager: 'finder' | 'explorer' | 'directory' | null }
947
+
948
+ /**
949
+ * Verify one path through the composed filesystem and open it on the Host desktop.
950
+ * @param request - path after best-effort Session workspace resolution.
951
+ * @param signal - caller lifetime; abort terminates the native command.
952
+ * @returns confirmation after the native opener accepts the path.
953
+ * @throws RemoteError when the request is invalid, has no verified Host mapping, is cancelled, or the opener fails.
954
+ */
955
+ @Remote('openWorkspacePath') async openWorkspacePath( request: SessionOpenWorkspacePathRequest, signal: AbortSignal, ): Promise<SessionOpenWorkspacePathValue>
956
+
957
+ /**
958
+ * Query current file handlers on the serving desktop without activating an Agent.
959
+ * @param request - file path in Host filesystem syntax.
960
+ * @param signal - caller lifetime, propagated to filesystem and desktop queries.
961
+ * @returns OS application names, icons, and default selection; empty when desktop opening is unavailable.
962
+ * @throws RemoteError when the path is invalid, the query is cancelled, or native discovery fails.
963
+ */
964
+ @Remote('workspacePathApplications') async workspacePathApplications( request: { readonly path: string }, signal: AbortSignal, ): Promise<readonly SessionWorkspacePathApplication[]>
965
+
966
+ /**
967
+ * Rename one Session after explicitly resuming it.
968
+ * @param request - Session identity and proposed title.
969
+ * @returns the accepted title and durable event sequence.
970
+ */
971
+ @Remote('rename') rename(request: SessionRenameRequest): Promise<SessionRenameValue>
972
+
973
+ /**
974
+ * Fork one cold-readable exact event prefix into a new Session. An omitted
975
+ * boundary selects the latest completed-turn prefix; an open cut receives
976
+ * synthetic fork closers.
977
+ * @param request - source Session, optional exact inclusive event boundary, and migration preflight choice.
978
+ * @returns the new Session identity.
979
+ */
980
+ @Remote('fork') async fork(request: SessionForkRequest): Promise<SessionForkValue>
981
+
982
+ /**
983
+ * Admit one prompt after explicitly resuming its Session.
984
+ * @param request - Session identity, prompt content, source metadata, and delivery mode.
985
+ * @param signal - caller cancellation before prompt admission begins.
986
+ * @returns acknowledgement that the Agent accepted the prompt.
987
+ */
988
+ @Remote('prompt') async prompt(request: SessionPromptRequest, signal: AbortSignal): Promise<SessionPromptValue>
989
+
990
+ /**
991
+ * Read one image from the attachment store after gateway authorization.
992
+ * @param request - image reference and gateway permission scope; Session existence is not checked.
993
+ * @returns the durable attachment reference and base64-encoded bytes.
994
+ */
995
+ @Remote('attachment') attachment(request: SessionAttachmentRequest): Promise<SessionAttachmentValue>
996
+
997
+ /**
998
+ * Mutate one still-pending queue occurrence, resuming a cold Agent first.
999
+ * @param request - Session, queue item, and requested mutation.
1000
+ * @returns acknowledgement that the queue mutation was applied.
1001
+ */
1002
+ @Remote('updateQueue') updateQueue(request: SessionUpdateQueueRequest): Promise<SessionUpdateQueueValue>
1003
+
1004
+ /**
1005
+ * Cancel one active Agent turn without dropping its pending inbox.
1006
+ * @param request - Session whose active Agent turn is cancelled.
1007
+ * @returns acknowledgement that cancellation was requested.
1008
+ */
1009
+ @Remote('cancel') cancel(request: SessionCancelRequest): SessionCancelValue
1010
+
1011
+ /**
1012
+ * Read one cold-safe, message-aligned Session history page.
1013
+ * @param request - durable address, optional fixed cut, backward cursor, and page budget.
1014
+ * @param signal - cancellation for persistence reads.
1015
+ * @returns one chronological page with its inclusive cursor.
1016
+ */
1017
+ @Remote events(request: SessionPageRequest, signal: AbortSignal): Promise<SessionEventsPage>
1018
+
1019
+ /**
1020
+ * Subscribe to Session events without reading a snapshot.
1021
+ * @param request - Session address.
1022
+ * @param signal - subscription cancellation.
1023
+ * @returns readiness and subsequent events, without a snapshot.
1024
+ */
1025
+ @Remote({ mode: 'stream' }) observeEvent(request: SessionObserveEventRequest, signal: AbortSignal): AsyncIterable<SessionEventFrame>
1026
+
1027
+ /**
1028
+ * Subscribe to Session projection changes.
1029
+ * @param request - target Session.
1030
+ * @param signal - subscription cancellation.
1031
+ * @returns readiness followed by one projection change frame per committed event.
1032
+ */
1033
+ @Remote({ mode: 'stream' }) observeProjections(request: SessionProjectionsRequest, signal: AbortSignal): AsyncIterable<SessionProjectionFrame>
1034
+
1035
+ /**
1036
+ * Read passive Session state.
1037
+ * @param request - target Session.
1038
+ * @param signal - read cancellation.
1039
+ * @returns current passive state.
1040
+ */
1041
+ @Remote state(request: SessionStateRequest, signal: AbortSignal): Promise<SessionState>
1042
+
1043
+ /**
1044
+ * Subscribe to complete Session state replacements.
1045
+ * @param request - target Session.
1046
+ * @param signal - subscription cancellation.
1047
+ * @returns complete passive state replacements.
1048
+ */
1049
+ @Remote({ mode: 'stream' }) observeState(request: SessionStateRequest, signal: AbortSignal): AsyncIterable<SessionState>
1050
+
1051
+ /**
1052
+ * Wait for Session quiescence or pending input with bounded assistant output.
1053
+ * @param request - Session, wait bounds and output window.
1054
+ * @param signal - caller cancellation.
1055
+ * @returns stopping state and bounded assistant output.
1056
+ */
1057
+ @Remote wait(request: SessionWaitRequest, signal: AbortSignal): Promise<SessionWaitValue>
1058
+
1059
+ /**
1060
+ * Follow one Session log from its opening or resume cursor.
1061
+ * @param request - durable address and last committed sequence already held by the caller.
1062
+ * @param signal - cancellation owned by the Remote stream carrier.
1063
+ * @returns a complete opening snapshot followed by gap-free durable event
1064
+ * frames and optional cursorless assistant-stream frames.
1065
+ */
1066
+ @Remote({ mode: 'stream' }) follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>
1067
+
1068
+ /**
1069
+ * Read exact projections, returning cached hints only when migration is required.
1070
+ * @param request - Session whose current values are required.
1071
+ * @param signal - cancellation for the Session read.
1072
+ * @returns a sequenced baseline, cached hints when migration is deferred, or null when absent.
1073
+ */
1074
+ @Remote('projections') projections(request: SessionProjectionsRequest, signal: AbortSignal): Promise<SessionProjectionsValue>
1075
+
1076
+ /**
1077
+ * Stream a complete live-control baseline followed by replacement frames.
1078
+ * @param signal - cancellation owned by the Remote stream carrier.
1079
+ * @returns one complete baseline followed by live replacement frames.
1080
+ */
1081
+ @Remote({ mode: 'stream' }) async *control(signal: AbortSignal): AsyncIterable<SessionControlFrame>
1082
+ ```
1083
+
1084
+ Types: [SessionId](./core.md) · [SessionSearchRequest](./session-query.md)
1085
+
1086
+ Source: [`packages/api/session-controller/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/index.ts)
1087
+
1088
+ <a id="ctxsessionlogdeepseek--sessionlogdeepseekservice"></a>
1089
+
1090
+ ### `ctx.sessionLogDeepSeek` — `SessionLogDeepSeekService`
1091
+
1092
+ Incremental Session-log contributor with caller-owned upload exclusions.
1093
+
1094
+ ```ts cordis-catalog
1095
+ /**
1096
+ * Exclude matching requests before reading or preparing their log contribution.
1097
+ * Any matching rule suppresses both the field and its acceptance callback. With
1098
+ * no matching rules, normal upload eligibility applies. A throwing rule fails
1099
+ * this contribution; the request-extension registry omits it and reports the error.
1100
+ * @param exclude - synchronous, side-effect-free predicate; `true` skips this upload.
1101
+ * @returns effect disposer that removes this registration when called or when the caller unloads.
1102
+ */
1103
+ registerExclusion(exclude: SessionLogUploadExclusion): () => Promise<void>
1104
+ ```
1105
+
1106
+ Source: [`packages/session/session-log-deepseek/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/session/session-log-deepseek/src/index.ts)
1107
+
1108
+ <a id="ctxsessions--sessionstore"></a>
1109
+
1110
+ ### `ctx.sessions` — `SessionStore`
1111
+
1112
+ In-memory session store (`ctx.sessions`).
1113
+
1114
+ Persistence is intentionally not implemented here — the agent lifecycle attaches a session-log writer to each published session's write handle; a session published outside that lifecycle persists nothing.
1115
+
1116
+ ```ts cordis-catalog
1117
+ /**
1118
+ * Register one event interpreter for live creation, restore, and fork.
1119
+ * Disposing the contribution makes sessions that used it refuse further derivation.
1120
+ * @param projection - pure definition owned by the event's plugin.
1121
+ * @returns the fiber-owned disposer.
1122
+ * @throws when another definition already owns this event type.
1123
+ */
1124
+ registerMessageProjection(projection: SessionMessageProjection): () => Promise<void>
1125
+
1126
+ /**
1127
+ * Create a session owned by the calling fiber: disposing that fiber stops
1128
+ * event notification and removes the session from the store. `options.seed`
1129
+ * populates the session with a copy of those events (replay/fork);
1130
+ * `options.meta` attaches creation metadata (validated absolute `cwd`, seed
1131
+ * and parent lineage, and delegation depth) as the immutable
1132
+ * {@link SessionHeader} (the store fills `version`/`id`/`createdAt`).
1133
+ *
1134
+ * For an agent whose session must be torn down IN ORDER with its loop (so the
1135
+ * loop's final events are published before the store attachment ends), do NOT use this
1136
+ * — fold the session lifecycle into the agent's own effect via
1137
+ * {@link prepare} + {@link enter} + {@link announce} (see
1138
+ * `dsh-agent-loop`'s creation transaction).
1139
+ *
1140
+ * @param id - the session id; omitted, the store mints `session-<n>`.
1141
+ * @param options - seed events and/or creation metadata for the header.
1142
+ * @returns the live session, already entered and announced.
1143
+ * @throws if a session with `id` already exists, metadata is not a plain
1144
+ * lossless-JSON record with valid scalar fields, or `meta.cwd` is a
1145
+ * non-absolute path (storage backends key directories off it).
1146
+ */
1147
+ create(id?: SessionId, options?: CreateSessionOptions): Session
1148
+
1149
+ /**
1150
+ * Build a session WITHOUT entering it into the store — validate the id/cwd and
1151
+ * construct the {@link Session} (with its immutable {@link SessionHeader}).
1152
+ * Pairs with {@link enter} + {@link announce}: a caller that owns a composite
1153
+ * `ctx.effect` (the agent factory) folds the session lifecycle into that ONE
1154
+ * effect so a fiber unload tears the session + agent down as a single ORDERED
1155
+ * chain rather than as racing sibling effects — which would remove the publication hooks
1156
+ * before the driver's closing events commit, dropping them.
1157
+ *
1158
+ * @param id - the session id; omitted, the store mints `session-<n>`.
1159
+ * @param options - seed events and/or creation metadata for the header. With
1160
+ * `eventState`, every seed event is either independently owned or any
1161
+ * shared value is deeply frozen; {@link Session.fromRestore} validates and
1162
+ * adopts those values without copying or freezing them.
1163
+ * @returns the store-owned session, not yet entered and read-only until announced.
1164
+ * @throws if a session with `id` already exists, metadata is not a plain
1165
+ * lossless-JSON record with valid scalar fields, or `meta.cwd` is a
1166
+ * non-absolute path.
1167
+ */
1168
+ prepare(id?: SessionId, options?: PrepareSessionOptions): Session
1169
+
1170
+ /**
1171
+ * Enter a session into the store: install the module-private
1172
+ * append publication hooks and add it to the store. Writes remain disabled
1173
+ * until {@link announce} completes successfully. Returns the DETACH
1174
+ * disposer (hooks + store removal). Does NOT emit `session/created` —
1175
+ * the caller yields this disposer inside its effect and THEN calls
1176
+ * {@link announce}, so a throwing `session/created` listener rolls the attach
1177
+ * back instead of leaking it.
1178
+ *
1179
+ * Re-checks the id for a duplicate: `prepare` and `enter` are public
1180
+ * cross-package primitives and a caller may interleave arbitrary work (or
1181
+ * another create) between them, so a stale prepared session must NOT overwrite
1182
+ * a live store entry of the same id — its detach disposer would later delete
1183
+ * the REAL session. The {@link create} convenience and the agent factory call
1184
+ * the two back-to-back so they never trip this, but the public API cannot
1185
+ * assume that.
1186
+ *
1187
+ * @param session - an unentered session; independent instances become store-owned.
1188
+ * @returns the detach disposer (publication hooks + store removal). When called from
1189
+ * a synchronous `session/created` listener, removal and disposal wait until
1190
+ * that creation dispatch unwinds.
1191
+ * @throws if a session with this id is already in the store.
1192
+ */
1193
+ enter(session: Session): () => void
1194
+
1195
+ /** Emit `session/created` exactly once for an {@link enter}ed session (with
1196
+ * the carrier {@link enter} captured). Separate from {@link enter} so the
1197
+ * caller can yield the detach disposer first (rollback safety — see
1198
+ * {@link enter}). The Session remains read-only throughout synchronous
1199
+ * dispatch; successful completion enables appends. A failed announcement
1200
+ * leaves it read-only and cannot be retried.
1201
+ * @param session - the entered session to announce to listeners.
1202
+ * @throws if the session is not live or its announcement already began,
1203
+ * including a reentrant call from a creation listener. */
1204
+ announce(session: Session): void
1205
+
1206
+ /**
1207
+ * Dispatch the awaited `session/flush` durability checkpoint for `session`,
1208
+ * with the carrier captured at {@link enter}. THE flush entry point: the
1209
+ * store owns the carrier, so callers (the checkpoint policy's per-request
1210
+ * barrier, goal-round-driver's idle checkpoint, teardown drains, and consumers
1211
+ * that flush themselves before reading storage) must come through here
1212
+ * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner
1213
+ * and one spelling.
1214
+ * @param session - the session whose buffered events must reach durable storage.
1215
+ * @returns whether at least one durability listener participated, after every
1216
+ * listener has settled successfully.
1217
+ * @throws the first registered listener failure after every listener settles.
1218
+ */
1219
+ async flush(session: Session): Promise<boolean>
1220
+
1221
+ /**
1222
+ * Look up a live session.
1223
+ * @param id - the session id to look up.
1224
+ * @returns the session, or undefined when no live session has that id.
1225
+ */
1226
+ get(id: SessionId): Session | undefined
1227
+
1228
+ /**
1229
+ * All live sessions, in creation order.
1230
+ * @returns a fresh array; mutating it does not affect the store.
1231
+ */
1232
+ list(): Session[]
1233
+
1234
+ /**
1235
+ * Create an independent live session from an exact prefix of a live source.
1236
+ * `boundary` is an inclusive source event seq; omitted means the source's
1237
+ * current last event. An open tail receives synthetic tool results and
1238
+ * step/turn closers with the forked cause. Closed steps and turns remain
1239
+ * unchanged, including any failed tool calls already missing results.
1240
+ * `inheritedEventCount` counts only copied source events, excluding these closers.
1241
+ *
1242
+ * @param source - Live source session object or id.
1243
+ * @param boundary - Inclusive source event seq to fork through; omitted means
1244
+ * the source's current last event, and omitted on an empty source forks an
1245
+ * empty child.
1246
+ * @param childSessionId - Optional child session id; omitted delegates to
1247
+ * `SessionStore`'s id policy.
1248
+ * @returns The created live child session.
1249
+ */
1250
+ fork(source: SessionForkSource, boundary?: SessionSeq, childSessionId?: SessionId): Session
1251
+ ```
1252
+
1253
+ Types: [CreateSessionOptions](./persistence.md) · [PrepareSessionOptions](./persistence.md) · [SessionId](./core.md)
1254
+
1255
+ Source: [`packages/core/session/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/index.ts)
1256
+
1257
+ <a id="api-session-events"></a>
1258
+
1259
+ ### `api-session/*` events
1260
+
1261
+ <a id="api-sessionactivity--emit"></a>
1262
+
1263
+ #### `api-session/activity` — emit
1264
+
1265
+ One user-authored durable message advanced Session list activity.
1266
+
1267
+ ```ts cordis-catalog
1268
+ /**
1269
+ * One user-authored durable message advanced Session list activity.
1270
+ * @mode emit
1271
+ * @param sessionId - addressed Session identity.
1272
+ * @param updatedAt - durable message time used for list ordering.
1273
+ */
1274
+ 'api-session/activity'(sessionId: SessionId, updatedAt: number): void
1275
+ ```
1276
+
1277
+ Types: [SessionId](./core.md)
1278
+
1279
+ Source: [`packages/api/session-controller/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/types.ts)
1280
+
1281
+ <a id="api-sessionadded--emit"></a>
1282
+
1283
+ #### `api-session/added` — emit
1284
+
1285
+ A Session became visible or its Agent was created or disposed. Consumers upsert the summary and replace its current running and availability state.
1286
+
1287
+ ```ts cordis-catalog
1288
+ /**
1289
+ * A Session became visible or its Agent was created or disposed.
1290
+ * Consumers upsert the summary and replace its current running and availability state.
1291
+ * @mode emit
1292
+ * @param summary - current list row for the Session.
1293
+ */
1294
+ 'api-session/added'(summary: SessionSummary): void
1295
+ ```
1296
+
1297
+ Source: [`packages/api/session-controller/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/types.ts)
1298
+
1299
+ <a id="api-sessionerror--emit"></a>
1300
+
1301
+ #### `api-session/error` — emit
1302
+
1303
+ One Agent failed outside a durable turn position.
1304
+
1305
+ ```ts cordis-catalog
1306
+ /**
1307
+ * One Agent failed outside a durable turn position.
1308
+ * @mode emit
1309
+ * @param sessionId - Agent and Session identity.
1310
+ * @param message - user-safe failure chain.
1311
+ */
1312
+ 'api-session/error'(sessionId: SessionId, message: string): void
1313
+ ```
1314
+
1315
+ Types: [SessionId](./core.md)
1316
+
1317
+ Source: [`packages/api/session-controller/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/types.ts)
1318
+
1319
+ <a id="api-sessionremoved--emit"></a>
1320
+
1321
+ #### `api-session/removed` — emit
1322
+
1323
+ A Session left the live Host registry.
1324
+
1325
+ ```ts cordis-catalog
1326
+ /**
1327
+ * A Session left the live Host registry.
1328
+ * @mode emit
1329
+ * @param sessionId - removed Session identity.
1330
+ */
1331
+ 'api-session/removed'(sessionId: SessionId): void
1332
+ ```
1333
+
1334
+ Types: [SessionId](./core.md)
1335
+
1336
+ Source: [`packages/api/session-controller/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/types.ts)
1337
+
1338
+ <a id="api-sessionstatus--emit"></a>
1339
+
1340
+ #### `api-session/status` — emit
1341
+
1342
+ One Agent changed running state.
1343
+
1344
+ ```ts cordis-catalog
1345
+ /**
1346
+ * One Agent changed running state.
1347
+ * @mode emit
1348
+ * @param sessionId - Agent and Session identity.
1349
+ * @param running - whether the Agent is running.
1350
+ */
1351
+ 'api-session/status'(sessionId: SessionId, running: boolean): void
1352
+ ```
1353
+
1354
+ Types: [SessionId](./core.md)
1355
+
1356
+ Source: [`packages/api/session-controller/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/types.ts)
1357
+
1358
+ <a id="session-events"></a>
1359
+
1360
+ ### `session/*` events
1361
+
1362
+ <a id="sessioncreated--emit"></a>
1363
+
1364
+ #### `session/created` — emit
1365
+
1366
+ Read-only creation announcement after store entry. New events reject until every synchronous listener has returned successfully. A synchronous throw vetoes creation and rolls back with a paired disposal; detach requested during dispatch is deferred. A returned-promise rejection is logged but cannot retroactively veto this synchronous boundary. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only sessions entered through that agent's context.
1367
+
1368
+ ```ts cordis-catalog
1369
+ /**
1370
+ * Read-only creation announcement after store entry.
1371
+ * New events reject until every synchronous listener has returned successfully.
1372
+ * A synchronous throw vetoes creation and rolls back with a paired disposal;
1373
+ * detach requested during dispatch is deferred.
1374
+ * A returned-promise rejection is logged but cannot retroactively veto this
1375
+ * synchronous boundary.
1376
+ * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners
1377
+ * receive only sessions entered through that agent's context.
1378
+ * @param session - the session just entered and announced.
1379
+ * @mode emit
1380
+ */
1381
+ 'session/created'(this: Scoped<Session>, session: Session): void
1382
+ ```
1383
+
1384
+ Types: [Scoped](./scope.md)
1385
+
1386
+ Source: [`packages/core/session/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/index.ts)
1387
+
1388
+ <a id="sessiondisposed--emit"></a>
1389
+
1390
+ #### `session/disposed` — emit
1391
+
1392
+ Emitted once when an announced session leaves the store, including publication rollback, but never for an entry whose creation announcement did not begin. Listener failures are logged and contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope.
1393
+
1394
+ ```ts cordis-catalog
1395
+ /**
1396
+ * Emitted once when an announced session leaves the store, including
1397
+ * publication rollback, but never for an entry whose creation announcement
1398
+ * did not begin. Listener failures are logged and contained.
1399
+ * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope.
1400
+ * @param session - the session that is no longer live in the store.
1401
+ * @mode emit
1402
+ */
1403
+ 'session/disposed'(this: Scoped<Session>, session: Session): void
1404
+ ```
1405
+
1406
+ Types: [Scoped](./scope.md)
1407
+
1408
+ Source: [`packages/core/session/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/index.ts)
1409
+
1410
+ <a id="sessionevent--emit"></a>
1411
+
1412
+ #### `session/event` — emit
1413
+
1414
+ Post-commit, fire-and-forget append feed. The listener snapshot resolves before the log push, but callbacks run after it; observer failures are logged and contained without making the committed append fail. Array append commits every member before publishing them in order; `session.seq` and `session.surface` already include the complete batch. `session.currentBatchPublication` identifies the member being delivered. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only events from sessions entered through that agent's context.
1415
+
1416
+ ```ts cordis-catalog
1417
+ /**
1418
+ * Post-commit, fire-and-forget append feed. The listener snapshot resolves
1419
+ * before the log push, but callbacks run after it; observer failures are
1420
+ * logged and contained without making the committed append fail.
1421
+ * Array append commits every member before publishing them in order;
1422
+ * `session.seq` and `session.surface` already include the complete batch.
1423
+ * `session.currentBatchPublication` identifies the member being delivered.
1424
+ * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners
1425
+ * receive only events from sessions entered through that agent's context.
1426
+ * @param session - the session whose log grew.
1427
+ * @param event - the appended event, exactly as recorded.
1428
+ * @mode emit
1429
+ */
1430
+ 'session/event'(this: Scoped<Session>, session: Session, event: SessionEvent): void
1431
+ ```
1432
+
1433
+ Types: [Scoped](./scope.md)
1434
+
1435
+ Source: [`packages/core/session/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/index.ts)
1436
+
1437
+ <a id="sessionflush--parallel"></a>
1438
+
1439
+ #### `session/flush` — parallel
1440
+
1441
+ Awaited parallel durability checkpoint: every listener runs and the caller awaits all of them, with no waterfall veto. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the session's owner scope.
1442
+
1443
+ ```ts cordis-catalog
1444
+ /**
1445
+ * Awaited parallel durability checkpoint: every listener runs and the
1446
+ * caller awaits all of them, with no waterfall veto. Scope-filtered dispatch
1447
+ * (`@deepseek-ai/dsh-scope`) reuses the session's owner scope.
1448
+ * @param session - the session whose buffered events must reach durable storage.
1449
+ * @mode parallel
1450
+ */
1451
+ 'session/flush'(this: Scoped<Session>, session: Session): Promise<void> | void
1452
+ ```
1453
+
1454
+ Types: [Scoped](./scope.md)
1455
+
1456
+ Source: [`packages/core/session/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/index.ts)
1457
+
1458
+ <a id="timeline-events"></a>
1459
+
1460
+ ### `timeline/*` events
1461
+
1462
+ <a id="timelinepriority--emit"></a>
1463
+
1464
+ #### `timeline/priority` — emit
1465
+
1466
+ A saved user edit entered or left P0. Model task patches do not emit it.
1467
+
1468
+ ```ts cordis-catalog
1469
+ /**
1470
+ * A saved user edit entered or left P0. Model task patches do not emit it.
1471
+ * @mode emit
1472
+ * @param session - the session that owns the task.
1473
+ */
1474
+ 'timeline/priority'(session: Session): void
1475
+ ```
1476
+
1477
+ Source: [`packages/experimental/timeline/src/storage.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/experimental/timeline/src/storage.ts)
1478
+ <!-- END GENERATED cordis-surface -->