@deepseek-ai/dsh-session 0.1.3-alpha.2 → 0.1.5-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/core/session/README.md
5
- README.md: e810299678c93fd0b5c740dcd2485df746eee0e0
6
- README.zh.md: 9d8250053a6b11fd9ac71839bd6517cb58c3d90a
5
+ README.md: a06b22b5e8f48743047c68e883edd496fbc6eb77
6
+ README.zh.md: 755894b4c30f0a3807f472cece32685d102efbeb
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- `dsh-session` provides the append-only session log that records an agent's whole interaction history — the single source of truth every model-visible fact flows through. The LLM message history is *derived* from the log (`deriveMessages()`), never stored separately, so replay is re-derivation from the same events and compaction can shadow older surface entries without deleting history. The package also provides the in-memory store (`ctx.sessions`), the typed `SessionEvent` vocabulary that plugins extend by declaration merging, and the surface layer that orders message-producing events. Persistence is deliberately a separate concern: backends subscribe to `session/event` and flush on `session/flush`. Choose it as the foundation of any agent session; it runs no model calls itself.
12
+ `dsh-session` records every model-visible fact in an append-only session log and derives model history from that record. Consumers can inspect, replay, fork, and flush sessions while preserving historical events; compaction hides superseded entries from the active conversation without deleting them. Sessions remain in memory unless a persistence backend is added, and durability checkpoints wait for configured backends. Choose this package wherever an agent needs a reconstructable session record; it does not call models.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -47,7 +47,11 @@ session.append('user/message', { role: 'user', content: [{ type: 'text', text: '
47
47
  session.deriveMessages() // the derived model history
48
48
  ```
49
49
 
50
- Surface events (`user/message`, `assistant/message`, `tool/result`) must declare how they join the ordered surface. An Assistant message embeds the exact compact provider stream that produced it; `assistant/attempt`, boundaries, and other log-only events never produce a message.
50
+ Surface events (`system/message`, `user/message`, `assistant/message`, `tool/result`) require `surfaceOp` in both typed events and append input. A replacement uses exactly `{ op: 'replace', startSeq, endSeq }`, with inclusive `SessionSeq` endpoints in current surface order. An Assistant message embeds its exact compact provider stream and forbids `sourceEventSeqs`. Known log-only events forbid both metadata fields and never produce a message.
51
+
52
+ Append, seed/restore, and event adoption/snapshot reject any `header.system` and exactly empty optional request-header fields (`tools: []`, `adapterDefaults: {}`) instead of normalizing input. Tool-result `data.error` is allowed only when `message.content[0].isError === true`; failure identity remains optional. Rejected appends do not change the log, derived state, or event feed. Adoption validates event-local metadata but not referenced history or replacement membership.
53
+
54
+ `system/message` holds the rendered system prompt: the first one is surface node 0, the prepared call capability governs admission, with a non-empty rendering consolidated at the first system node on an incapable route or appended after cached history inside a continuing `in-history` series; empty system nodes project to no message, so clearing the prompt requires logged empty replacements of all active system nodes, not just the latest; the surface fold rejects a replacement covering node 0 while it is a `system/message` unless the replacing event is itself a `system/message` over exactly that node, while later system nodes carry no protection and a compaction range may shadow them ([decision](../../../.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md)).
51
55
 
52
56
  ### Read the log
53
57
 
@@ -101,11 +105,11 @@ Every append uses the shared iterative `snapshotJsonValue()` pass, which reads,
101
105
 
102
106
  ### Derived history
103
107
 
104
- `deriveMessages()` caches each surface node's projection once and returns a fresh array per call over shared, deep-frozen messages; each of the three surface event types (`user/message`, `assistant/message`, `tool/result`) projects its own message kind — user content verbatim, the assembled assistant message with its provider and model, or a user-role tool result. Embedded Assistant streams and `assistant/attempt` events remain replay and diagnostic data only. A surface rewrite rebuilds the projection — there is no raw-log fallback, so the surface is the single source of derived history.
108
+ `deriveMessages()` caches each surface node's projection once and returns a fresh array per call over shared, deep-frozen messages; each of the four surface event types (`system/message`, `user/message`, `assistant/message`, `tool/result`) projects its own message kind — the system-role prompt (an empty-content system node projects to no message), user content verbatim, the assembled assistant message with its provider and model, or a user-role tool result. Embedded Assistant streams and `assistant/attempt` events remain replay and diagnostic data only. A surface rewrite rebuilds the projection — there is no raw-log fallback, so the surface is the single source of derived history.
105
109
 
106
110
  ### The request header
107
111
 
108
- The loop logs a full canonical `request/header` snapshot (call config, adapter defaults, rendered system prompt, assembled tool schemas) at each loop-instance boundary and on change; `foldRequestHeader(events)` reconstructs it by selecting the latest snapshot, making every conversation request a pure function of the log. Route metadata (`request/context`) is separate logged state appended only when the provider, model, or capacity differs.
112
+ The loop logs a full canonical `request/header` snapshot (call config, adapter defaults, assembled tool schemas — the rendered system prompt is a `system/message` surface node, not header state) at each loop-instance boundary and on change; `foldRequestHeader(events)` reconstructs it by selecting the latest snapshot, making every conversation request a pure function of the log. Route metadata (`request/context`) is separate logged state appended only when the provider, model, capacity, or `systemPromptUpdate` mode differs; it records the actual prepared call's mode after prompt and user admission, rather than supplying that admission decision.
109
113
 
110
114
  </details>
111
115
 
@@ -131,7 +135,7 @@ The package-level contract is enough for most consumers; read these when you nee
131
135
 
132
136
  #### What the model sees
133
137
 
134
- The model receives the complete messages from `user/message`, `assistant/message`, and `tool/result` surface entries verbatim — identities, roles, sources, and content blocks are the same values established at creation, and projections never mint identities. Direct prompts and injected context remain separate `user/message` events whose sources preserve their provenance. Embedded streams, `assistant/attempt`, boundaries, and other log-only facts add no message.
138
+ The model receives the complete messages from `system/message`, `user/message`, `assistant/message`, and `tool/result` surface entries verbatim, the system prompt first — identities, roles, sources, and content blocks are the same values established at creation, and projections never mint identities. Direct prompts and injected context remain separate `user/message` events whose sources preserve their provenance. Embedded streams, `assistant/attempt`, boundaries, and other log-only facts add no message.
135
139
 
136
140
  #### Token effect
137
141
 
@@ -159,15 +163,15 @@ Append-only; newly visible content follows the reusable request prefix and does
159
163
 
160
164
  #### What the model sees
161
165
 
162
- The session reconstructs the system prompt, tool schemas, call config, and session prefix that the loop actually sent. Header events do not add a second copy to message history; the prefix is prepended outside `deriveMessages()`.
166
+ The session reconstructs the tool schemas and call config that the loop actually sent; the system prompt is part of `deriveMessages()` as surface node 0 and, after an in-history update, as the latest system node. Header events add no message to history and hold no copy of the prompt.
163
167
 
164
168
  #### Token effect
165
169
 
166
- Zero duplicate tokens from logging. The reconstructed prefix, system text, and schemas still incur their normal per-request cost.
170
+ Zero duplicate tokens from logging. The system nodes and schemas still incur their normal per-request cost.
167
171
 
168
172
  #### KV Cache effect
169
173
 
170
- Logging causes no invalidation, and exact reconstruction preserves request-prefix identity. A later header with changed prefix, prompt, or schemas may invalidate reuse from its first difference.
174
+ Logging causes no invalidation, and exact reconstruction preserves request-prefix identity. A later header with changed config or schemas may invalidate reuse from its first difference; a prompt change that replaces surface node 0 invalidates reuse from the first token, while an in-history append keeps the prefix through the cached history reusable.
171
175
 
172
176
  ## Known Limitations and Deferred Work
173
177
 
@@ -177,7 +181,7 @@ Logging causes no invalidation, and exact reconstruction preserves request-prefi
177
181
  These limits define when the session store needs special care. They are current package constraints, not a task backlog.
178
182
 
179
183
  - **`fork()` cuts only at stable boundaries of live sessions** — the selected prefix must end outside an open turn and the source must be in the store; forking a persisted-but-unloaded session is excluded from the fork API.
180
- - **`SESSION_FORMAT_VERSION` names only the current logical representation** — historical headers and events remain in adjacent format packages, while persistence publishes a complete supported chain before constructing `Session`; equal-version unknown events still require the envelope's explicit `ignorable` marker ([mechanism](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md)).
184
+ - **`SESSION_FORMAT_VERSION` names the current V3 logical representation** — the V3 reader rejects retired `header.system` and validates `system/message` payloads and protected-head rewrites. Historical headers and events belong to adjacent format packages; the V2→V3 edge converts supported history before constructing `Session`, and write open publishes only the V3 successor. Equal-version unknown events require the envelope's explicit `ignorable` marker, which does not promise safe structural migration ([mechanism](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md)).
181
185
  - **`TurnEndReasonMap` omits the ACP-named `refusal` / `max_turn_requests` variants** — producer-gated: they land when an adapter or the loop first emits them.
182
186
  - **No session tree beyond fork** — a pi-style entry tree over branched sessions is deferred unless a consumer needs more than boundary-based forking.
183
187
 
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- `dsh-session` 提供仅追加的会话日志,记录 agent(智能体)的完整交互历史——每个模型可见事实都流经的单一真源。LLM(大语言模型)消息历史由日志*派生*(`deriveMessages()`),从不另行存储,因此回放就是对同一批事件重新派生,压缩(compaction)也可以遮蔽较旧的表层条目而不删除历史。该包还提供内存存储(`ctx.sessions`)、插件通过声明合并扩展的类型化 `SessionEvent` 词汇,以及为产生消息的事件排序的 surface 层。持久化刻意是独立关注点:后端订阅 `session/event` 并在 `session/flush` 时刷新。作为任何 agent 会话的基础时请选择本包;它本身不运行模型调用。
12
+ `dsh-session` 在仅追加的会话日志中记录每个模型可见事实,并从该记录派生模型历史。消费方可以检查、回放、派生和刷新会话,同时保留历史事件;压缩会在活跃对话中隐藏被取代的条目,但不会删除它们。除非添加持久化后端,否则会话仅保留在内存中;持久性检查点会等待配置的后端。agent 需要可重建的会话记录时请选择本包;它本身不调用模型。
13
13
 
14
14
  ## 目录
15
15
 
@@ -47,7 +47,11 @@ session.append('user/message', { role: 'user', content: [{ type: 'text', text: '
47
47
  session.deriveMessages() // the derived model history
48
48
  ```
49
49
 
50
- 表层事件(`user/message`、`assistant/message`、`tool/result`)必须声明如何进入有序 surfaceAssistant message 会嵌入产生它的精确紧凑 provider stream;`assistant/attempt`、边界与其他仅日志事件从不产生消息。
50
+ 表层事件(`system/message`、`user/message`、`assistant/message`、`tool/result`)在类型化事件与追加输入中都必须带有 `surfaceOp`。替换操作仅接受 `{ op: 'replace', startSeq, endSeq }`,端点为包含边界的 `SessionSeq`,按当前 surface 顺序解释。Assistant message 嵌入其精确紧凑 provider stream,并禁止 `sourceEventSeqs`。已知仅日志事件禁止这两个元数据字段,且从不产生消息。
51
+
52
+ 追加、seed/restore 与事件 adoption/snapshot 会拒绝任何 `header.system` 及恰好为空的可选请求头字段(`tools: []`、`adapterDefaults: {}`),而不规范化输入。工具结果的 `data.error` 仅在 `message.content[0].isError === true` 时允许存在;失败标识仍是可选的。被拒绝的追加不会改变日志、派生状态或事件流。Adoption 校验事件局部元数据,但不校验所引用的历史或替换端点是否属于 surface。
53
+
54
+ `system/message` 承载渲染后的系统提示词:第一条是 surface 第 0 号节点,准入依据已准备调用的能力,不具备能力的路由将非空渲染文本归并到首个系统节点,延续中的 `in-history` 序列则在缓存历史之后追加;空系统节点不投影为消息,因此清除提示词必须为所有生效的系统节点记录空内容替换,而非仅替换最新节点;当第 0 号节点是 `system/message` 时,surface 折叠拒绝覆盖它的替换,除非替换事件本身是恰好覆盖该节点的 `system/message`,而后续系统节点不受保护,压缩范围可以遮蔽它们([决策](../../../.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.zh.md))。
51
55
 
52
56
  ### 读取日志
53
57
 
@@ -101,11 +105,11 @@ session.deriveMessages() // the derived model history
101
105
 
102
106
  ### 派生历史
103
107
 
104
- `deriveMessages()` 把每个 surface 节点的投影缓存一次,每次调用都返回共享、深度冻结消息之上的新数组;三种 surface 事件类型(`user/message`、`assistant/message`、`tool/result`)各自投影自己的消息种类——user 内容原样、带提供方与模型的组装 assistant 消息,或 user 角色的工具结果。嵌入式 Assistant stream 与 `assistant/attempt` 事件只保留重放和诊断数据。surface 重写会重建投影——不存在原始日志回退,因此 surface 是派生历史的唯一来源。
108
+ `deriveMessages()` 把每个 surface 节点的投影缓存一次,每次调用都返回共享、深度冻结消息之上的新数组;四种 surface 事件类型(`system/message`、`user/message`、`assistant/message`、`tool/result`)各自投影自己的消息种类——system 角色的提示词(空内容的系统节点投影为无消息)、user 内容原样、带提供方与模型的组装 assistant 消息,或 user 角色的工具结果。嵌入式 Assistant stream 与 `assistant/attempt` 事件只保留重放和诊断数据。surface 重写会重建投影——不存在原始日志回退,因此 surface 是派生历史的唯一来源。
105
109
 
106
110
  ### 请求头
107
111
 
108
- 循环在每个循环实例边界及变更时记录完整规范 `request/header` 快照(调用配置、适配器默认值、渲染后的系统提示词、组装后的工具 schema);`foldRequestHeader(events)` 通过选择最新快照来重建它,使每个对话请求都成为日志的纯函数。路由元数据(`request/context`)是独立的已记录状态,仅在提供方、模型或容量变化时追加。
112
+ 循环在每个循环实例边界及变更时记录完整规范 `request/header` 快照(调用配置、适配器默认值、组装后的工具 schema——渲染后的系统提示词是 `system/message` surface 节点,不是 header 状态);`foldRequestHeader(events)` 通过选择最新快照来重建它,使每个对话请求都成为日志的纯函数。路由元数据(`request/context`)是独立的已记录状态,仅在提供方、模型、容量或 `systemPromptUpdate` 模式变化时追加;它在提示词与用户消息准入之后记录实际已准备调用的模式,而非提供准入决策。
109
113
 
110
114
  </details>
111
115
 
@@ -131,7 +135,7 @@ session.deriveMessages() // the derived model history
131
135
 
132
136
  #### 模型看到什么
133
137
 
134
- 模型会原样接收 `user/message`、`assistant/message` 与 `tool/result` surface 条目中的完整消息——标识、角色、来源与内容块都与创建时确定的值相同,投影从不生成标识。直接提示词与注入上下文仍是彼此独立的 `user/message` 事件,各事件的来源会保留其出处。嵌入式 stream、`assistant/attempt`、边界与其他仅日志事实不会添加消息。
138
+ 模型会原样接收 `system/message`、`user/message`、`assistant/message` 与 `tool/result` surface 条目中的完整消息,系统提示词在先——标识、角色、来源与内容块都与创建时确定的值相同,投影从不生成标识。直接提示词与注入上下文仍是彼此独立的 `user/message` 事件,各事件的来源会保留其出处。嵌入式 stream、`assistant/attempt`、边界与其他仅日志事实不会添加消息。
135
139
 
136
140
  #### Token 影响
137
141
 
@@ -159,15 +163,15 @@ session.deriveMessages() // the derived model history
159
163
 
160
164
  #### 模型看到什么
161
165
 
162
- 会话会重建循环实际发送的系统提示词、工具 schema、调用配置与会话前缀。请求头事件不会向消息历史加入第二份副本;前缀在 `deriveMessages()` 外部前置。
166
+ 会话会重建循环实际发送的工具 schema 与调用配置;系统提示词作为 surface 第 0 号节点、并在历史内更新之后作为最新的系统节点,属于 `deriveMessages()` 的一部分。请求头事件不向历史加入任何消息,也不持有提示词的副本。
163
167
 
164
168
  #### Token 影响
165
169
 
166
- 日志记录不产生重复 token。重建的前缀、系统文本与 schema 仍会产生正常的逐请求开销。
170
+ 日志记录不产生重复 token。各系统节点与 schema 仍会产生正常的逐请求开销。
167
171
 
168
172
  #### KV Cache 影响
169
173
 
170
- 记录日志不会导致失效,精确重建会保持请求前缀一致。后续请求头若更改前缀、提示词或 schema,可能从第一处差异开始使复用失效。
174
+ 记录日志不会导致失效,精确重建会保持请求前缀一致。后续请求头若更改配置或 schema,可能从第一处差异开始使复用失效;替换 surface 第 0 号节点的提示词变更会从第一个 token 起使复用失效,而历史内追加则保持直到已缓存历史末尾的前缀可复用。
171
175
 
172
176
  ## 已知限制与延期工作
173
177
 
@@ -177,7 +181,7 @@ session.deriveMessages() // the derived model history
177
181
  这些限制说明会话存储何时需要特别留意。它们是当前包约束,不是任务积压。
178
182
 
179
183
  - **`fork()` 仅在实时会话的稳定边界处切分**:所选前缀结束时不得有开放轮次,且源会话必须位于存储中;fork API 不支持对已持久化但未加载的会话进行 fork。
180
- - **`SESSION_FORMAT_VERSION` 只命名当前逻辑表示**——历史 header 与事件位于相邻格式包中,持久化会在构造 `Session` 前发布完整受支持链;同版本未知事件仍要求信封显式带有 `ignorable` 标记([机制](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md))。
184
+ - **`SESSION_FORMAT_VERSION` 命名当前 V3 逻辑表示**——V3 读取器拒绝已退役的 `header.system`,并校验 `system/message` 载荷与受保护头节点的重写。历史 header 与事件归相邻格式包所有;V2→V3 迁移边在构造 `Session` 前转换受支持的历史,写打开只发布 V3 后继代际。同版本未知事件要求信封显式带有 `ignorable` 标记,但这不保证结构迁移的安全性([机制](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md))。
181
185
  - **`TurnEndReasonMap` 不含 ACP(Agent Client Protocol)命名的 `refusal`/`max_turn_requests` 变体**:受生产方约束;只有当适配器或循环首次产生这些变体时才加入。
182
186
  - **fork 之外没有会话树**:基于分支会话的 pi 风格条目树被推迟,除非消费方需要超越基于边界的 forking 的能力。
183
187