dsh-plugin-dev-kb 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.en.md +144 -0
  3. package/README.md +21 -7
  4. package/kb/INDEX.md +19 -5
  5. package/kb/README.md +11 -10
  6. package/kb/extra/AGENTS.md +4 -4
  7. package/kb/extra/cookbook/adding-a-vendored-package.md +2 -2
  8. package/kb/extra/cookbook/adding-a-vendored-package.zh.md +2 -2
  9. package/kb/extra/deepseek-llm-api-wire-extensions.md +159 -0
  10. package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +159 -0
  11. package/kb/extra/development.md +8 -14
  12. package/kb/extra/development.zh.md +8 -14
  13. package/kb/extra/event-producer-consumer.md +47 -41
  14. package/kb/extra/event-producer-consumer.zh.md +47 -41
  15. package/kb/extra/glossary.md +1 -1
  16. package/kb/extra/glossary.zh.md +1 -1
  17. package/kb/extra/graph-atlas.md +0 -2
  18. package/kb/extra/graph-atlas.zh.md +0 -2
  19. package/kb/extra/i18n/README.md +4 -4
  20. package/kb/extra/i18n/README.zh.md +4 -4
  21. package/kb/extra/module-graph.md +680 -413
  22. package/kb/extra/module-graph.zh.md +681 -414
  23. package/kb/extra/postmortem/0001-acp-default-export-drops-inject.md +2 -2
  24. package/kb/extra/postmortem/0001-acp-default-export-drops-inject.zh.md +2 -2
  25. package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.md +2 -2
  26. package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +2 -2
  27. package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.md +2 -2
  28. package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.zh.md +2 -2
  29. package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +1 -1
  30. package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +1 -1
  31. package/kb/extra/rescope.md +2 -2
  32. package/kb/extra/rescope.zh.md +2 -2
  33. package/kb/extra/subsystems/agent-team.md +24 -1
  34. package/kb/extra/subsystems/agent-team.zh.md +24 -1
  35. package/kb/extra/subsystems/attachment.md +12 -4
  36. package/kb/extra/subsystems/attachment.zh.md +12 -4
  37. package/kb/extra/subsystems/extensions.md +18 -0
  38. package/kb/extra/subsystems/extensions.zh.md +18 -0
  39. package/kb/extra/subsystems/feedback.md +2 -2
  40. package/kb/extra/subsystems/feedback.zh.md +2 -2
  41. package/kb/extra/subsystems/todo.md +32 -0
  42. package/kb/extra/subsystems/todo.zh.md +32 -0
  43. package/kb/extra/subsystems/webhook.md +70 -0
  44. package/kb/extra/subsystems/webhook.zh.md +70 -0
  45. package/kb/extra/testing.md +11 -10
  46. package/kb/extra/testing.zh.md +8 -7
  47. package/kb/meta/search-index.json +269 -161
  48. package/kb/meta/site-pages.txt +182 -168
  49. package/kb/meta/source.json +5 -5
  50. package/kb/meta/topics.md +14 -6
  51. package/kb/site/develop/basic/publish.md +2 -2
  52. package/kb/site/develop/basic/tool.md +1 -1
  53. package/kb/site/develop/cordis-tutorial/07-into-the-harness.md +4 -4
  54. package/kb/site/develop/framework/events.md +1 -1
  55. package/kb/site/develop/practice/dynamic-cordis.md +17 -0
  56. package/kb/site/develop/practice/llm-adapter.md +3 -3
  57. package/kb/site/en/develop/basic/publish.md +2 -2
  58. package/kb/site/en/develop/basic/tool.md +1 -1
  59. package/kb/site/en/develop/cordis-tutorial/07-into-the-harness.md +4 -4
  60. package/kb/site/en/develop/framework/events.md +1 -1
  61. package/kb/site/en/develop/practice/dynamic-cordis.md +17 -0
  62. package/kb/site/en/develop/practice/llm-adapter.md +3 -3
  63. package/kb/site/en/guide/github-review.md +104 -0
  64. package/kb/site/en/guide/mcp-memory.md +103 -0
  65. package/kb/site/en/guide/python-sdk.md +80 -34
  66. package/kb/site/en/guide/schedule.md +21 -0
  67. package/kb/site/en/reference/agent-lifecycle.md +1 -1
  68. package/kb/{extra → site/en/reference}/api-gateway.md +11 -9
  69. package/kb/site/en/reference/capability-seams.md +115 -67
  70. package/kb/site/en/reference/config-catalog.md +358 -164
  71. package/kb/site/en/reference/cookbook/adding-a-package.md +2 -2
  72. package/kb/site/en/reference/cookbook/adding-a-settings-card.md +2 -2
  73. package/kb/site/en/reference/cookbook/adding-a-tool.md +11 -4
  74. package/kb/site/en/reference/cookbook/adding-an-llm-adapter.md +1 -1
  75. package/kb/site/en/reference/cookbook/extension-cookbook.md +6 -6
  76. package/kb/site/en/reference/cordis-api/inherited.md +1 -1
  77. package/kb/site/en/reference/cordis-primer.md +2 -1
  78. package/kb/site/en/reference/index.md +19 -7
  79. package/kb/site/en/reference/persistence-catalog.md +91 -44
  80. package/kb/site/en/reference/subsystems/approval.md +10 -10
  81. package/kb/site/en/reference/subsystems/client-modules.md +58 -16
  82. package/kb/site/en/reference/subsystems/code-runtime.md +3 -3
  83. package/kb/site/en/reference/subsystems/compaction.md +2 -2
  84. package/kb/site/en/reference/{cookbook/adding-a-conversation-node.md → subsystems/conversation.md} +43 -24
  85. package/kb/site/en/reference/subsystems/core.md +70 -12
  86. package/kb/site/en/reference/subsystems/credentials.md +43 -3
  87. package/kb/site/en/reference/subsystems/filesystem.md +12 -2
  88. package/kb/site/en/reference/subsystems/index.md +6 -1
  89. package/kb/site/en/reference/subsystems/jobs.md +1 -1
  90. package/kb/site/en/reference/subsystems/llm-streaming.md +132 -11
  91. package/kb/site/en/reference/subsystems/permission-presets.md +1 -1
  92. package/kb/site/en/reference/subsystems/persistence.md +22 -3
  93. package/kb/site/en/reference/subsystems/plan.md +1 -1
  94. package/kb/site/en/reference/subsystems/session-projection.md +74 -33
  95. package/kb/site/en/reference/subsystems/session-query.md +9 -1
  96. package/kb/site/en/reference/subsystems/session-reference.md +28 -7
  97. package/kb/site/en/reference/subsystems/session-telemetry.md +2 -3
  98. package/kb/site/en/reference/subsystems/session.md +260 -41
  99. package/kb/site/en/reference/subsystems/settings.md +78 -1
  100. package/kb/site/en/reference/subsystems/skills.md +23 -0
  101. package/kb/site/en/reference/subsystems/slots.md +177 -0
  102. package/kb/site/en/reference/subsystems/spill.md +2 -2
  103. package/kb/site/en/reference/subsystems/storage.md +9 -1
  104. package/kb/site/en/reference/subsystems/subagent.md +90 -23
  105. package/kb/site/en/reference/subsystems/system-prompt.md +4 -4
  106. package/kb/site/en/reference/subsystems/token-meter.md +25 -10
  107. package/kb/site/en/reference/subsystems/tools.md +39 -39
  108. package/kb/site/en/reference/subsystems/typert.md +44 -37
  109. package/kb/site/en/reference/subsystems/user-questions.md +33 -33
  110. package/kb/site/en/reference/subsystems/web-client.md +98 -0
  111. package/kb/site/en/reference/subsystems/web-server.md +11 -5
  112. package/kb/site/en/reference/subsystems/web.md +7 -1
  113. package/kb/site/en/reference/subsystems/workspace.md +95 -2
  114. package/kb/site/en/reference/tool-catalog.md +76 -18
  115. package/kb/site/en/reference/tool-execution-pipeline.md +1 -1
  116. package/kb/site/guide/github-review.md +104 -0
  117. package/kb/site/guide/mcp-memory.md +103 -0
  118. package/kb/site/guide/python-sdk.md +87 -41
  119. package/kb/site/guide/schedule.md +21 -0
  120. package/kb/site/reference/agent-lifecycle.md +1 -1
  121. package/kb/{extra/api-gateway.zh.md → site/reference/api-gateway.md} +11 -9
  122. package/kb/site/reference/capability-seams.md +115 -67
  123. package/kb/site/reference/config-catalog.md +357 -163
  124. package/kb/site/reference/cookbook/adding-a-package.md +2 -2
  125. package/kb/site/reference/cookbook/adding-a-settings-card.md +2 -2
  126. package/kb/site/reference/cookbook/adding-a-tool.md +11 -4
  127. package/kb/site/reference/cookbook/adding-an-llm-adapter.md +1 -1
  128. package/kb/site/reference/cookbook/extension-cookbook.md +6 -6
  129. package/kb/site/reference/cordis-api/inherited.md +1 -1
  130. package/kb/site/reference/cordis-primer.md +2 -1
  131. package/kb/site/reference/index.md +19 -7
  132. package/kb/site/reference/persistence-catalog.md +87 -40
  133. package/kb/site/reference/subsystems/approval.md +10 -10
  134. package/kb/site/reference/subsystems/client-modules.md +58 -16
  135. package/kb/site/reference/subsystems/code-runtime.md +3 -3
  136. package/kb/site/reference/subsystems/compaction.md +2 -2
  137. package/kb/site/reference/{cookbook/adding-a-conversation-node.md → subsystems/conversation.md} +43 -24
  138. package/kb/site/reference/subsystems/core.md +70 -12
  139. package/kb/site/reference/subsystems/credentials.md +43 -3
  140. package/kb/site/reference/subsystems/filesystem.md +12 -2
  141. package/kb/site/reference/subsystems/index.md +6 -1
  142. package/kb/site/reference/subsystems/jobs.md +1 -1
  143. package/kb/site/reference/subsystems/llm-streaming.md +132 -11
  144. package/kb/site/reference/subsystems/persistence.md +22 -3
  145. package/kb/site/reference/subsystems/plan.md +1 -1
  146. package/kb/site/reference/subsystems/session-projection.md +74 -33
  147. package/kb/site/reference/subsystems/session-query.md +9 -1
  148. package/kb/site/reference/subsystems/session-reference.md +28 -7
  149. package/kb/site/reference/subsystems/session-telemetry.md +2 -3
  150. package/kb/site/reference/subsystems/session.md +260 -41
  151. package/kb/site/reference/subsystems/settings.md +78 -1
  152. package/kb/site/reference/subsystems/skills.md +23 -0
  153. package/kb/site/reference/subsystems/slots.md +177 -0
  154. package/kb/site/reference/subsystems/spill.md +2 -2
  155. package/kb/site/reference/subsystems/storage.md +9 -1
  156. package/kb/site/reference/subsystems/subagent.md +90 -23
  157. package/kb/site/reference/subsystems/system-prompt.md +4 -4
  158. package/kb/site/reference/subsystems/token-meter.md +25 -10
  159. package/kb/site/reference/subsystems/tools.md +39 -39
  160. package/kb/site/reference/subsystems/typert.md +44 -37
  161. package/kb/site/reference/subsystems/user-questions.md +33 -33
  162. package/kb/site/reference/subsystems/web-client.md +98 -0
  163. package/kb/site/reference/subsystems/web-server.md +11 -5
  164. package/kb/site/reference/subsystems/web.md +7 -1
  165. package/kb/site/reference/subsystems/workspace.md +95 -2
  166. package/kb/site/reference/tool-catalog.md +76 -18
  167. package/kb/site/reference/tool-execution-pipeline.md +1 -1
  168. package/package.json +11 -3
  169. package/skills/dsh-plugin-dev-kb.md +8 -6
@@ -1,14 +1,29 @@
1
1
  ---
2
- editSource: "docs/cookbook/adding-a-conversation-node.md"
2
+ editSource: "docs/subsystems/conversation.md"
3
+ outline: [2,3]
3
4
  ---
4
5
 
5
- # Add a Web Client conversation node
6
+ # Conversation assembly
6
7
 
7
- This tutorial adds one business-owned row to the Web Client Chat view. The finished plugin correlates a durable Session event family into one Context, incrementally builds business State, publishes typed Step data, and renders a keyed Chat Node without scanning the Session window or other rendered nodes. It assumes the Host already records the events and the client plugin is composed into the Web bundle; external Host-side UIs and additional view targets such as Trajectory are outside this tutorial.
8
+ Conversation is the target-neutral assembly layer between a Client `SessionEventLikeEntry` window and browser views. [`ui-conversation`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-conversation/README.md) owns the event and view registries, one identity-stable binding per `SessionBinding`, Turn/Step locations, incremental Context assembly, target sources, the shared shell, and input orchestration. Target packages such as [`ui-chat`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-chat/README.md) and [`ui-trajectory`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-trajectory/README.md) own their Definitions, final snapshots, and rendering.
8
9
 
9
- The [Conversation Node assembly decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md) owns the rationale and complete engine model. This guide covers the implementation path.
10
+ This page defines the data model and the extension path for a business-owned Conversation node. The [Web Client architecture](./web-client.md) places the subsystem between Client models and Slots; the [Conversation Node assembly decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md) owns its rationale.
10
11
 
11
- ## 1. Design a replayable event family
12
+ ## Data model and ownership
13
+
14
+ The Session Controller owns the contiguous loaded logical-event window. Each `SessionEventLikeEntry` is either `{ type: 'event', event: SessionEvent }` or `{ type: 'chunks', event: ChunkRowEvent }`; both inner events expose `type`, `seq`, `time`, and `data`. `ui-conversation` passes these entries to the assembler without opening a second history stream, converting records, or expanding packed members. One `ConversationNodeAssembler` per Session applies every registered Definition and publishes an independent source for each registered view target.
15
+
16
+ | Concept | Owner and purpose |
17
+ |---|---|
18
+ | Event Definition | A business package matches one standard event or packed Assistant run at a time, correlates it by stable `(kind, id)`, folds deterministic State, and optionally materializes one target node. |
19
+ | Context | The engine-owned ordered Matches and current State for one `(kind, id)`. A packed run occupies one update Match; update-only evidence may remain pending until pagination supplies its unique scalar start. |
20
+ | Location | The engine-owned Session, Turn, or Step coordinates derived from durable boundary events. Definitions may publish typed data onto one Turn or Step. |
21
+ | View Definition | A target package creates one incremental builder per Session and owns the final snapshot type for that target. |
22
+ | View | A Slot entry such as Chat or Trajectory reads only its target snapshot and renders target-owned nodes. |
23
+
24
+ Chat and Trajectory may recognize the same durable event family, but each keeps its own Definition State and final node payload. Shared target-neutral machinery is limited to identity routing, ordered replay, Location data, predecessor dependencies, and publication cadence.
25
+
26
+ ## Replayable event families
12
27
 
13
28
  Choose one stable business id before writing the Definition. Every event that contributes to the same Node must carry that id or derive it independently from its own payload; the client must never assign an update to “the latest unfinished” Context.
14
29
 
@@ -24,18 +39,21 @@ Use the producer-owned branded id type across the process boundary. Put the `Ses
24
39
 
25
40
  Incremental events are supported. Prefer whole-value checkpoints when the producer can emit them cheaply, because they remain useful when the start is outside the loaded window. Each delta must carry the stable id and produce deterministic State when replayed in ascending log `seq`; it must not depend on live-only memory. If the current history window contains only updates, the assembler keeps a pending Context and builds no State until an older page supplies the start. If the product must render before the start is loaded, a terminal or checkpoint event must carry enough whole fallback state for the Definition to build that result directly; do not recover it by scanning unrelated events.
26
41
 
27
- ## 2. Implement the Definition and typed Chat payload
42
+ Historical runs of consecutive same-block `assistant/chunk` deltas arrive as `chunkrow/text-chunks`, `chunkrow/reasoning-chunks`, or `chunkrow/tool-call-chunks`. Their top-level `seq` and `time` identify the first logical member, and their `data` retains each fragment and timestamp gap. These Client-only events can only be updates; `start()` receives a standard `SessionEvent`. A Definition that consumes Assistant deltas handles the relevant packed tags in the same `match()` and `update()` methods, while other Definitions return `null` without expanding the run.
43
+
44
+ ## Definition and typed Chat payload
28
45
 
29
46
  The example keeps the producer declarations and client contribution in one block so the complete relationship is visible. In a package family, keep the branded id and `SessionEventMap` declaration with the event producer, and keep the Definition, Chat data merge, and renderer in the client plugin.
30
47
 
31
48
  ```ts ignore-check
32
49
  import { createElement } from 'react'
50
+ import type { Context as ClientContext } from '@deepseek-ai/cordis'
33
51
  import type { Branded } from '@deepseek-ai/dsh-brand'
34
52
  import type {
35
- ClientContext, ConversationLocation, ConversationNodeContext,
53
+ ConversationLocation, ConversationNodeContext,
36
54
  ConversationNodeDefinition,
37
- } from '@deepseek-ai/dsh-client-runtime/client'
38
- import type { ChatNodeViewProps } from '@deepseek-ai/dsh-client-ui-conversation/client'
55
+ } from '@deepseek-ai/dsh-client-ui-conversation/client'
56
+ import type { ChatNodeViewProps } from '@deepseek-ai/dsh-client-ui-chat/client'
39
57
 
40
58
  type ReviewId = Branded<'ReviewId'>
41
59
 
@@ -90,13 +108,13 @@ interface ReviewChatData {
90
108
  readonly summary?: string
91
109
  }
92
110
 
93
- declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
111
+ declare module '@deepseek-ai/dsh-client-ui-chat/client' {
94
112
  interface ChatNodeDataMap {
95
113
  'review-job': ReviewChatData
96
114
  }
97
115
  }
98
116
 
99
- declare module '@deepseek-ai/dsh-client-runtime/client' {
117
+ declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
100
118
  interface ConversationStepDataMap {
101
119
  'review-job': ReviewChatData
102
120
  }
@@ -184,10 +202,10 @@ function ReviewNodeView({ node }: ChatNodeViewProps<'review-job'>) {
184
202
  return createElement('p', null, text)
185
203
  }
186
204
 
187
- export const inject = ['conversationEvents', 'slots']
205
+ export const inject = ['uiConversation', 'slots']
188
206
 
189
207
  export function apply(ctx: ClientContext): void {
190
- ctx.conversationEvents.register(reviewDefinition)
208
+ ctx.uiConversation.events.register(reviewDefinition)
191
209
  ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
192
210
  name: 'conversation.chat.node',
193
211
  key: 'review-job',
@@ -195,33 +213,33 @@ export function apply(ctx: ClientContext): void {
195
213
  }
196
214
  ```
197
215
 
198
- `match(event)` is an identity extractor, not a fold: it receives only the current event and returns the Definition-local id and lifecycle role. After a match, the assembler locates the Context by `(kind, id)` and calls `start` once or `update` with the current State. Both functions return the State that the engine adopts; returning a new immutable value is preferred, but a function that mutates and returns the same object has the same adoption semantics.
216
+ `match(event)` is an identity extractor, not a fold: it receives only the current `SessionEventLike` and returns the Definition-local id and lifecycle role. After a match, the assembler locates the Context by `(kind, id)` and calls `start` once for a standard event or `update` for a standard or packed event. Both functions return the State that the engine adopts; returning a new immutable value is preferred, but a function that mutates and returns the same object has the same adoption semantics.
199
217
 
200
218
  `buildLocationData(context, scope)` optionally publishes Definition-owned data onto an engine-owned Turn or Step. Use declaration merging to give each key a precise value type. Another Node in the same Location can consume that value through its constrained slot hook, such as `useTurnData(key)`, without receiving the Session or scanning `snapshot.chat.nodes`.
201
219
 
202
220
  `target` and `buildViewNode(context)` declare one target-owned rendering contribution and must appear together. Preserve `context.key` as the React-facing identity, choose `anchorSeq` from durable ordering evidence, and return only renderer-ready data. Once a target Node has been published, keep returning the same key; use `visibility: 'hidden'` when it must temporarily leave the visible flow rather than withdrawing it with `null`.
203
221
 
204
- ## 3. Query an earlier business Context only at start
222
+ ## Predecessor reads
205
223
 
206
224
  Some Definitions need the latest earlier State of another business kind. `start` receives a `ConversationContextReader`; call `reader.previous<State>(kind)` there instead of accepting a Context collection or scanning events. The reader returns the nearest started Context before the current start `seq` as read-only data.
207
225
 
208
226
  The assembler records that dependency. If an older prepend later supplies a nearer predecessor, closes a previously unknown window gap, or revises the predecessor State, it reruns the dependent Context from `start` and replays its updates in ascending `seq`. The queried Definition remains responsible for writing useful State; the reader exposes no business-specific query methods and grants no mutation authority over another Context.
209
227
 
210
- ## 4. Understand the three ingestion paths
228
+ ## Window update paths
211
229
 
212
- History may be requested from the tail backward one page at a time, but every accepted page is normalized into ascending `seq` before State replay.
230
+ History may be requested from the tail backward one page at a time. The Session journal validates non-overlapping logical sequence ranges first; the Assembler then orders accepted inputs by their first `seq` before State replay.
213
231
 
214
232
  | Path | Engine work | Definition-visible behavior |
215
233
  |---|---|---|
216
- | Replace on open, resync, or gap repair | Rebuild the loaded window, match every event once per Definition, then replay each started Context | `start`, followed by its updates in ascending `seq`; pending update-only Contexts remain without State |
217
- | Prepend one older page | Match only fresh older events, merge them into Contexts by `(kind, id)`, preserve existing keyed nodes, and replay only affected Contexts and dependencies | A newly found start activates its collected updates; a changed Location or predecessor may rerun the Context |
218
- | Append one live event | Call each Definition's `match` once, look up the matched Context by key, and update only that Context | One `update` and one requested publication for a matching post-start event; no existing Context scan |
234
+ | Replace on open, resync, or gap repair | Rebuild the loaded window, match every standard event or packed run once per Definition, then replay each started Context | `start`, followed by its updates in ascending logical `seq`; pending update-only Contexts remain without State |
235
+ | Prepend one older page | Match only fresh older inputs, merge them into Contexts by `(kind, id)`, preserve existing keyed nodes, and replay only affected Contexts and dependencies | A newly found scalar start activates its collected scalar and packed updates; a changed Location or predecessor may rerun the Context |
236
+ | Append one live event | Call each Definition's `match` once, look up the matched Context by key, and update only that Context | One scalar `update` and one requested publication for a matching post-start event; no existing Context scan |
219
237
 
220
- With `D` registered Definitions, one incoming event performs `D` current-event matches and constant-time Context-key lookup after a match. Definition code must preserve that property: do not traverse the complete event window, every Context, `context.matches`, or the rendered Node collection on the normal append path. Use State for accumulated facts, Location data for same-Turn/Step sharing, and `reader.previous()` for indexed predecessor dependencies.
238
+ With `D` registered Definitions, one incoming scalar event or packed run performs `D` current-input matches and constant-time Context-key lookup after a match. Definition code must preserve that property: do not traverse the complete event window, every Context, `context.matches`, or the rendered Node collection on the normal append path. Use State for accumulated facts, Location data for same-Turn/Step sharing, and `reader.previous()` for indexed predecessor dependencies.
221
239
 
222
- `publication` controls when changed State is materialized. Use `immediate` for structural or terminal changes, `animation-frame` for high-frequency visible deltas, and `none` when the State change feeds only a later publication. The engine still applies every update in log order; cadence only coalesces view publication.
240
+ `publication` controls when changed State is materialized. Use `immediate` for structural or terminal changes, `animation-frame` for high-frequency visible deltas, and `none` when the State change feeds only a later publication. The engine applies every scalar update in log order and every packed run in one batch update; cadence only coalesces view publication.
223
241
 
224
- ## 5. Verify replay, pagination, and rendering
242
+ ## Verification obligations
225
243
 
226
244
  Add focused tests that establish these outcomes:
227
245
 
@@ -231,5 +249,6 @@ Add focused tests that establish these outcomes:
231
249
  4. Prepending an older page adds earlier rows without replacing existing keyed Node values whose data did not change.
232
250
  5. Repeated visible deltas preserve `context.key` and publish at most once per animation frame when requested.
233
251
  6. The keyed renderer consumes `node.data` and constrained Location hooks only; it does not scan the Session event window, Contexts, or Chat Nodes.
252
+ 7. Scalar and packed Assistant history produce the same final State, timing boundaries, and target snapshot, while one packed run remains one Match through replace, prepend, Location replay, and registry rebuild.
234
253
 
235
- Use [`packages/client/ui-conversation/src/client/conversation-nodes/assistant.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-conversation/src/client/conversation-nodes/assistant.ts) for streaming and interruption, [`inbox.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-conversation/src/client/conversation-nodes/inbox.ts) plus [`message.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-conversation/src/client/conversation-nodes/message.ts) for predecessor queries, and [`packages/client/ui-deliverables`](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/ui-deliverables) for a Definition that publishes Turn data without creating its own Node.
254
+ Use [`packages/client/ui-chat/src/client/conversation-nodes/assistant.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-chat/src/client/conversation-nodes/assistant.ts) for streaming and interruption, [`inbox.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-chat/src/client/conversation-nodes/inbox.ts) plus [`message.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-chat/src/client/conversation-nodes/message.ts) for predecessor queries, and [`packages/client/ui-deliverables`](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/ui-deliverables) for a Definition that publishes Turn data without creating its own Node.
@@ -60,9 +60,9 @@ interface AgentHandle {
60
60
  Source: [`packages/core/agent/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/agent/src/types.ts)
61
61
 
62
62
  ```ts type-equiv
63
- /** Public live-agent handle. */
63
+ /** Public live-agent handle; the runtime face augments its live capabilities. */
64
64
  interface Agent {
65
- /** The single identity shared with {@link session}. */
65
+ /** Session-backed Agent identity. */
66
66
  readonly id: SessionId
67
67
  /** The provider route and model this agent's requests use. */
68
68
  readonly options: AgentOptions
@@ -164,12 +164,14 @@ interface AgentOptions {
164
164
  provider?: string
165
165
  /** Model id interpreted by the selected provider adapter. */
166
166
  model?: string
167
+ /** Adapter-owned reasoning effort for the selected provider/model route. */
168
+ reasoningEffort?: ReasoningEffortId
167
169
  /** Maximum output tokens for each conversation-model request. */
168
170
  maxTokens?: number
169
171
  }
170
172
  ```
171
173
 
172
- Dispatch requires `provider` and `model` after `agent/request`. When present, `maxTokens` must be a positive safe integer and caps every conversation-model request; omission allows the exact-model adapter default to materialize before the request header, or otherwise leaves provider behavior unchanged. An agent-scoped `deployment:persona` prompt section may shadow the global default persona.
174
+ Dispatch requires `provider` and `model` after `agent/request`. An explicit `reasoningEffort` seeds the first request on that route; exact-model resolution validates it, while omission allows the adapter default to materialize. When present, `maxTokens` must be a positive safe integer and caps every conversation-model request; omission allows the exact-model adapter default to materialize before the request header, or otherwise leaves provider behavior unchanged. An agent-scoped `deployment:persona` prompt section may shadow the global default persona.
173
175
 
174
176
  The inbox is the delivery vocabulary — two ordered pending-message lists the agent owns as a durable projection:
175
177
 
@@ -203,7 +205,7 @@ type AgentCancelCause =
203
205
  | { readonly kind: 'disposed' }
204
206
  ```
205
207
 
206
- The cause is a TypeScript-enforced same-process input. An active cancellation holder copies it into the runtime-only `AbortSignal.reason`; a signal grants cooperating listeners no classification authority. Durable `turn/end` retains the coarse `{ kind: 'aborted' }` outcome; recording who requested cancellation would require a separate durable event rather than overloading the terminal result.
208
+ The cause is a TypeScript-enforced same-process input. An active cancellation holder copies it into the runtime-only `AbortSignal.reason`; a signal grants cooperating listeners no classification authority. Durable `turn/end` records the outcome as `{ kind: 'aborted', reason: TurnEndCancelCause }`, so the cancel cause lands in the terminal result.
207
209
 
208
210
  The [event taxonomy](../index.md#events) owns the `agent/*` lifecycle, checkpoint, and waterfall contracts. Turn and step boundaries are durable session events rather than agent emits.
209
211
 
@@ -225,7 +227,12 @@ It returns a `PreStepDecision`. Reject opens no step. Enter supplies the complet
225
227
  /** Whether and with which messages the loop enters a proposed step. */
226
228
  type PreStepDecision =
227
229
  | { kind: 'reject' }
228
- | { kind: 'enter'; messages: UserMessage[] }
230
+ | {
231
+ kind: 'enter'
232
+ messages: UserMessage[]
233
+ /** Start a distinct model-message series before this step's admitted messages. */
234
+ startsRequestSeries?: true
235
+ }
229
236
  ```
230
237
 
231
238
  `agent/request-error` runs after a failed model step closes and before its turn closes. Listeners can repair durable state or await policy work while the failed turn's signal is still live. A handling listener returns `{ kind: 'retry' }` without calling `next()`; the default `undefined` leaves the failure terminal.
@@ -235,7 +242,7 @@ type PreStepDecision =
235
242
  type RequestErrorAction = { kind: 'retry' } | undefined
236
243
  ```
237
244
 
238
- `agent/pre-step` is the only serial listener chain before request derivation. `agent/turn-stopping` runs when a turn has no tool or steering continuation, before one final steering drain.
245
+ `agent/pre-step` is the only waterfall listener chain before request derivation. `agent/turn-stopping` runs when a turn has no tool or steering continuation, before one final steering drain.
239
246
 
240
247
  `agent/session-start` carries a `SessionStartSource` (why the session lifecycle began; a bridge keys its SessionStart matcher on it):
241
248
 
@@ -248,7 +255,7 @@ type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact'
248
255
 
249
256
  A `Session` is an **append-only log** of typed `SessionEvent`s — the single source of truth. The LLM message history is *derived* from the log (`deriveMessages()`), not stored separately. Every entry carries a monotonic `seq`, a `time`, and a `type`-discriminated `data` payload; surface variants may also list cited earlier events in `sourceEventSeqs` and carry a `surfaceOp`.
250
257
 
251
- The `SessionEvent` envelope's exact conditional fields, the twelve event variants (`turn/start`, `turn/end`, `step/start`, `step/end`, `user/message`, `assistant/chunk`, `assistant/message`, `tool/call`, `tool/result`, `steering/message`, `todo/write`, `request/header`), the `deriveMessages()` projection rules, the `TurnTrigger`/`TurnEndReason` reasons, and the execution-enclosure and standalone-event rules are on **[session.md](./session.md)**. How the log is made durable — the `SessionPersistence` interface, JSONL/SQLite backends, the `session/flush` checkpoint, crash recovery, and `SessionHeader` — is on **[persistence.md](./persistence.md)**.
258
+ The `SessionEvent` envelope's exact conditional fields, the twelve core event variants (`turn/start`, `turn/end`, `step/start`, `step/end`, `user/message`, `assistant/chunk`, `assistant/message`, `tool/call`, `tool/result`, `request/header`, `request/context`, `session/end-seed`), the `deriveMessages()` projection rules, the `TurnEndReason` reasons, and the execution-enclosure and standalone-event rules are on **[session.md](./session.md)**. How the log is made durable — the `SessionPersistence` interface, JSONL/SQLite backends, the `session/flush` checkpoint, crash recovery, and `SessionHeader` — is on **[persistence.md](./persistence.md)**.
252
259
 
253
260
  ## `ToolDefinition`
254
261
 
@@ -283,14 +290,13 @@ declare module '@deepseek-ai/dsh-llm' {
283
290
  }
284
291
  ```
285
292
 
286
- Six canonical maps use this pattern; a plugin author extends these:
293
+ Five canonical maps use this pattern; a plugin author extends these:
287
294
 
288
295
  | Map | Package | Derives | Catalog |
289
296
  |---|---|---|---|
290
297
  | `ContentBlockMap` | dsh-llm | `ContentBlock` | [llm-streaming.md](./llm-streaming.md#content-blocks-and-messages) |
291
298
  | `MessageSourceMap` | dsh-llm | `MessageSource` | [llm-streaming.md](./llm-streaming.md#content-blocks-and-messages) |
292
299
  | `FinishReasonMap` | dsh-llm | `FinishReason` | [llm-streaming.md](./llm-streaming.md#the-model-request-and-result) |
293
- | `TurnTriggerMap` | dsh-session | `TurnTrigger` | [session.md](./session.md) |
294
300
  | `TurnEndReasonMap` | dsh-session | `TurnEndReason` | [session.md](./session.md) |
295
301
  | `SessionEventMap` | dsh-session | `SessionEvent` | [session.md](./session.md) |
296
302
 
@@ -298,7 +304,7 @@ Two large discriminated unions are the ones consumers `switch` over most: **`Str
298
304
 
299
305
  ### Branded IDs
300
306
 
301
- IDs passed between packages are **branded** — structurally strings, but non-interchangeable at the type level (a `SessionId` cannot be passed where a `CallId` is expected). Construction goes through a per-type factory; comparison, logging, and JSON behave as ordinary strings.
307
+ IDs passed between packages are **branded** — structurally strings, but non-interchangeable at the type level (a `SessionId` cannot be passed where a `ToolCallId` is expected). Construction goes through a per-type factory; comparison, logging, and JSON behave as ordinary strings.
302
308
 
303
309
  The `Branded<B>` primitive lives in its own type-only package, [dsh-brand](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/util/brand) (no runtime code, no harness-package dependency), so any package can brand the ids it owns without depending on an unrelated capability package.
304
310
 
@@ -309,7 +315,7 @@ Source: [`packages/util/brand/src/index.ts`](https://github.com/deepseek-ai/deep
309
315
  type Branded<B extends string> = string & { readonly [BRAND]: B }
310
316
  ```
311
317
 
312
- The two core IDs are `CallId` (correlates a tool call with its result; dsh-llm) and `SessionId` (the shared live agent and durable session identity; dsh-session). Capability packages brand their own ids too, such as `JobId` in [jobs.md](./jobs.md).
318
+ The two core IDs are `ToolCallId` (correlates a tool call with its result; dsh-llm) and `SessionId` (the shared live agent and durable session identity; dsh-session). Capability packages brand their own ids too, such as `JobId` in [jobs.md](./jobs.md).
313
319
 
314
320
  <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
315
321
 
@@ -397,6 +403,16 @@ Discovery is unmemoized: `list()` and `resolve()` re-read the roots on every cal
397
403
  */
398
404
  async list(): Promise<AgentPreset[]>
399
405
 
406
+ /**
407
+ * The roster off the Host: {@link list} projected to path-free rows, with
408
+ * the default marked and this deployment's authoring capability beside it.
409
+ *
410
+ * Whether a client can open a preset's directory is the Host's own opener
411
+ * capability, not a roster property — a caller needing both joins them.
412
+ * @returns the rows and the authoring capability.
413
+ */
414
+ @Remote('list') async remoteExportList(): Promise<AgentPresetRoster>
415
+
400
416
  /**
401
417
  * Resolve one preset by id.
402
418
  *
@@ -471,6 +487,15 @@ composedPreset(agentCtx: Context): string | undefined
471
487
  */
472
488
  async read(id: string): Promise<string>
473
489
 
490
+ /**
491
+ * One preset's composition text with the roster row it belongs to.
492
+ * @param agentPreset - the preset id.
493
+ * @returns the composition beside its trust and published metadata.
494
+ * @throws {TypertRemoteFailure} `bad-request` for an empty id, or
495
+ * `agent-preset-not-found` when no configured root supplies it.
496
+ */
497
+ @Remote('read') async readDocument(agentPreset: string): Promise<AgentPresetDocument>
498
+
474
499
  /**
475
500
  * Create a locally authored preset by copying an existing one whole.
476
501
  *
@@ -488,13 +513,34 @@ async read(id: string): Promise<string>
488
513
  */
489
514
  async copy(from: string, id: string, name?: string): Promise<void>
490
515
 
516
+ /**
517
+ * Copy one preset through the Remote API.
518
+ * @param from - the source preset id.
519
+ * @param id - the new preset id.
520
+ * @param name - the copy's optional display name.
521
+ * @returns once the copy is stored.
522
+ * @throws {TypertRemoteFailure} with the corresponding stable preset code
523
+ * and details when the copy is refused.
524
+ */
525
+ @Remote('copy') async remoteExportCopy(from: string, id: string, name?: string): Promise<void>
526
+
491
527
  /**
492
528
  * Delete a locally authored preset.
529
+ *
493
530
  * @param id - the preset id.
494
531
  * @throws when the preset is unknown or ships with the deployment.
495
532
  */
496
533
  async remove(id: string): Promise<void>
497
534
 
535
+ /**
536
+ * Delete one preset through the Remote API.
537
+ * @param id - the preset id.
538
+ * @returns once the preset is deleted.
539
+ * @throws {TypertRemoteFailure} with the corresponding stable preset code
540
+ * and details when deletion is refused.
541
+ */
542
+ @Remote('deletePreset') async remoteExportDelete(id: string): Promise<void>
543
+
498
544
  /**
499
545
  * One agent's instance of a service its preset mounted.
500
546
  *
@@ -527,7 +573,9 @@ serviceFor<K extends string & keyof Context>(agent: { ctx: Context }, name: K):
527
573
  * state to restore. The re-link runs through the binding this roster kept
528
574
  * from the agent's mount — dsh-scope's only re-link authority. An agent
529
575
  * that never composed one has nothing to re-link: the switch is then the
530
- * agent's first bind, exactly a mount.
576
+ * agent's first bind, exactly a mount. A committed re-link emits
577
+ * `tools/change` because changing the parent scope changes the Agent's
578
+ * resolved tool set without adding or removing registry entries.
531
579
  * @param agentCtx - the agent's scope context.
532
580
  * @param id - the preset to compose the agent from instead.
533
581
  * @returns the preset now installed.
@@ -535,6 +583,16 @@ serviceFor<K extends string & keyof Context>(agent: { ctx: Context }, name: K):
535
583
  */
536
584
  async recompose(agentCtx: Context, id: string): Promise<AgentPreset>
537
585
 
586
+ /**
587
+ * Compose a blank session's agent from a different preset and record it.
588
+ * @param agent - the session's live agent, resolved from the wire identity.
589
+ * @param agentPreset - the preset to compose the agent from instead.
590
+ * @returns the preset id that was recorded.
591
+ * @throws {TypertRemoteFailure} with `bad-request`, `agent-preset-locked`,
592
+ * `agent-preset-not-found`, or `agent-preset-invalid` when refused.
593
+ */
594
+ @Remote('select') async select(agent: Agent, agentPreset: string): Promise<string>
595
+
538
596
  /**
539
597
  * The standing scope key of one preset, for a host reader with no agent.
540
598
  *
@@ -37,13 +37,17 @@ interface ResolvedCredential {
37
37
  `describe(ref)` answers configuration surfaces without ever exposing a value: whether the reference resolves, from which layer, and whether `set` would currently succeed. The local provider reports a reference supplied by the live process environment as `writable: false` — a write would appear to succeed while resolution kept returning the shadowing value, so the seam rejects it and the UI can render the reference read-only up front.
38
38
 
39
39
  ```ts type-equiv
40
- /** Source and writability facts for one reference, safe for configuration UIs — never the value. */
40
+ /**
41
+ * Source and writability facts for one reference, safe for configuration UIs —
42
+ * never the value. The view has no slot a value could ride in, which is what
43
+ * lets the whole read half cross the Remote wire.
44
+ */
41
45
  interface CredentialInfo {
42
- /** Whether {@link CredentialProvider.resolve} would currently return a value. */
46
+ /** Whether resolving the reference would currently return a value. */
43
47
  configured: boolean
44
48
  /** Source layer currently supplying the value; absent while unconfigured. */
45
49
  source?: string
46
- /** Whether {@link CredentialProvider.set} would currently succeed for this reference. */
50
+ /** Whether the active provider can write this reference. */
47
51
  writable: boolean
48
52
  }
49
53
  ```
@@ -215,6 +219,42 @@ abstract deleteRecord(key: CredentialKey): Promise<void>
215
219
 
216
220
  Source: [`packages/credentials/credentials/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/credentials/credentials/src/index.ts)
217
221
 
222
+ <a id="ctxcredentialscontroller--credentialscontroller"></a>
223
+
224
+ ### `ctx.credentialsController` — `CredentialsController`
225
+
226
+ Host service backing the generated `ctx.remote.credentials` namespace. It carries every wire obligation the credential seam itself does not: the batch fan-out bound, the field-by-field view projection, the reference-grammar guard, and the refusal mapping. Secret values cross in one direction only — no method here returns one.
227
+
228
+ ```ts cordis-catalog
229
+ /**
230
+ * Describe several references for one configuration surface. Batched because
231
+ * a settings page describes every reference its rows name at once, and one
232
+ * round trip keeps those rows from settling separately.
233
+ * @param refs - reference names, at most {@link MAX_DESCRIBE_REFS}; a name outside the grammar rejects the whole call as `bad-request`.
234
+ * @returns one view per requested name, keyed by that name.
235
+ * @throws TypertRemoteFailure when the request is invalid or no credential provider is mounted.
236
+ */
237
+ @Remote async describe(refs: string[]): Promise<Record<string, CredentialInfo>>
238
+
239
+ /**
240
+ * Store one value from a configuration surface. The value crosses the wire in
241
+ * this direction only: no read path returns it.
242
+ * @param ref - reference name to store under.
243
+ * @param value - the non-empty secret value.
244
+ * @throws TypertRemoteFailure when the request is invalid, no provider is mounted, or the provider refuses the write.
245
+ */
246
+ @Remote async set(ref: string, value: string): Promise<void>
247
+
248
+ /**
249
+ * Remove one reference from a configuration surface.
250
+ * @param ref - reference name to remove.
251
+ * @throws TypertRemoteFailure when the request is invalid, no provider is mounted, or the provider refuses the write.
252
+ */
253
+ @Remote async unset(ref: string): Promise<void>
254
+ ```
255
+
256
+ Source: [`packages/api/settings-controller/src/credentials.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/settings-controller/src/credentials.ts)
257
+
218
258
  <a id="authorization-events"></a>
219
259
 
220
260
  ### `authorization/*` events
@@ -15,7 +15,7 @@ Provider source: [`packages/fs/fs/src/types.ts`](https://github.com/deepseek-ai/
15
15
 
16
16
  Every operation resolves a user-supplied path to an opaque backend target first. Consumers may display `displayPath`, but must not parse `targetKey` (a branded opaque id) or assume it is a local absolute path.
17
17
 
18
- Consumers that share the filesystem's execution world obtain cross-capability coordinates through the provider instead of interpreting that identity: `processPath(target)` returns the canonical absolute path a subprocess can open, `fileUrl(target)` returns its provider-platform `file:` URI, and `contains(parent, child)` tests canonical identity or descendant containment.
18
+ Consumers that share the filesystem's execution world obtain cross-capability coordinates through the provider instead of interpreting that identity: `processPath(target)` returns the canonical absolute path a subprocess can open, `processPathFromHostPath(hostPath)` maps an absolute harness-host file only when that execution world shares it, `fileUrl(target)` returns its provider-platform `file:` URI, and `contains(parent, child)` tests canonical identity or descendant containment.
19
19
 
20
20
  ```ts type-equiv
21
21
  /**
@@ -278,7 +278,7 @@ type FsErrorCode =
278
278
 
279
279
  ## The service and the plugin
280
280
 
281
- `FileSystem` (`ctx.fs`, abstract) owns the provider primitives: `resolve`, `processPath`, `fileUrl`, `contains`, `stat`, `lstat`, `readText`, `streamText`, `readBytes`, `listDir`, `writeText`, and `editText`. `dsh-fs-observation-policy` registers **no service** — it is a plugin that adds policy through the `fs/*` event gate: it decides the write/edit intent waterfalls from unseen/absent/present state and records `FsObservation` values. The executor is `dsh-tool-fs`: it reads/writes/edits through `ctx.fs`, dispatches the waterfalls, and emits the recording event. The generated [`ctx.fs` section](#ctxfs--filesystem-abstract-seam) below shows the exact signatures.
281
+ `FileSystem` (`ctx.fs`, abstract) owns the provider primitives: `resolve`, `processPath`, `processPathFromHostPath`, `fileUrl`, `contains`, `stat`, `lstat`, `readText`, `streamText`, `readBytes`, `listDir`, `writeText`, and `editText`. `dsh-fs-observation-policy` registers **no service** — it is a plugin that adds policy through the `fs/*` event gate: it decides the write/edit intent waterfalls from unseen/absent/present state and records `FsObservation` values. The executor is `dsh-tool-fs`: it reads/writes/edits through `ctx.fs`, dispatches the waterfalls, and emits the recording event. The generated [`ctx.fs` section](#ctxfs--filesystem-abstract-seam) below shows the exact signatures.
282
282
 
283
283
  <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
284
284
 
@@ -316,6 +316,16 @@ abstract resolve(path: string, opts?: { cwd?: string; signal?: AbortSignal }): P
316
316
  */
317
317
  abstract processPath(target: FsTarget): string
318
318
 
319
+ /**
320
+ * Map an absolute path from the harness host into this filesystem's
321
+ * execution world when both paths identify the same file. The base provider
322
+ * exposes no mapping; host-backed or explicitly shared backends override it.
323
+ * @param hostPath - absolute path in the harness host filesystem.
324
+ * @returns the process path for the same file, or undefined when this
325
+ * execution world cannot read that host file.
326
+ */
327
+ processPathFromHostPath(hostPath: string): string | undefined
328
+
319
329
  /**
320
330
  * Return the canonical `file:` URI for a target in this filesystem's
321
331
  * execution world. Backends own URI encoding because the host platform may
@@ -16,8 +16,9 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures
16
16
  | [typert.md](./typert.md) | Remote invocation descriptors, lookup/Context declarations, Typert registries, and the Host Gateway/Client API boundaries |
17
17
  | [goal.md](./goal.md) | persisted goal identity, lifecycle snapshots, activation, change records, and round attribution |
18
18
  | [schedule.md](./schedule.md) | Session-local reminder records, durable transitions, active views, and ordinary-conversation delivery |
19
+ | [todo.md](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/subsystems/todo.md) | the todo package's whole-list item type, durable event ownership, projection, and open-turn invariant |
19
20
  | [commands.md](./commands.md) | the human-command registry service: definitions, adapter discovery, direct invocation, results, and parsing views |
20
- | [session.md](./session.md) | the full `SessionEventMap` variant catalog, `TurnTrigger`/`TurnEndReason`, `deriveMessages()`, execution enclosure, and standalone events |
21
+ | [session.md](./session.md) | the full `SessionEventMap` variant catalog, `TurnEndReason`, `deriveMessages()`, execution enclosure, and standalone events |
21
22
  | [persistence.md](./persistence.md) | the durability seam: `SessionPersistence`, JSONL + SQLite backends, `session/flush`, crash recovery, `SessionHeader` |
22
23
  | [settings.md](./settings.md) | the user-settings seam: `SettingsNamespace` registration, layered resolution (defaults → composition `base` → user document), owner scopes, hot commits |
23
24
  | [credentials.md](./credentials.md) | the credential seam: `CredentialRef` references (never values) in configuration, per-operation resolution, UI-safe `CredentialInfo`, provider source layers |
@@ -50,9 +51,13 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures
50
51
  | [plan.md](./plan.md) | plan mode: the log-only `plan/mode` state, pending-selection flush, `PlanModeConfig`, the `exit_plan_mode` review arc |
51
52
  | [invariants.md](./invariants.md) | the runtime-invariant registry: selection `Config`, `InvariantInstaller`/`InvariantFailure`, the empty-companion contract |
52
53
  | [web-server.md](./web-server.md) | the HTTP carrier: `WebRouteKind`/`WebRoute`, match order, the claimable fallback seat, index taps |
54
+ | [webhook.md](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/subsystems/webhook.md) | authenticated provider deliveries, arbitrary programmatic rules, and fire-and-forget Workspace Session creation |
53
55
  | [storage.md](./storage.md) | the storage subsystem: the backend contract (`StorageBackend`), `StorageForms`, `DomainSpec`/`Domain`, `domain/changed` |
54
56
  | [workspace.md](./workspace.md) | the workspace registry: `Workspace`/`WorkspaceId`, registration and resolution, the session `cwd` relationship |
57
+ | [web-client.md](./web-client.md) | the browser architecture: boot, Remote communication, paired Client models, UI adapters, Conversation assembly, Slots, and reconnect semantics |
55
58
  | [client-modules.md](./client-modules.md) | the web plugin table: `dsh.client` declarations, `WebBootGraph` wire composition, the bundle route and index tap |
59
+ | [slots.md](./slots.md) | typed Web UI composition: declaration ownership, cardinality and scope, framework and feature injection, props derivation, and the shipped hierarchy |
60
+ | [conversation.md](./conversation.md) | target-neutral Session-event assembly: Context identity, Location data, replay paths, view builders, and target-owned render nodes |
56
61
  | [session-projection.md](./session-projection.md) | the projection seam: `SessionProjectionMap`, the pure `ProjectionDefinition` unit, `ProjectionSnapshot`'s consistent cut, the change feed |
57
62
  | [session-telemetry.md](./session-telemetry.md) | the outbound session-reporting capability seam: `SessionTelemetryRecord`/`SessionTelemetrySeverity`, the `SessionTelemetrySink` contract, and the `session-telemetry/record` redact waterfall |
58
63
 
@@ -157,7 +157,7 @@ interface JobRead {
157
157
 
158
158
  ## Service behavior
159
159
 
160
- The abstract [`JobRegistry`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/jobs/jobs/src/index.ts) Service Definition specifies atomic `start`, caller-scoped `get` and `list`, `read`, `kill`, bounded `wait`, failure-isolated `onJobDone` and `onJobsChanged` listeners, and when `attachController` becomes available; [`LocalJobRegistry`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/jobs/jobs-local/src/index.ts) is the process-local Service Provider. Authorization compares owner sessions; owner cleanup and admission use the exact registered `Agent` instance. The local provider's positive-safe-integer `maxConcurrentJobsPerOwner` config defaults to `10` and counts `running` plus `stopping` records per exact owner, with one shared bucket for unowned jobs; terminal producer settlement releases capacity. See [`dsh-jobs`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/jobs/jobs/README.md) for the Service Definition contract, [`dsh-jobs-local`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/jobs/jobs-local/README.md) for the registry lifecycle and admission policy, and [`dsh-tool-jobs`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/jobs/tool-jobs/README.md) for the model-facing Consumer.
160
+ The abstract [`JobRegistry`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/jobs/jobs/src/index.ts) Service Definition specifies atomic `start`, caller-scoped `get` and `list`, `read`, `kill`, bounded `wait`, failure-isolated `onJobDone` and `onJobsChanged` listeners, and `attachController`; [`LocalJobRegistry`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/jobs/jobs-local/src/index.ts) is the process-local Service Provider. Authorization compares owner sessions; owner cleanup and admission use the exact registered `Agent` instance. The local provider's positive-safe-integer `maxConcurrentJobsPerOwner` config defaults to `10` and counts `running` plus `stopping` records per exact owner, with one shared bucket for unowned jobs; terminal producer settlement releases capacity. See [`dsh-jobs`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/jobs/jobs/README.md) for the Service Definition contract, [`dsh-jobs-local`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/jobs/jobs-local/README.md) for the registry lifecycle and admission policy, and [`dsh-tool-jobs`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/jobs/tool-jobs/README.md) for the model-facing Consumer.
161
161
 
162
162
  <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
163
163