dsh-plugin-dev-kb 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.en.md +14 -8
  3. package/README.md +14 -8
  4. package/docs/banner.svg +25 -0
  5. package/kb/INDEX.md +6 -2
  6. package/kb/README.md +4 -4
  7. package/kb/extra/AGENTS.md +2 -2
  8. package/kb/extra/cookbook/adding-a-session-format-version.md +109 -0
  9. package/kb/extra/cookbook/adding-a-session-format-version.zh.md +109 -0
  10. package/kb/extra/cookbook/adding-a-vendored-package.md +1 -1
  11. package/kb/extra/cookbook/adding-a-vendored-package.zh.md +1 -1
  12. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.md +1 -1
  13. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.zh.md +1 -1
  14. package/kb/extra/deepseek-llm-api-wire-extensions.md +2 -2
  15. package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +2 -2
  16. package/kb/extra/development.md +13 -3
  17. package/kb/extra/development.zh.md +13 -3
  18. package/kb/extra/event-producer-consumer.md +33 -31
  19. package/kb/extra/event-producer-consumer.zh.md +34 -32
  20. package/kb/extra/i18n/README.md +2 -1
  21. package/kb/extra/i18n/README.zh.md +3 -2
  22. package/kb/extra/i18n/translation-prompt.md +1 -1
  23. package/kb/extra/module-graph.md +62 -24
  24. package/kb/extra/module-graph.zh.md +62 -24
  25. package/kb/extra/rescope.md +1 -1
  26. package/kb/extra/rescope.zh.md +1 -1
  27. package/kb/extra/session-format-status.md +47 -0
  28. package/kb/extra/session-format-status.zh.md +47 -0
  29. package/kb/extra/subsystems/agent-team.md +1 -1
  30. package/kb/extra/subsystems/agent-team.zh.md +1 -1
  31. package/kb/extra/subsystems/feedback.md +164 -30
  32. package/kb/extra/subsystems/feedback.zh.md +164 -30
  33. package/kb/extra/testing.md +4 -3
  34. package/kb/extra/testing.zh.md +4 -3
  35. package/kb/extra/web-styling.md +1 -0
  36. package/kb/extra/web-styling.zh.md +1 -0
  37. package/kb/meta/search-index.json +151 -103
  38. package/kb/meta/site-pages.txt +188 -184
  39. package/kb/meta/source.json +5 -5
  40. package/kb/meta/topics.md +2 -2
  41. package/kb/site/develop/basic/publish.md +1 -1
  42. package/kb/site/en/develop/basic/publish.md +1 -1
  43. package/kb/site/en/guide/providers.md +1 -1
  44. package/kb/site/en/guide/python-sdk.md +19 -3
  45. package/kb/site/en/reference/agent-lifecycle.md +12 -5
  46. package/kb/site/en/reference/capability-seams.md +10 -3
  47. package/kb/site/en/reference/config-catalog.md +173 -42
  48. package/kb/site/en/reference/cookbook/adding-a-tool.md +1 -1
  49. package/kb/site/en/reference/cookbook/extension-cookbook.md +1 -1
  50. package/kb/site/en/reference/index.md +18 -8
  51. package/kb/site/en/reference/persistence-catalog.md +121 -48
  52. package/kb/site/en/reference/subsystems/approval.md +1 -1
  53. package/kb/site/en/reference/subsystems/client-modules.md +9 -0
  54. package/kb/site/en/reference/subsystems/client-resources.md +94 -0
  55. package/kb/site/en/reference/subsystems/compaction.md +1 -1
  56. package/kb/site/en/reference/subsystems/core.md +79 -16
  57. package/kb/site/en/reference/subsystems/filesystem.md +15 -0
  58. package/kb/site/en/reference/subsystems/goal.md +37 -1
  59. package/kb/site/en/reference/subsystems/index.md +2 -0
  60. package/kb/site/en/reference/subsystems/invariants.md +1 -1
  61. package/kb/site/en/reference/subsystems/llm-streaming.md +20 -10
  62. package/kb/site/en/reference/subsystems/lsp.md +1 -1
  63. package/kb/site/en/reference/subsystems/persistence.md +36 -15
  64. package/kb/site/en/reference/subsystems/schedule.md +1 -1
  65. package/kb/site/en/reference/subsystems/scope.md +1 -1
  66. package/kb/site/en/reference/subsystems/session-reference.md +4 -0
  67. package/kb/site/en/reference/subsystems/session-telemetry.md +22 -6
  68. package/kb/site/en/reference/subsystems/session.md +64 -42
  69. package/kb/site/en/reference/subsystems/shell.md +9 -6
  70. package/kb/site/en/reference/subsystems/sidebar-right.md +151 -0
  71. package/kb/site/en/reference/subsystems/slots.md +41 -33
  72. package/kb/site/en/reference/subsystems/spill.md +13 -6
  73. package/kb/site/en/reference/subsystems/subagent.md +20 -13
  74. package/kb/site/en/reference/subsystems/subprocess.md +25 -26
  75. package/kb/site/en/reference/subsystems/system-prompt.md +3 -1
  76. package/kb/site/en/reference/subsystems/token-meter.md +2 -1
  77. package/kb/site/en/reference/subsystems/tools.md +5 -5
  78. package/kb/site/en/reference/subsystems/workspace.md +80 -1
  79. package/kb/site/en/reference/tool-catalog.md +46 -2
  80. package/kb/site/en/reference/tool-execution-pipeline.md +2 -2
  81. package/kb/site/guide/providers.md +1 -1
  82. package/kb/site/guide/python-sdk.md +19 -3
  83. package/kb/site/reference/agent-lifecycle.md +12 -5
  84. package/kb/site/reference/capability-seams.md +10 -3
  85. package/kb/site/reference/config-catalog.md +173 -42
  86. package/kb/site/reference/cookbook/adding-a-tool.md +1 -1
  87. package/kb/site/reference/cookbook/extension-cookbook.md +1 -1
  88. package/kb/site/reference/index.md +18 -8
  89. package/kb/site/reference/persistence-catalog.md +127 -54
  90. package/kb/site/reference/subsystems/approval.md +1 -1
  91. package/kb/site/reference/subsystems/client-modules.md +9 -0
  92. package/kb/site/reference/subsystems/client-resources.md +94 -0
  93. package/kb/site/reference/subsystems/compaction.md +1 -1
  94. package/kb/site/reference/subsystems/core.md +79 -16
  95. package/kb/site/reference/subsystems/filesystem.md +15 -0
  96. package/kb/site/reference/subsystems/goal.md +37 -1
  97. package/kb/site/reference/subsystems/index.md +14 -12
  98. package/kb/site/reference/subsystems/invariants.md +1 -1
  99. package/kb/site/reference/subsystems/llm-streaming.md +20 -10
  100. package/kb/site/reference/subsystems/lsp.md +1 -1
  101. package/kb/site/reference/subsystems/persistence.md +36 -15
  102. package/kb/site/reference/subsystems/schedule.md +1 -1
  103. package/kb/site/reference/subsystems/scope.md +1 -1
  104. package/kb/site/reference/subsystems/session-reference.md +4 -0
  105. package/kb/site/reference/subsystems/session-telemetry.md +22 -6
  106. package/kb/site/reference/subsystems/session.md +64 -42
  107. package/kb/site/reference/subsystems/shell.md +9 -6
  108. package/kb/site/reference/subsystems/sidebar-right.md +151 -0
  109. package/kb/site/reference/subsystems/slots.md +41 -33
  110. package/kb/site/reference/subsystems/spill.md +13 -6
  111. package/kb/site/reference/subsystems/subagent.md +20 -13
  112. package/kb/site/reference/subsystems/subprocess.md +25 -26
  113. package/kb/site/reference/subsystems/system-prompt.md +3 -1
  114. package/kb/site/reference/subsystems/token-meter.md +2 -1
  115. package/kb/site/reference/subsystems/tools.md +5 -5
  116. package/kb/site/reference/subsystems/workspace.md +80 -1
  117. package/kb/site/reference/tool-catalog.md +46 -2
  118. package/kb/site/reference/tool-execution-pipeline.md +2 -2
  119. package/lib/index.js +25 -44
  120. package/package.json +3 -2
  121. package/skills/dsh-plugin-dev-kb.md +3 -3
@@ -49,9 +49,9 @@ interface AgentHandle {
49
49
  }
50
50
  ```
51
51
 
52
- `CreateAgentOptions` carries the shared identity and everything a fresh agent needs before publication: session metadata (`meta` — validated `cwd`, fork lineage, the `isSeeded` marker, origin classification, delegation depth, and `agentPreset`), the exact fork cut in sibling field `inheritedEventCount`, an optional `seed` replay prefix, per-agent `AgentOptions`, a creation-only cancellation `signal`, and `setup`. `ResumeAgentOptions` is the persisted-identity counterpart: `resumeSessionId`, `agentOptions`, `signal`, and `setup`. The `setup` callback (`AgentSetup`) composes the agent's scoped world while both ids are still unpublished everything registered through `agentCtx` exists before `agent/created` and the first prompt assembly and may return a synchronous commit invoked immediately before publication; a setup rejection, commit throw, or owner disposal rolls the transaction back without publishing either id.
52
+ `CreateAgentOptions` carries the shared identity and everything a fresh agent needs before publication: an optional live `parentAgent`, session metadata (`meta` — validated `cwd`, fork lineage, the `isSeeded` marker, origin classification, delegation depth, and `agentPreset`), the exact fork cut in sibling field `inheritedEventCount`, an optional `seed` replay prefix, per-agent `AgentOptions`, a creation-only cancellation `signal`, and `setup`. `ResumeAgentOptions` is the persisted-identity counterpart: `resumeSessionId`, `parentAgent`, `agentOptions`, `signal`, and `setup`. The `setup` callback (`AgentSetup`) receives `(agentCtx, agent)` while both ids are still unpublished: the context owns scoped registrations, while the explicit Agent supplies the exact child Session without a reverse property on the Context. Everything registered through `agentCtx` exists before `agent/created` and the first prompt assembly. Setup may return a synchronous commit invoked immediately before publication; a setup rejection, commit throw, or owner disposal rolls the transaction back without publishing either id.
53
53
 
54
- `AgentFactory` is the creation interface behind the registry: the loop registers its factory via `ctx.agents.setFactory()`, so consumers use `ctx.agents` without depending on the concrete loop package. The exact `create`/`resume` signatures and rollback contracts are in the [generated section](#ctxagents--agentregistry) below.
54
+ `AgentFactory` is the creation interface behind the registry: the loop registers its factory via `ctx.agents.setFactory()`, so consumers use `ctx.agents` without depending on the concrete loop package. A runtime child creator sets `options.parentAgent`; the registry passes the options and caller Context to the factory without deriving one from the other. The exact `create`/`resume` signatures and rollback contracts are in the [generated section](#ctxagents--agentregistry) below.
55
55
 
56
56
  ## The agent handle
57
57
 
@@ -68,7 +68,7 @@ interface Agent {
68
68
  readonly options: AgentOptions
69
69
  /** The live session this agent drives; its log is the durable source of truth. */
70
70
  readonly session: Session
71
- /** The agent-owned projection of durable pending work. */
71
+ /** Agent-owned access to durable pending work. */
72
72
  readonly inbox: Inbox
73
73
  /** The current lifecycle state, mirrored on every `agent/status` transition. */
74
74
  readonly status: AgentStatus
@@ -209,16 +209,73 @@ interface AgentOptions {
209
209
  }
210
210
  ```
211
211
 
212
- 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.
212
+ 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-prefix` prompt section may shadow the global default persona.
213
213
 
214
214
  The inbox is the delivery vocabulary — two ordered pending-message lists the agent owns as a durable projection:
215
215
 
216
+ ```ts type-equiv
217
+ /** Agent-owned access to pending work; concrete storage belongs to the driver. */
218
+ interface Inbox {
219
+ /** Prompts awaiting individual turns. */
220
+ readonly nextTurn: readonly UserMessage[]
221
+ /** Input awaiting the next step boundary. */
222
+ readonly nextStep: readonly UserMessage[]
223
+
224
+ /** Durably cancel all pending input, clearing next-step before next-turn. */
225
+ clear(): void
226
+
227
+ /**
228
+ * Append one message to a pending list.
229
+ * @param target - pending list to extend.
230
+ * @param message - message to append.
231
+ */
232
+ append(target: InboxTarget, message: UserMessage): void
233
+
234
+ /**
235
+ * Prepend one message to a pending list.
236
+ * @param target - pending list to extend.
237
+ * @param message - message to prepend.
238
+ */
239
+ prepend(target: InboxTarget, message: UserMessage): void
240
+
241
+ /**
242
+ * Replace one pending message in place.
243
+ * @param messageId - identity of the pending message to replace.
244
+ * @param newMessage - replacement message.
245
+ * @returns whether the message was still pending.
246
+ */
247
+ replace(messageId: MessageId, newMessage: UserMessage): boolean
248
+
249
+ /**
250
+ * Remove one pending message.
251
+ * @param messageId - identity of the pending message to remove.
252
+ * @returns whether the message was still pending.
253
+ */
254
+ remove(messageId: MessageId): boolean
255
+
256
+ /**
257
+ * Apply standard splice semantics and durably record the normalized result.
258
+ * @param target - pending list to mutate.
259
+ * @param start - splice position.
260
+ * @param deleteCount - maximum number of messages to remove.
261
+ * @param inserted - messages to insert at the resolved position.
262
+ * @returns messages removed by the splice.
263
+ */
264
+ splice(
265
+ target: InboxTarget,
266
+ start: number,
267
+ deleteCount: number,
268
+ inserted: UserMessage[],
269
+ ): UserMessage[]
270
+ }
271
+ ```
272
+
216
273
  ```ts type-equiv
217
274
  /** One of the two ordered pending-message lists owned by an agent. */
218
275
  type InboxTarget = 'next-turn' | 'next-step'
219
276
  ```
220
277
 
221
- Every pending occurrence is its `UserMessage`; `MessageId` is the sole identity. `Inbox.append`, `prepend`, `replace`, `remove`, `clear`, `splice`, and `claim` record normalized durable `agent/inbox/spliced` mutations and reject duplicate pending ids. `replace(messageId, newMessage)` and `remove(messageId)` locate the pending message across both lists; replacement may change identity and emits the old message as discarded followed by the new message as inserted. Ordinary removals and `clear()` are cancellations. `claim(target)` removes the proposed step batch — all `next-step` input plus, at a turn boundary, one `next-turn` message — through pure deletion splices without emitting discarded notifications, and the loop separately emits per-message claimed notifications. Whole-queue consumers such as UI projections reconstruct `nextTurn` and `nextStep` from the durable splices, while consumers following one message use the exact `agent/inbox/inserted`, `claimed`, and `discarded` notifications.
278
+ Every pending occurrence is its `UserMessage`; `MessageId` is the sole identity. The structural `Inbox` methods record normalized durable `agent/inbox/spliced` mutations and reject duplicate pending ids. `replace(messageId, newMessage)` and `remove(messageId)` locate the pending message across both lists; replacement may change identity and emits the old message as discarded followed by the new message as inserted. Ordinary removals and `clear()` are cancellations. At a step boundary, dsh-agent-loop's package-internal `ReactLoopInbox` removes the proposed batch — all `next-step` input plus, at a turn boundary, one `next-turn` message — through pure deletion splices without discarded notifications, then emits per-message claimed notifications. Loop-only pending detection and claiming are not part of `Agent.inbox`. Each `ReactLoopInbox` constructor contributes the standard `inbox` projection from its agent scope; the registry shares that definition across agents by reference count, and its cell is the sole live state while the same fold serves cold consumers. The fold rejects unsafe or out-of-range splice coordinates and duplicate identities across both lists, identifying malformed durable history by event seq. Consumers following one message use the exact `agent/inbox/inserted`, `claimed`, and `discarded` notifications.
222
279
 
223
280
  Cancellation:
224
281
 
@@ -293,7 +350,7 @@ type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact'
293
350
 
294
351
  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`.
295
352
 
296
- The `SessionEvent` envelope's exact conditional fields, the twelve core event variants (`turn/start`, `turn/end`, `step/start`, `step/end`, `user/message`, `assistant/message`, `assistant/attempt`, `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 provider, `session/flush` checkpoint, crash recovery, and `SessionHeader` — is on **[persistence.md](./persistence.md)**.
353
+ The `SessionEvent` envelope's exact conditional fields, the thirteen core event variants (`turn/start`, `turn/end`, `step/start`, `step/end`, `user/message`, `system/message`, `assistant/message`, `assistant/attempt`, `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 provider, `session/flush` checkpoint, crash recovery, and `SessionHeader` — is on **[persistence.md](./persistence.md)**.
297
354
 
298
355
  ## `ToolDefinition`
299
356
 
@@ -409,7 +466,7 @@ async create(id: SessionId, options: AgentOptions = {}, meta: Pick<SessionHeader
409
466
  /**
410
467
  * Create an owned agent on a caller-supplied session id.
411
468
  * @param ownerCtx - caller context that structurally owns the lifecycle.
412
- * @param options - identities, session seed/metadata, loop options, setup, and cancellation.
469
+ * @param options - identities, optional live parent, session seed/metadata, loop options, setup, and cancellation.
413
470
  * @returns the published handle.
414
471
  */
415
472
  async createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<AgentHandle>
@@ -417,7 +474,7 @@ async createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<Agent
417
474
  /**
418
475
  * Resume an owned agent from the configured persistence service.
419
476
  * @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
420
- * @param options - persisted identity, loop options, setup, and cancellation.
477
+ * @param options - persisted identity, optional live parent, loop options, setup, and cancellation.
421
478
  * @returns the published handle.
422
479
  */
423
480
  async resume(ownerCtx: Context, options: ResumeAgentOptions): Promise<AgentHandle>
@@ -682,7 +739,8 @@ Initiator methods provide same-process causal attribution only. Ambient presence
682
739
  * Read the Agent that initiated the inherited asynchronous driver chain.
683
740
  * Use this optional form for logging, tracing, metrics, or host attribution
684
741
  * that also supports agentless calls. When a parent creates a child, setup
685
- * reports the causal parent while `agentCtx.agent` identifies the child.
742
+ * reports the causal parent while the setup callback's Agent parameter
743
+ * identifies the child.
686
744
  * @returns the inherited Agent, or `undefined` outside an initiator boundary
687
745
  * and inside an explicit clearing boundary.
688
746
  * @throws when this service instance has been disposed.
@@ -747,7 +805,7 @@ setFactory(factory: AgentFactory): () => void
747
805
  * agent): this constructs the agent and its session. Rejects if no factory is
748
806
  * registered or creation/setup fails. The resolved {@link AgentHandle} lets
749
807
  * the owner tear down exactly this agent.
750
- * @param options - shared identity, session seed/metadata, and agent options.
808
+ * @param options - shared identity, optional live parent, session seed/metadata, and agent options.
751
809
  * @returns the handle after setup, rollback-covered publication, and loop start complete.
752
810
  */
753
811
  async create(options: CreateAgentOptions): Promise<AgentHandle>
@@ -756,7 +814,7 @@ async create(options: CreateAgentOptions): Promise<AgentHandle>
756
814
  * Load a persisted session and resume an agent on it through the registered
757
815
  * factory. Rejects if no factory is registered; the factory rejects if
758
816
  * session persistence is not configured or persistence/setup fails.
759
- * @param options - persisted identity, configuration, and optional setup.
817
+ * @param options - persisted identity, optional live parent, configuration, and setup.
760
818
  * @returns the handle after setup, rollback-covered publication, and loop start complete.
761
819
  */
762
820
  async resume(options: ResumeAgentOptions): Promise<AgentHandle>
@@ -768,7 +826,8 @@ async resume(options: ResumeAgentOptions): Promise<AgentHandle>
768
826
  * (`scopeTarget(agent, agent)`): the subject is the agent in hand, so the
769
827
  * emits are scope-filtered regardless of which context invoked `register`
770
828
  * (calling through `agent.ctx` scopes EFFECTS; dispatch scoping always
771
- * requires passing the carrier). Returns the disposer.
829
+ * requires passing the carrier). The entry is a runtime root; factory-backed
830
+ * creation uses `options.parentAgent` for child ownership. Returns the disposer.
772
831
  * @param agent - the already-constructed agent to record in the store.
773
832
  * @returns the EXACT Cordis effect disposer (single-shot; a repeat call
774
833
  * returns undefined without awaiting an in-flight teardown). Exact
@@ -788,7 +847,7 @@ register(agent: Agent): () => void
788
847
  * returned detach closure into its pre-installed composite teardown before
789
848
  * calling {@link announce}. Ordinary callers use {@link register}.
790
849
  * @param agent - the prepared, unpublished agent.
791
- * @param owner - live agent whose scoped context created this agent, or
850
+ * @param owner - explicitly supplied live runtime owner, or
792
851
  * undefined for a top-level runtime root. This is runtime ownership, not
793
852
  * the resumed session's durable parent lineage.
794
853
  * @returns an idempotent closure that removes this exact entry and emits
@@ -1033,14 +1092,18 @@ Source: [`packages/core/agent/src/runtime-types.ts`](https://github.com/deepseek
1033
1092
 
1034
1093
  #### `agent/request` — waterfall
1035
1094
 
1036
- Replace the frozen call configuration. `await next()` yields the config the machine would use (agent options on the first request, the logged header afterwards); return a replacement to switch. Model-visible content must use logged channels; this waterfall cannot mutate messages.
1095
+ Replace the frozen call configuration. `await next()` yields the config the machine would use (agent options on the first request, the logged header afterwards); return a replacement to switch. On step admission, this runs after assembly and `step/start`, before the system prompt and accepted user batch are committed. Cancellation here or during subsequent `prepareCall()` resolution commits neither. The prepared call capability governs prompt admission. Model-visible content must use logged channels; this waterfall cannot mutate messages.
1037
1096
 
1038
1097
  ```ts cordis-catalog
1039
1098
  /**
1040
1099
  * Replace the frozen call configuration. `await next()` yields the config
1041
1100
  * the machine would use (agent options on the first request, the logged
1042
- * header afterwards); return a replacement to switch. Model-visible
1043
- * content must use logged channels; this waterfall cannot mutate messages.
1101
+ * header afterwards); return a replacement to switch. On step admission,
1102
+ * this runs after assembly and `step/start`, before the system prompt and
1103
+ * accepted user batch are committed. Cancellation here or during subsequent
1104
+ * `prepareCall()` resolution commits neither. The prepared call capability
1105
+ * governs prompt admission. Model-visible content must use logged channels;
1106
+ * this waterfall cannot mutate messages.
1044
1107
  * @param payload.agent - the agent making the model call.
1045
1108
  * @param payload.turn - the open turn number.
1046
1109
  * @param payload.step - the step whose request this is.
@@ -399,6 +399,21 @@ abstract streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterab
399
399
  */
400
400
  abstract readBytes(target: FsTarget, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>
401
401
 
402
+ /**
403
+ * Read one byte window of the regular file as raw bytes with no decoding or
404
+ * binary rejection: the bytes at `[offset, offset + length)`, shorter when
405
+ * the file ends inside the window and empty when `offset` lies at or past
406
+ * its end. The window is the bound here, not the file: a backend transfers
407
+ * at most `length` bytes of content beyond the prefix it skips to reach
408
+ * `offset` and never buffers the whole file, so the caller's cap on `length`
409
+ * is the guard against unbounded buffering.
410
+ * @param target - the resolved target to read.
411
+ * @param range - `offset`, the 0-based first byte, and `length`, the largest byte count; both non-negative integers.
412
+ * @param signal - aborts the read.
413
+ * @returns the window's bytes, at most `length` long.
414
+ */
415
+ abstract readByteRange(target: FsTarget, range: { offset: number; length: number }, signal?: AbortSignal): Promise<Uint8Array>
416
+
402
417
  /**
403
418
  * List direct children of a directory in stable name order. Returns resolved
404
419
  * child targets plus cheap metadata only; never reads file contents.
@@ -72,6 +72,25 @@ interface GoalView extends GoalSnapshot {
72
72
  }
73
73
  ```
74
74
 
75
+ The service also publishes process-local activation edges without changing durable state; clients consume this event for live status.
76
+
77
+ ```ts type-equiv
78
+ /** Live process-local activation update forwarded to UI clients. */
79
+ interface GoalActivationChanged {
80
+ /** Session whose live goal activation changed. */
81
+ readonly sessionId: SessionId
82
+ /** Current exact activation, absent when no goal is current. */
83
+ readonly goal?: {
84
+ /** Exact current goal identity. */
85
+ readonly id: GoalId
86
+ /** Exact current goal revision. */
87
+ readonly revision: number
88
+ /** Current process-local continuation state. */
89
+ readonly activation: GoalActivation
90
+ }
91
+ }
92
+ ```
93
+
75
94
  ## Durable changes
76
95
 
77
96
  Every mutation is a durable `goal/change` session event whose payload is either a complete post-mutation snapshot or a clear tombstone. The strict fold and persisted projection derive lifecycle state only from these events; inbox mutations do not affect goal state.
@@ -168,7 +187,7 @@ Goal service (`ctx.goals`) backed exclusively by the owning session log.
168
187
  * @returns a fresh view or `undefined` when no goal is current.
169
188
  * @throws {@link GoalError} when the agent is not the registry's live instance.
170
189
  */
171
- get(agent: Agent): GoalView | undefined
190
+ @Remote('get') get(agent: Agent): GoalView | undefined
172
191
 
173
192
  /**
174
193
  * Remove process-local continuation authority without changing durable goal
@@ -256,6 +275,23 @@ Source: [`packages/goal/goal/src/index.ts`](https://github.com/deepseek-ai/deeps
256
275
 
257
276
  ### `goal/*` events
258
277
 
278
+ <a id="goalactivation-changed--emit"></a>
279
+
280
+ #### `goal/activation-changed` — emit
281
+
282
+ Process-local goal activation changed for one session.
283
+
284
+ ```ts cordis-catalog
285
+ /**
286
+ * Process-local goal activation changed for one session.
287
+ * @mode emit
288
+ * @param payload - session id and the exact current goal activation, or no goal after a clear.
289
+ */
290
+ 'goal/activation-changed'(payload: GoalActivationChanged): void
291
+ ```
292
+
293
+ Source: [`packages/goal/goal/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/goal/goal/src/types.ts)
294
+
259
295
  <a id="goalchanged--emit"></a>
260
296
 
261
297
  #### `goal/changed` — emit
@@ -57,6 +57,8 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures
57
57
  | [web-client.md](./web-client.md) | the browser architecture: boot, Remote communication, paired Client models, UI adapters, Conversation assembly, Slots, and reconnect semantics |
58
58
  | [client-modules.md](./client-modules.md) | the web plugin table: `dsh.client` declarations, `WebBootGraph` wire composition, the bundle route and index tap |
59
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
+ | [client-resources.md](./client-resources.md) | the client resource model: `dsh-resource://<type>/…` addresses, protocol providers and `ResourceProtocolMap`, the `useResource` global hook and its states, pins and release |
61
+ | [sidebar-right.md](./sidebar-right.md) | the right Sidebar: resource and navigation addresses, tab-type registration and routing, the `ctx.sidebarRight` navigation service, the pane-tab slots and owner props, the resource model, and the Workspace Files service |
60
62
  | [conversation.md](./conversation.md) | target-neutral Session-event assembly: Context identity, Location data, replay paths, view builders, and target-owned render nodes |
61
63
  | [session-projection.md](./session-projection.md) | the projection seam: `SessionProjectionMap`, the pure `ProjectionDefinition` unit, `ProjectionSnapshot`'s consistent cut, the change feed |
62
64
  | [session-telemetry.md](./session-telemetry.md) | the outbound session-reporting capability seam: `SessionTelemetryRecord`/`SessionTelemetrySeverity`, the `SessionTelemetrySink` contract, and the `session-telemetry/record` redact waterfall |
@@ -5,7 +5,7 @@ outline: [2,3]
5
5
 
6
6
  # Runtime Invariants
7
7
 
8
- [dsh-invariants](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/runtime-diagnostics/invariants) is the configurable registry service (`ctx.invariants`) for package-owned runtime invariant checks. It is one support-group package, not a three-package capability seam, and not part of the agent-loop spine: the registry owns selection, name reservation, child-fiber lifecycle, and package-attributed failure, while every workspace package publishes a `./invariant` companion plugin that registers checks under its exact npm package name. What a check may assert — authoritative event streams or mutable data, never service or method presence — is the runtime-invariants convention in [AGENTS.md](https://github.com/deepseek-ai/deepseek-harness/blob/master/AGENTS.md#conventions); the registry design is owned by the [invariant-service Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.md).
8
+ [dsh-invariants](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/runtime-diagnostics/invariants) is the configurable registry service (`ctx.invariants`) for package-owned runtime invariant checks. It is one support-group package, not a three-package capability seam, and not part of the agent-loop spine: the registry owns selection, name reservation, child-fiber lifecycle, and package-attributed failure, while every workspace package publishes a `./invariant` companion plugin that registers checks under its exact npm package name. What a check may assert — authoritative event streams or mutable data, never service or method presence — is the runtime-invariants convention in [AGENTS.md](https://github.com/deepseek-ai/deepseek-harness/blob/master/AGENTS.md#conventions).
9
9
 
10
10
  Source: [`packages/runtime-diagnostics/invariants/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/runtime-diagnostics/invariants/src/index.ts)
11
11
 
@@ -228,7 +228,7 @@ type StreamChunk =
228
228
 
229
229
  `snapshot()` returns a detached immutable stream. `expandAssistantStream()` strictly checks record keys, member counts, indexes, timestamps, tool-call identity, and lossless JSON before recreating the exact timed chunk sequence. The Session log embeds this stream in `assistant/message` for a surface result or `assistant/attempt` for an attempt with no surface message.
230
230
 
231
- Process-local `agent/assistant-stream` frames carry live presentation. Durable replay, telemetry, token accounting, and historical UI assembly expand the embedded settlement instead of treating live frames as persisted facts.
231
+ Process-local `agent/assistant-stream` frames carry live presentation. Durable replay and restore validation still expand the embedded settlement; telemetry, token accounting, and Host folds read the compact records directly. Record-level readers (`assistantStreamFirstTokenTime`, `assistantStreamHasVisibleContent`, `assistantStreamHasVisibleText`, `lastAssistantStreamChunk`, `assistantStreamChunks`, `joinAssistantStreamText`, `assembleAssistantStream`, and the per-run `runFirstTokenTime` and `runFirstVisibleTime`) answer consumer questions in one pass over the records with early exit, so a large history costs O(records) per settlement instead of O(members) expansion ([fold decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-09-06-embedded-stream-record-readers.md)). `expandAssistantStream()` remains the validating path for records read at a durable boundary and for consumers that need every member.
232
232
 
233
233
  ## `LlmFailure`
234
234
 
@@ -298,7 +298,7 @@ Every adapter MUST obey these, and every consumer may rely on them:
298
298
  - **One adapter call is one provider attempt.** Adapters disable library retries. Agent-level recovery opens another durable numbered turn; direct `ctx.llm.stream()` callers remain single-attempt.
299
299
  - **Provider stalls are bounded at the transport.** Both shipping remote adapters expose positive finite `streamIdleTimeoutMs` with a five-minute default. The watchdog arms only while iterator `next()` is outstanding, uses one stable signal for the whole request, maps its own expiry to `TIMEOUT`, and keeps an earlier caller abort as `ABORTED`.
300
300
  - **Context overflow has one canonical code.** Both DeepSeek adapters classify explicit provider detail through `isContextWindowExceededError()` and surface `CONTEXT_WINDOW_EXCEEDED`, whether the failure arrives as a thrown HTTP `LlmError` or an in-band finish error. Consumers route on the code, never provider text.
301
- - **An empty completion is a retryable error, not a silent success.** Both adapters map a terminal `stop` finish that carried no content blocks to `finish {kind:'error'}` with the canonical `EMPTY_RESPONSE` code, and `dsh-llm-retry` retries it by default; see [empty model responses are retryable](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/bug-fix/2026-07-24-empty-model-response-is-retryable.md).
301
+ - **An empty completion is a retryable error, not a silent success.** Both adapters map a terminal `stop` finish that carried no content blocks to `finish {kind:'error'}` with the canonical `EMPTY_RESPONSE` code, and `dsh-llm-retry` retries it by default.
302
302
  - **Every provider HTTP request carries the app-attribution header.** Adapters send `attributionHeaders()` (below) - the `User-Agent` baseline - and prove it with a wire-level test.
303
303
  - **Replay state is adapter-owned; its split is shared.** A successful `finish` may carry a `ReplayEnvelope`: opaque response-level metadata plus optional per-block entries aligned with the emitted block sequence. The alignment is the harness's vocabulary — when assembly drops a block it drops the entry at the same position, so stored metadata always describes stored content. The loop stores the pruned envelope with the assembled assistant message. On a later request, `LlmRuntime` passes the state only when the historical provider and target provider are currently registered to the exact same adapter instance. That adapter validates the state and owns any cross-model or cross-provider conversion; other adapters receive the provider-neutral content plus provider/model fields without the private state. Durable content stays authoritative: a stored state the reading adapter cannot use degrades that one message to provider-neutral conversion with a diagnostic instead of failing the request.
304
304
 
@@ -495,6 +495,8 @@ interface LlmConfigurableProvider {
495
495
  * from outside.
496
496
  */
497
497
  declared?: boolean
498
+ /** Configuration diagnostic for repair; unaffected models may remain serviceable. */
499
+ error?: string
498
500
  }
499
501
  ```
500
502
 
@@ -514,7 +516,7 @@ interface LlmModelInfo {
514
516
  }
515
517
  ```
516
518
 
517
- Correctness-sensitive metadata is resolved separately from the advisory catalog and is owned by the adapter serving the exact route. Context capacity, adapter call defaults, and reasoning choices share one exact-model result so consumers do not repeat authoritative model resolution.
519
+ Correctness-sensitive metadata is resolved separately from the advisory catalog and is owned by the adapter serving the exact route. Context capacity, adapter call defaults, reasoning choices, and the system prompt update mode share one exact-model result so consumers do not repeat authoritative model resolution. `SystemPromptUpdate` has the single value `'in-history'`: the model reads the latest `system` message at any position of `messages` as the complete effective system prompt, so the agent loop can append a changed prompt after the cached history instead of rewriting message 0 ([decision rule](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/agent-loop/README.md#understand-the-implementation)); an absent mode means only a leading system message is read, and `normalizeModelInfo` rejects any other value with `INVALID_MODEL_INFO`.
518
520
 
519
521
  ```ts type-equiv
520
522
  /** Provider-owned context capacity for one exact provider/model route. */
@@ -565,6 +567,8 @@ interface LlmResolvedModelInfo extends LlmModelInfo {
565
567
  defaultMaxTokens?: number
566
568
  /** Adapter-owned selectable reasoning levels when exposed. */
567
569
  reasoning?: LlmModelReasoningInfo
570
+ /** Declared mid-conversation system prompt handling; absent means only a leading system message is read. */
571
+ systemPromptUpdate?: SystemPromptUpdate
568
572
  }
569
573
  ```
570
574
 
@@ -577,12 +581,16 @@ interface GenerateOptions {
577
581
  /** Adapter-owned reasoning effort selected for this exact model. */
578
582
  reasoningEffort?: ReasoningEffortId
579
583
  /**
580
- * Ordered conversation messages, exactly as the provider sees them (after
581
- * the `system` slot). A loop-built request assembles them as
582
- * the derived history (dsh-agent-loop); a hand-built one-shot passes any list.
584
+ * Ordered conversation messages, exactly as the provider sees them. A
585
+ * loop-built request passes the derived history (dsh-agent-loop), whose
586
+ * leading system-role message carries the system prompt; a hand-built
587
+ * one-shot passes any list.
583
588
  */
584
589
  messages: Message[]
585
- /** System prompt text (adapters map to the provider's system slot). */
590
+ /**
591
+ * System prompt text for one-shot callers; adapters map it to the provider's
592
+ * system slot ahead of `messages`. Loop-built requests leave it undefined.
593
+ */
586
594
  system?: string
587
595
  /** Tool schemas (adapters map to the provider's `tools` field). */
588
596
  tools?: ToolSchema[]
@@ -696,11 +704,11 @@ interface LlmDiscoveredModel {
696
704
 
697
705
  ### The request envelope: `LlmCallConfig` and the logged header
698
706
 
699
- The loop builds each request from logged state. `EpochHeader` records call config, marks the fields supplied by adapter defaults, and records the rendered prompt and authoritative returned tool order (configured by `toolOrder`, or lexicographic when unset) through full `request/header` snapshots. Together with derived history, this makes the request reconstructable from the session log. See [session.md](./session.md#the-request-header-event-requestheader) and the [reconstructability Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md).
707
+ The loop builds each request from logged state. `EpochHeader` records call config, marks the fields supplied by adapter defaults, and records the authoritative returned tool order (configured by `toolOrder`, or lexicographic when unset) through full `request/header` snapshots. The rendered prompt is derived history — the `system/message` at surface node 0, plus any later system node an `in-history` route appended — so the header and the derived history together make the request reconstructable from the session log. See [session.md](./session.md#the-request-header-event-requestheader) and the [reconstructability Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md).
700
708
 
701
- `agent/request` receives a frozen call-config seed and may return a replacement to switch provider, model, reasoning effort, or sampling. Before the waterfall, the loop removes values marked as adapter defaults so exact-model preparation materializes the selected route's current values; unmarked explicit settings remain in the proposal. After the waterfall, preparation rejects unsupported explicit effort ids without clamping and logs the effective config plus the fields supplied by adapter defaults under the turn signal. The prepared call keeps one adapter registration through dispatch. Requests reaching `llm/stream` are deep-frozen, so mutation throws, and carry a process-local loop identity so observers do not confuse separately logged frozen auxiliary calls with conversation requests.
709
+ `agent/request` receives a frozen call-config seed and may return a replacement to switch provider, model, reasoning effort, or sampling. Before the waterfall, the loop removes values marked as adapter defaults so exact-model preparation materializes the selected route's current values; unmarked explicit settings remain in the proposal. After the waterfall, preparation rejects unsupported explicit effort ids without clamping and logs the effective config plus the fields supplied by adapter defaults under the turn signal. On step admission, this waterfall and preparation run after assembly and `step/start` but before the system prompt and accepted user batch are committed; cancellation during either commits neither. The prepared capability governs prompt reconciliation, and the call keeps one adapter registration through dispatch. Requests reaching `llm/stream` are deep-frozen, so mutation throws, and carry a process-local loop identity so observers do not confuse separately logged frozen auxiliary calls with conversation requests.
702
710
 
703
- On the wire, a loop-built request reads the `system` slot (the rendered prompt assembly) followed by the derived history. The logged request snapshot ends with the newest `user/message` on a turn's first step and the previous step's tool results on later steps. The dev invariant recomputes exactly this equation against every loop-built request.
711
+ On the wire, a loop-built request is the derived history alone: the rendered prompt travels as the leading `system`-role message (surface node 0, a `system/message` event) and, when the prepared call declares `systemPromptUpdate: 'in-history'`, a non-empty changed prompt may follow the cached history as a later `system`-role message that the model reads as the effective prompt; the request's `system` field is unset — `GenerateOptions.system` serves direct one-shot callers such as title providers. An empty rendering leaves no system messages in derived history, even when earlier requests retained several prompt versions. The logged request ends with the newest `user/message` on a turn's first step and the previous step's tool results on later steps. The dev invariant recomputes exactly this equation against every loop-built request and rejects a loop request carrying a `system` field.
704
712
 
705
713
  FIXME(call-config-shape): revisit which remaining fields are genuinely epoch-level for cache purposes (`model` and the model-owned reasoning effort are explicit; the sampling scalars sit here out of caution).
706
714
 
@@ -753,6 +761,8 @@ interface PreparedLlmCall {
753
761
  readonly context?: LlmModelContext
754
762
  /** Exact model modalities captured with the adapter dispatch generation. */
755
763
  readonly inputModalities?: readonly ModelModality[]
764
+ /** Exact model system prompt update mode captured with the adapter dispatch generation. */
765
+ readonly systemPromptUpdate?: SystemPromptUpdate
756
766
  /** Config fields materialized by the captured adapter rather than proposed by the caller. */
757
767
  readonly adapterDefaults: LlmCallConfigAdapterDefaults
758
768
  /**
@@ -5,7 +5,7 @@ outline: [2,3]
5
5
 
6
6
  # LSP navigation
7
7
 
8
- The LSP seam — a [capability seam](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-07-15-lsp-capability-seam.md) exposing semantic code navigation on one `ctx.lsp` service, split across packages: Service Definition ([dsh-lsp](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/lsp/lsp), `ctx.lsp` + the provider registry), a generic Service Provider ([dsh-lsp-stdio](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/lsp/lsp-stdio), a configured stdio language-server host), and Consumer ([dsh-tool-lsp](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/lsp/tool-lsp), the `lsp` tool schema). LSP is **one optional capability**, not part of the agent-loop spine — so its vocabulary lives here, not in [core.md](./core.md). A provider swap does not change how the model asks for navigation.
8
+ The LSP seam — a [capability seam](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/glossary.md#capability-seam) exposing semantic code navigation on one `ctx.lsp` service, split across packages: Service Definition ([dsh-lsp](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/lsp/lsp), `ctx.lsp` + the provider registry), a generic Service Provider ([dsh-lsp-stdio](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/lsp/lsp-stdio), a configured stdio language-server host), and Consumer ([dsh-tool-lsp](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/lsp/tool-lsp), the `lsp` tool schema). LSP is **one optional capability**, not part of the agent-loop spine — so its vocabulary lives here, not in [core.md](./core.md). A provider swap does not change how the model asks for navigation.
9
9
 
10
10
  Source: [`packages/lsp/lsp/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/lsp/lsp/src/types.ts)
11
11
 
@@ -11,7 +11,20 @@ The seam is a [capability seam](https://github.com/deepseek-ai/deepseek-harness/
11
11
 
12
12
  ## `SessionHandle` — one open channel onto a stored session
13
13
 
14
- Every log read and write flows through a handle, never through id-addressed service methods: the handle is the single door the cross-process write lease guards. One handle type serves both accesses — a mutation on a `read` handle is a runtime `SessionReadOnlyError` rather than a typed split — and in-process single-writer ownership makes a second `open(id, 'write')` reject with `SessionAlreadyOwnedError` while an owner is active.
14
+ Every log read and write flows through a handle, never through id-addressed service methods: the handle is the single door the cross-process write lease guards. A read returns a caller-owned outer slice and the producer-established aliasing state of its event values. One handle type serves both accesses — a mutation on a `read` handle is a runtime `SessionReadOnlyError` rather than a typed split — and in-process single-writer ownership makes a second `open(id, 'write')` reject with `SessionAlreadyOwnedError` while an owner is active.
15
+
16
+ ```ts type-equiv
17
+ /** One persistence event slice returned by {@link SessionHandle.read}. */
18
+ interface SessionHandleReadResult {
19
+ /**
20
+ * Whether event values are exclusively owned or shared only after deep
21
+ * freezing. Slicing preserves the producer's state even when no events remain.
22
+ */
23
+ readonly eventState: SessionSeedEventState
24
+ /** Event values in a caller-owned outer array. */
25
+ readonly events: readonly SessionEvent[]
26
+ }
27
+ ```
15
28
 
16
29
  ```ts type-equiv
17
30
  /**
@@ -50,9 +63,9 @@ interface SessionHandle extends AsyncDisposable {
50
63
  * @param length - maximum number of events to return; defaults to the rest
51
64
  * of the log. An offset at or past the end returns an empty list.
52
65
  * @param options - optional cancellation.
53
- * @returns the events with `seq >= offset`, at most `length` of them.
66
+ * @returns the caller-owned outer slice plus the ownership state of its event values.
54
67
  */
55
- read(offset?: number, length?: number, options?: SessionHandleReadOptions): Promise<readonly SessionEvent[]>
68
+ read(offset?: number, length?: number, options?: SessionHandleReadOptions): Promise<SessionHandleReadResult>
56
69
 
57
70
  /**
58
71
  * Append a contiguous batch continuing the current logical end. The first
@@ -93,7 +106,7 @@ A created session is observable in this process from the moment `create` resolve
93
106
 
94
107
  ## The flush checkpoint
95
108
 
96
- `session/event` is a *synchronous* notification; the mounted backend routes it by session id into the active write handle's bounded write-behind window without blocking the producer (the backend installs these listeners once, because persistence already enforces one active write handle per id). The first pending event starts a fixed internal batching window, and later events join without resetting its deadline. Expiry starts one durable `append` through the session's write handle; events admitted during that write receive their own deadline and form a follow-up batch. `session/flush` cancels the wait and drains through quiescence, so the loop still uses it as the ordering and error-observation checkpoint before claiming the next ordinary turn. A rejected background write retains its events in order, pauses the automatic path, and is reported through the logger; the next explicit flush retries and rejects loudly to its caller. `session/disposed` performs the same final drain and closes the handle, and `close()` itself drains the routed buffer through the still-open storage, so backend teardown's close sweep loses nothing. The window bounds only intentional batching wait, not event-loop scheduling or backend durability latency ([decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-08-bounded-session-persistence-write-batching.md)).
109
+ `session/event` is a *synchronous* notification; the mounted backend routes it by session id into the active write handle's bounded write-behind window without blocking the producer (the backend installs these listeners once, because persistence already enforces one active write handle per id). The first pending event starts a fixed internal batching window, and later events join without resetting its deadline. Expiry starts one durable `append` through the session's write handle; events admitted during that write receive their own deadline and form a follow-up batch. `session/flush` cancels the wait and drains through quiescence, so the loop still uses it as the ordering and error-observation checkpoint before claiming the next ordinary turn. A rejected background write retains its events in order, pauses the automatic path, and is reported through the logger; the next explicit flush retries and rejects loudly to its caller. `session/disposed` performs the same final drain and closes the handle, and `close()` itself drains the routed buffer through the still-open storage, so backend teardown's close sweep loses nothing. The window bounds only intentional batching wait, not event-loop scheduling or backend durability latency.
97
110
 
98
111
  ## Crash recovery preserves an interrupted turn
99
112
 
@@ -101,7 +114,7 @@ A log crashed mid-turn ends with an open `turn/start` and no `turn/end`. Persist
101
114
 
102
115
  Repair therefore writes only under write ownership: a live session's write handle is held by its lifecycle owner, so a concurrent `open(id, 'write')` rejects with `SessionAlreadyOwnedError` instead of racing repair against a live turn. Read-only observers (session-query) balance an interrupted cold log with the same closers in memory only, writing nothing back.
103
116
 
104
- Read-only observation is `open(id, 'read')`: the handle serves validated contiguous prefix slices, never a torn tail, and repeated reads on one handle never observe an older state than a prior read. There is no persistence-side prepared-Session cache: session-query owns its cold-read cache, keying one balanced cold Session per id on the `stat().revision` change token and re-reading only when the token changes. The [handle-based persistence Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.md) owns this lifecycle; the [Session preparation decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-05-session-preparation.md) records the publication-boundary `SessionPreparation` that remains.
117
+ Read-only observation is `open(id, 'read')`: the handle serves validated contiguous prefix slices, never a torn tail, and repeated reads on one handle never observe an older state than a prior read. There is no persistence-side prepared-Session cache: session-query owns its cold-read cache, keying one balanced cold Session per id on the `stat().revision` change token and re-reading only when the token changes. The [handle-based persistence Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.md) owns this lifecycle; the archived [Session preparation record](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/archived/architecture/2026-08-05-session-preparation.md) documents the original publication-boundary `SessionPreparation` decision.
105
118
 
106
119
  ## `SessionLocation` — refusal-diagnostics artifact target
107
120
 
@@ -176,7 +189,7 @@ interface SessionHeader {
176
189
 
177
190
  ## Format refusal — logs a build cannot faithfully read
178
191
 
179
- A backend refuses a log it cannot faithfully interpret with `SessionFormatUnsupportedError`, distinct from `SessionPersistenceCorruptionError` because nothing is damaged. `stat` and `list` classify the highest canonical generation and translate a supported historical header without reading or mutating its body. `open` runs the build-static adjacent migration chain under per-id serialization before returning a handle, leaves every source path, byte, and inode unchanged, and exclusively publishes only the final current generation. A future highest generation refuses even when an older readable generation remains. Current v2 restoration retains installed extensions and unknown events carrying `ignorable: true`; historical v0/v1 migration refuses an unknown type even when marked ignorable. The message appends the selected raw log path when the backend keeps one artifact per session. The JSONL backend migrates released v0 or v1 to current v2 and refuses a future version before interpreting its version-specific fields or event rows. An out-of-tree backend must enforce equivalent current-only handle values and direction-aware refusals at its physical-format entry. The [released-format migration decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md) owns the chain and immutable-publication rules.
192
+ A backend refuses a log it cannot faithfully interpret with `SessionFormatUnsupportedError`, distinct from `SessionPersistenceCorruptionError` because nothing is damaged. `stat` and `list` classify the highest canonical generation and translate a supported historical header without reading or mutating its body. Historical `open` calls share one per-session migration preparation before returning current logical values and leave every source path, byte, and inode unchanged. The JSONL provider returns a read handle from that in-memory result without publishing; a write open holds its single-writer claim and file lease while it reuses the preparation, exclusively publishes the final current generation, and only then returns the writable handle. A future highest generation refuses even when an older readable generation remains. Current-format restoration retains installed extensions and unknown events carrying `ignorable: true`; historical v0/v1/v2 migration refuses an unknown type even when marked ignorable. The message appends the selected raw log path when the backend keeps one artifact per session. An out-of-tree backend must enforce equivalent current-only handle values and direction-aware refusals at its physical-format entry. The [released-format migration decision](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md) owns the chain and immutable-publication rules.
180
193
 
181
194
  ## `CreateSessionOptions` — seeding and metadata
182
195
 
@@ -192,7 +205,7 @@ interface CreateSessionOptions {
192
205
  /** Initial replay or fork history supplied at construction. */
193
206
  readonly seed?: readonly SessionEvent[]
194
207
  /**
195
- * Exact fork-inherited prefix length when `meta.isSeeded` is true. In v2 the
208
+ * Exact fork-inherited prefix length when `meta.isSeeded` is true. The
196
209
  * constructor seed is exactly this inherited prefix; the constructor
197
210
  * appends the child-owned tagged marker at the cut.
198
211
  */
@@ -217,29 +230,37 @@ Replay/fork is therefore `ctx.agents.create({ sessionId, seed, meta })` — a fo
217
230
 
218
231
  ## Preparation and restoration ownership
219
232
 
220
- `SessionStore.prepare()` accepts ordinary creation options or fresh persistence graphs transferred through `RestoredSessionOptions`. The restoration branch validates and freezes the transferred header and events in place, so callers must retain no mutable aliases. `SessionPreparation` then owns the exact unpublished Session until publication or rollback; disposal is synchronous and idempotent. agent-loop's resume builds these graphs by reading the stored log through the session's write handle and appending any needed `interruptedTurnClosers` before preparation.
233
+ `SessionStore.prepare()` accepts ordinary creation options or an adoptable seed through `RestoredSessionOptions`. Its `eventState` says whether event values are independently owned or shared only after deep freezing; the producer establishes that state, and slicing does not infer a different state from result length. Restoration validates and adopts those values without another copy or freeze pass. `SessionPreparation` then owns the exact unpublished Session until publication or rollback; disposal is synchronous and idempotent. agent-loop's resume reads this result through the session's write handle and appends independently owned `interruptedTurnClosers` before preparation.
234
+
235
+ ```ts type-equiv
236
+ /**
237
+ * Aliasing state of an adoptable Session seed. `shared-frozen` permits deeply
238
+ * frozen aliases plus independently owned unfrozen values in the same seed.
239
+ */
240
+ type SessionSeedEventState = 'detached' | 'shared-frozen'
241
+ ```
221
242
 
222
243
  ```ts type-equiv
223
244
  /**
224
- * Fresh storage values transferred to {@link SessionStore.prepare} without a
225
- * second serialization copy. Callers retain no mutable aliases.
245
+ * Adoptable storage values transferred to {@link SessionStore.prepare}
246
+ * without another copy or freeze pass.
226
247
  */
227
248
  interface RestoredSessionOptions {
228
- /** Fresh detached storage events to validate and freeze in place. */
249
+ /** Events that are independently owned or already deeply frozen. */
229
250
  readonly seed: SessionEvent[]
230
- /** Fresh detached storage metadata to validate and freeze in place. */
251
+ /** Independently owned storage metadata to validate and freeze in place. */
231
252
  readonly meta: SessionHeader
232
253
  /** Exact number of fork-inherited leading events decoded from storage. */
233
254
  readonly inheritedEventCount: SessionLogOffset
234
- /** Select the persistence ownership-transfer path. */
235
- readonly seedSource: 'persistence'
255
+ /** Aliasing state carried from the operation that produced the seed. */
256
+ readonly eventState: SessionSeedEventState
236
257
  }
237
258
  ```
238
259
 
239
260
  ```ts type-equiv
240
261
  /** Inputs accepted while constructing an unpublished Session. */
241
262
  type PrepareSessionOptions =
242
- | (CreateSessionOptions & { readonly seedSource?: undefined })
263
+ | (CreateSessionOptions & { readonly eventState?: undefined })
243
264
  | RestoredSessionOptions
244
265
  ```
245
266
 
@@ -5,7 +5,7 @@ outline: [2,3]
5
5
 
6
6
  # Session-local Schedule
7
7
 
8
- Schedule owns durable reminders that return to the original live Session as ordinary later conversation turns. The [durable Schedule Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-08-05-durable-web-schedule.md) owns persistence, lifecycle, and active-state presentation, [conversational delivery](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/simplification/2026-08-09-conversational-schedule-delivery.md) owns the no-receipt boundary, the [explicit time-zone boundary](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.md) owns browser-local interpretation, and [bounded fixed-rate Schedule](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/simplification/2026-08-09-bounded-fixed-rate-schedule.md) owns recurrence. This page records the durable and model-facing shapes from [`packages/schedule/schedule/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/schedule/schedule/src/types.ts); the [package README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/schedule/schedule/README.md) owns composition, tool behavior, and the exact reminder framing.
8
+ Schedule owns durable reminders that return to the original live Session as ordinary later conversation turns. The [durable Schedule Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-08-05-durable-web-schedule.md) owns persistence, lifecycle, and active-state presentation, and the [explicit time-zone boundary](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.md) owns browser-local interpretation. This page records the durable and model-facing shapes from [`packages/schedule/schedule/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/schedule/schedule/src/types.ts); the [package README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/schedule/schedule/README.md) owns composition, tool behavior, and the exact reminder framing.
9
9
 
10
10
  ## Durable records
11
11
 
@@ -5,7 +5,7 @@ outline: [2,3]
5
5
 
6
6
  # Scoped Registration
7
7
 
8
- The [scope package](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/core/scope) supplies the identity, carrier, and scoped-layer vocabulary that makes one registration context mean both per-agent visibility and shared lifetime ownership. It is a library primitive rather than a Cordis service; the [agent-scope runtime-design Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md#scope-routing-one-opaque-key-selects-one-layer) owns the lifecycle rationale, the [shared-storage Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-07-12-scoped-layers-store.md) owns the registry-layer decision, and the package [README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/scope/README.md) owns the callable API and filtering semantics.
8
+ The [scope package](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/core/scope) supplies the identity, carrier, and scoped-layer vocabulary that makes one registration context mean both per-agent visibility and shared lifetime ownership. It is a library primitive rather than a Cordis service; the [agent-scope runtime-design Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md#scope-routing-one-opaque-key-selects-one-layer) owns the lifecycle rationale, and the package [README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/scope/README.md) owns the callable API and filtering semantics.
9
9
 
10
10
  Sources: [`packages/core/scope/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/scope/src/index.ts) and [`packages/core/scope/src/store.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/scope/src/store.ts).
11
11
 
@@ -207,6 +207,10 @@ async listCandidates( agent: Agent, query: string = '', limit: number = this.con
207
207
 
208
208
  /**
209
209
  * Snapshot all references for one accepted direct message and return one aggregated durable context.
210
+ * Automatic budgets use the last assembled route, or agent options before any assembly.
211
+ * Missing model capacity or adapter uses 64 KiB; other metadata lookup failures and cancellation reject preparation.
212
+ * Truncated previews include omission facts and a full-snapshot spill locator, or an explicit unavailable notice.
213
+ * Cancellation prevents context publication, including when storage completes after cancellation.
210
214
  * @param agent - target agent; references to it are rejected.
211
215
  * @param content - already host-normalized readable message content.
212
216
  * @param references - structured source sessions in mention order.