@deepseek-ai/dsh-session 0.1.1-rc.2 → 0.1.2-alpha.3

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: c62668167f4e0dfda822c7b82add3b4d8eb3d635
6
- README.zh.md: 0b8eb6e1bc106509a86ed51ae2f37f2ad471c7a1
5
+ README.md: 0d691b31c4918152ecf1092002646296c5d9984a
6
+ README.zh.md: 2118def74c059f6637f12a9aeba20ff92a5d2363
package/README.md CHANGED
@@ -1,104 +1,130 @@
1
- # dsh-session
1
+ ---
2
+ description: "The event-sourced session log and in-memory store for users and maintainers building, inspecting, or extending the durable record behind every agent interaction."
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @deepseek-ai/dsh-session
2
7
 
3
8
  English | [中文](README.zh.md)
4
9
 
5
- Event-sourced session log and in-memory store. A `Session` is the append-only source of truth for an agent's whole interaction history — the LLM message history is *derived* from it. A **surface** layer (an ordered projection of message-producing events) is maintained on top of the raw log for efficient derivation and compaction.
10
+ ## Summary
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.
13
+
14
+ ## Table of Contents
6
15
 
7
- The optional `@deepseek-ai/dsh-session/invariant` companion registers this package's relational trace checks with `ctx.invariants`: monotonic sequence numbers, turn/step enclosure, and same-step tool call/result pairing. It replays existing sessions when loaded or reloaded; storage validation, snapshotting, freezing, cited source-event validation, and surface acceptance remain always-on responsibilities of the root session package.
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
8
22
 
9
- ## Service: `SessionStore` (ctx key: `sessions`)
23
+ -----
10
24
 
11
- Creates and holds event-sourced `Session` instances. Persistence is intentionally not implemented here — plugins subscribe to `session/event`, flush on `session/flush`, and may mirror the paired `session/created`/`session/disposed` lifecycle.
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
12
27
 
13
- ### Public API
28
+ Mount `dsh-session` wherever a session must exist. It creates and holds event-sourced `Session` instances in memory; durable storage is layered on by a persistence plugin that subscribes to the `session/event` feed.
14
29
 
15
- - `ctx.sessions.create(id?, { seed?, meta? }?)` validates and detaches durable seed/header data, fills the version and id, defaults `createdAt` to now, publishes the session, and binds it to the calling fiber. Persisted reconstruction supplies its original `createdAt`, `seedLength`, and `delegationDepth`.
16
- - `ctx.sessions.flush(session)` dispatches the awaited parallel durability checkpoint through the session's captured scope. Every listener starts and the call waits for all to settle before reporting failure; unpublished, detached, and stale objects reject.
17
- - `ctx.sessions.fork(source, boundary?, childSessionId?): Session` — Resolve a live session object or id, select a seed through the inclusive `boundary` event seq (default: current last event), require that prefix to end outside an open turn, and create a live child session with lineage metadata.
18
- - `ctx.sessions.get(id: SessionId): Session | undefined`
19
- - `ctx.sessions.list(): Session[]`
30
+ ### Create and inspect sessions
20
31
 
21
- #### Advanced: ordered-teardown lifecycle primitives
32
+ `ctx.sessions.create()` builds a live session bound to the calling fiber; `get(id)` and `list()` find sessions, and `fork()` creates a child session from a stable prefix of a live one.
22
33
 
23
- Use the split lifecycle only when teardown must be ordered with another resource:
34
+ ```text
35
+ const session = ctx.sessions.create(sessionId, { meta: { cwd: '/workspace' } })
36
+ ctx.sessions.get(sessionId) // the live session
37
+ ctx.sessions.list() // every live session, in creation order
38
+ ```
24
39
 
25
- - `prepare(id?, options?)` validates and constructs without publication.
26
- - `enter(session)` performs the collision check, publishes without announcing, and returns an entry-bound idempotent detach. Concurrent same-id preparations are allowed, but only one entry succeeds; a stale detach cannot remove its replacement.
27
- - `announce(session)` emits the single creation edge and rejects repeat or reentrant announcements. Detach during that dispatch is deferred and later emits the paired disposal edge; an unannounced entry emits neither lifecycle edge.
40
+ ### Append and derive
28
41
 
29
- `dsh-agent-loop` uses this split so final loop flush precedes session detach; see the [ownership Agent Note](../../../.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.md).
42
+ `session.append(type, data, opts?)` commits one typed event it snapshots and freezes the payload, validates it as lossless JSON, and notifies observers. `session.deriveMessages()` projects the log into the `Message[]` the model sees, incrementally and cached:
30
43
 
31
- ### Live service events
44
+ ```text
45
+ session.append('user/message', { role: 'user', content: [{ type: 'text', text: 'hello' }], source: { kind: 'user' } },
46
+ { surfaceOp: 'append' })
47
+ session.deriveMessages() // the derived model history
48
+ ```
32
49
 
33
- The store pairs announced creation with disposal, publishes post-commit append notifications with per-listener containment, and provides an awaited durability checkpoint. Exact signatures and scope behavior live in the generated region of [session.md](../../../docs/subsystems/session.md#cordis-surface); payloads live in the [persistence catalog](../../../docs/persistence-catalog.md).
50
+ Surface events (`user/message`, `assistant/message`, `tool/result`) must declare how they join the ordered surface; raw chunks, boundaries, and other log-only events never produce a message.
34
51
 
35
- ### Class: `Session`
52
+ ### Fork a session
36
53
 
37
- Plain class (not a Cordis Service). Create live sessions through `ctx.sessions.create()` and detached replay or inspection sessions through `Session.create()`; the detached factory does not publish lifecycle events or bind the session to a fiber.
54
+ `ctx.sessions.fork(source, boundary?, childSessionId?)` selects source events through an inclusive `boundary` seq (default: the current last event), requires the prefix to end outside an open turn, and creates a live child session with lineage metadata. A tool-time delegation that must branch mid-turn clips to a completed prefix instead.
38
55
 
39
- - `session.append(type, data, opts?)` snapshots and freezes durable data and surface metadata, validates marker shape, cited source-event seqs, complete replacement coverage, and content-only single-result `tool/result` rewrites, commits synchronously, then notifies observers with independent failure containment. Reentrant attached-session appends reject, and runtime checks cover widened unions and loaded logs.
40
- - `session.deriveMessages()` incrementally projects each new surface entry once and returns a fresh array over the complete identified, frozen messages stored by those entries. Assistant messages preserve the provider and model that produced them plus adapter-private replay state in their model source. A surface rewrite rebuilds the projection; there is no raw-log fallback.
41
- - `session.deriveEventMessage(event)` is the canonical per-event projection used by reconstruction and request checks.
42
- - `session.surface` exposes the readonly `SessionSurface` view owned by the session's single incremental surface manager; `replaceGeneration` changes on every committed rewrite.
43
- - `session.events` is a cached frozen snapshot invalidated by append; accepted events remain deeply frozen.
44
- - `session.seq`, `session.id` — current sequence and readonly typed identity.
45
- - `session.header: SessionHeader` — detached, deep-frozen creation metadata (`version`, `id`, `createdAt`, optional `cwd`/`parentSession`/`seedLength`/`delegationDepth`). Construction validates the durable record and requires its id to match `session.id`.
56
+ ### Flush durable state
46
57
 
47
- ### Lossless JSON utilities
58
+ `ctx.sessions.flush(session)` dispatches the awaited durability checkpoint: every persistence listener flushes and the call settles after all of them. A producer that needs an immediate durability barrier awaits it instead of assuming the write-behind drained.
48
59
 
49
- Durable values need one accepted representation, not a check followed by a second read. `isJsonValue(value)` is the boolean predicate; `snapshotJsonValue(value)` iteratively validates and copies a plain value in one pass, returning `undefined` for invalid input and propagating a throwing getter. The snapshot helper accepts finite JSON numbers except `-0` (JSON rewrites it to `0`), dense ordinary arrays, and plain or null-prototype objects; it rejects cycles, unsupported scalars, and exotic prototypes before normalization without imposing a call-stack depth limit.
60
+ -----
50
61
 
51
- Session-event import separates ownership from message validation. `snapshotSessionEvent(event)` clones a borrowed event before validating and freezing its identified message. `adoptSessionEvent(event)` performs the same message work in place and returns the original event; callers may use it only when they transfer an exclusively owned object graph with no mutable child shared with another event.
62
+ <a id="understand-the-implementation"></a>
63
+ ## Understand the implementation
52
64
 
53
- ### Chunk-row storage codec (`chunk-rows.ts`)
65
+ <details>
66
+ <summary>Implementation internals — click to expand</summary>
54
67
 
55
- The shared [storage codec](src/chunk-rows.ts) losslessly converts event sequences to compact rows and back. It preserves unrecognized events verbatim and rejects malformed encoded rows; persistence backends decide whether to enable packed writes.
68
+ This section explains how the package realizes the behavior above; the observable contract is covered in [Use this package](#use-this-package).
56
69
 
57
- ### Surface types
70
+ ### Design concept
58
71
 
59
- This package owns ordered surface projection, replacement validation, replay, and the type guards that distinguish append-origin from replacement events. The [surface type catalog](../../../docs/subsystems/session.md#surface-types) owns the exact shapes and field semantics. A human transcript must project append-origin events rather than `session.surface`, because landed replacements shadow history the reader already saw; model-facing consumers continue to read `session.surface`.
72
+ The package is built on event sourcing: a `Session` is an append-only log of typed `SessionEvent`s, and everything else — model history, transcripts, telemetry, titles, persistence — derives from that stream. The surface is a derived projection: an incremental manager validates append candidates, advances the ordered view from committed events, and tracks a `replaceGeneration` that bumps on every committed rewrite. Model-visible means logged: anything that reaches a model request must be reconstructable from the log. The shared [row codec](src/chunk-rows.ts) losslessly converts event sequences to compact rows and back, preserves unrecognized events verbatim, and rejects malformed rows. Persistence backends decide whether to pack writes; bounded history transports can use the same rows while retaining the complete logical interval and exact decoding for consumers that need token boundaries.
60
73
 
61
- ### Request-header reconstruction (`request-header.ts`)
74
+ ### Request headers
62
75
 
63
- `request/header` records a full canonical snapshot of the non-history request envelope with reason `initial`, `resume`, or `change`. Its optional `adapterDefaults` map marks effective `reasoningEffort` or `maxTokens` values materialized by exact-model resolution, allowing the next request proposal to distinguish them from explicit conversation settings. `foldRequestHeader()` selects the latest snapshot; legacy delta events and the removed `fallback` reason are rejected. See the [reconstructable-requests Agent Note](../../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md).
76
+ `request/header` stores a full canonical snapshot of the non-history request envelope with reason `initial`, `resume`, `change`, or `series`. An explicit message-series start or a surface replacement writes a `series` snapshot when the envelope is unchanged; a simultaneous change uses `startsSeries: true`. Same-series steps, retries, and ordinary later turns inherit the latest snapshot. `adapterDefaults` distinguishes values resolved by the adapter from explicit settings, and `foldRequestHeader()` selects the latest snapshot. This self-contained record supports partial-window rendering and exact reconstruction at the cost of growth per message series; the [reconstructable-requests Agent Note](../../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md) owns the detail.
64
77
 
65
- A `user/message` stores the complete `UserMessage` directly, including the identity created before inbox routing or step entry. It renders its `content` verbatim whether it is a direct human prompt, a synthetic injection, or an entered goal round; its typed `source` is the only channel that tells them apart and carries any domain-specific durable facts. `assistant/message` and `tool/result` likewise store complete message values. Turn execution remains enclosed by `turn/start` and `turn/end`; `agent.inject()` queues input until a later pre-step claims it and returns it in an enter decision.
78
+ ### Source map
66
79
 
67
- `tool/result` persists one identified user-role tool-result message, optional internal failure identity, and optional presentation metadata. A tool's successful canonical `value` and human-readable canonical failure message remain execution-local; rendered error content is the replay-authoritative message.
80
+ | File | Role |
81
+ |---|---|
82
+ | [`src/index.ts`](src/index.ts) | Plugin entry: `SessionStore` service, store lifecycle, `fork`, `flush` |
83
+ | [`src/types.ts`](src/types.ts) | `SessionEventMap`, `SessionEvent`, `UserMessage`, `SessionHeader`, `TurnEndReasonMap` |
84
+ | [`src/surface.ts`](src/surface.ts) | Ordered surface projection, replacement validation, `deriveEventMessage` |
85
+ | [`src/request-header.ts`](src/request-header.ts) | `request/header` folding and reconstruction |
86
+ | [`dsh-util-values`](../../util/values/README.md) | Shared lossless JSON validation and detached snapshots |
87
+ | [`src/chunk-rows.ts`](src/chunk-rows.ts) | Shared compact-row storage codec for persistence backends |
88
+ | [`src/repair.ts`](src/repair.ts) | Cold repair of crash-orphaned logs |
89
+ | [`src/invariant.ts`](src/invariant.ts) | Invariant companion: seq, turn/step enclosure, tool call/result pairing |
68
90
 
69
- ### Session event vocabulary (`types.ts`)
91
+ ### Append validation
70
92
 
71
- The generated [persistence log event catalog](../../../docs/persistence-catalog.md) enumerates each append-only event type with its payload, surface badge, and declaration site. Token accounting reads per-step `assistant/chunk { type: 'usage' }` records and treats `assistant/message.usage` as the committed-step fallback when no usage chunk exists; failed model-request attempts have no assistant message. Each `assistant/message` records the provider, model, and optional replay state.
93
+ Every append uses the shared iterative `snapshotJsonValue()` pass, which reads, validates, and copies each nested value once, so a stateful getter cannot supply one value to validation and another to storage. Non-lossless-JSON payloads (BigInt, cycles, sparse arrays, `-0`, exotic prototypes) are rejected at the append site, before any backend flush. Surface events additionally validate marker shape, cited source-event seqs, and complete shadowed-node coverage for replacements.
72
94
 
73
- Merge-extensible via `SessionEventMap` — a plugin declaration-merges its own types (the compaction seam's `compaction/*`, bounded recovery's non-surface `llm/retry`, the hook bridges' `hook/*`); merged members appear in the same catalog. A plugin owns the relational invariant for its merged events, including whether a log-only event may appear between turns. A producer that requires durability appends through `Session` and then awaits `ctx.sessions.flush(session)` without fabricating an execution turn.
95
+ ### Derived history
74
96
 
75
- Also defines `TurnEndReasonMap`, the merge-extensible `kind`-tagged sum type for turn endings. `turn/start` carries only the turn number; the following entered `user/message` batch records its input, while `llm/retry` records request recovery.
97
+ `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. A surface rewrite rebuilds the projection — there is no raw-log fallback, so the surface is the single source of derived history.
76
98
 
77
- An interrupted live turn ends with `{ kind: 'aborted', reason: AgentCancelCause }`, preserving the typed cancellation cause in the durable transcript. Persistence imports the coarse aborted outcome from the supported older format as `{ kind: 'aborted', reason: { kind: 'legacy' } }`, because that record did not retain its caller. A turn failure carries `{ kind: 'error', error }`; crash recovery alone synthesizes `{ kind: 'interrupted' }`.
99
+ ### The request header
78
100
 
79
- Every `SessionEvent` carries three optional top-level fields (structural metadata):
101
+ 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.
80
102
 
81
- - `sourceEventSeqs?: number[]` — seq numbers of earlier events cited as sources (e.g., the `assistant/chunk` seqs behind an `assistant/message`, or the shadowed entries behind a compaction replacement entry). On `assistant/message`, a present `[]` records a known empty provider stream, while omission means a legacy or foreign event did not record the source stream; other surface events require a non-empty list when this field is present.
82
- - `surfaceOp?: SurfaceOp` — how this event entered the surface. Absent for non-surface events (boundaries, chunks, usage, errors).
83
- - `ignorable?: true` — marks an event a reader may safely skip when it does not recognize the type; absent means required, so an unknown-type event refuses session reconstruction ([mechanism](../../../.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md)).
103
+ </details>
84
104
 
85
- ### Metadata types (`types.ts`)
105
+ -----
86
106
 
87
- - `SessionHeader` — session metadata written once when published as `Session.header`, where detachment and deep-freezing enforce immutability at runtime: `{ version, id, createdAt, cwd?, parentSession?, seedLength?, delegationDepth? }`. Persistence loaders may return mutable detached copies of the same data type. Owned here (beside `SessionId`) because `Session.header` is typed by it; persistence backends re-export it rather than own it (which would force a package cycle).
107
+ <a id="further-exploration"></a>
108
+ ## Further Exploration
88
109
 
89
- ### Extension points
110
+ The package-level contract is enough for most consumers; read these when you need the surrounding domain.
90
111
 
91
- - Persistence plugins: subscribe to `session/event` (write-behind) and drain on `session/flush` (awaited) and fiber dispose. A durable backend reads the log and reloads it into a live session; the metadata contract (`SessionHeader`, `session.header`) is what such a backend stores beside the log.
92
- - Replay/fork: `create(id, { seed })` validates and freezes a contiguous current-format log and rebuilds its surface; request headers require provider/model, and assistant messages require provider/model provenance. Persistence owns read compatibility before constructing this current-format seed. `fork(source, boundary?, childSessionId?)` selects a completed-turn prefix and records lineage.
93
- - Compaction: `dsh-compaction-basic` appends a `user/message` replacement for summary checkpoints, while `dsh-compaction-tool-result-pruner` appends a content-only `tool/result` replacement. Tool-pairing boundary policy and its cache belong to the [`dsh-compaction` seam](../../compaction/compaction/README.md), while this package owns ordered surface membership, replacement validation, and `replaceGeneration`.
112
+ - [Session subsystem](../../../docs/subsystems/session.md) the full event vocabulary, surface types, and generated service API.
113
+ - [Persistence subsystem](../../../docs/subsystems/persistence.md) how backends make this log durable.
114
+ - [Core subsystem](../../../docs/subsystems/core.md) the loop that writes and derives from sessions.
115
+ - [Generated persistence catalog](../../../docs/persistence-catalog.md) — every session event with its payload and declaration site.
116
+ - [Core group map](../README.md) — how the core packages compose.
94
117
 
118
+ -----
119
+
120
+ <a id="model-experience"></a>
95
121
  ## Model Experience
96
122
 
97
123
  ### Derived message history
98
124
 
99
125
  #### What the model sees
100
126
 
101
- The model receives the complete messages from `user/message`, `assistant/message`, and `tool/result` surface entries verbatim. Their identities, roles, sources, and content blocks are the same values established at creation; projections do not mint identities. Direct prompts and injected context remain separate `user/message` events whose sources preserve their provenance. A prompt envelope changes only human presentation; its prefix context and request delimiter are already present in the event content. Tool calls live inside assistant messages. Chunks, boundaries, usage, hook records, todo records, and other log-only events add no message.
127
+ 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. Chunks, boundaries, usage, and other log-only events add no message.
102
128
 
103
129
  #### Token effect
104
130
 
@@ -138,7 +164,22 @@ Logging causes no invalidation, and exact reconstruction preserves request-prefi
138
164
 
139
165
  ## Known Limitations and Deferred Work
140
166
 
141
- - **Session branching/tree** (pi-style entry tree) — deferred unless needed beyond boundary-based `fork()`.
167
+ <a id="known-limitations-and-deferred-work"></a>
168
+
169
+
170
+ These limits define when the session store needs special care. They are current package constraints, not a task backlog.
171
+
142
172
  - **`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](../../../.agents/notes/implemented/feature/2026-06-30-session-store-fork-api.md).
143
- - **`SESSION_FORMAT_VERSION` stays pinned at `0`** — pre-release, no broad compatibility implied: `Session` accepts only current seed shapes, and a backend refuses any other version naming the direction (newer: "written by a newer harness — upgrade"; older: no upgrade path ships yet). Unknown event types refuse the same way unless marked `ignorable` in the envelope; the versioning mechanism is the [session-log-version-mechanism note](../../../.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md). Narrow storage import upgrades belong to the persistence boundary ([policy](../../../AGENTS.md), [pre-identity message recovery](../../../.agents/notes/implemented/bug-fix/2026-07-28-load-pre-identity-session-messages.md)).
173
+ - **`SESSION_FORMAT_VERSION` stays pinned at `0`** — pre-release, no broad compatibility implied: `Session` accepts only current seed shapes, a backend refuses any other version, and unknown event types refuse reconstruction unless marked `ignorable` in the envelope ([mechanism](../../../.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md)).
144
174
  - **`TurnEndReasonMap` omits the ACP-named `refusal` / `max_turn_requests` variants** — producer-gated: they land when an adapter or the loop first emits them.
175
+ - **No session tree beyond fork** — a pi-style entry tree over branched sessions is deferred unless a consumer needs more than boundary-based forking.
176
+
177
+ <a id="dev-note"></a>
178
+ ### Dev Note
179
+
180
+ <details>
181
+ <summary>Working context for maintainers — click to expand</summary>
182
+
183
+ None.
184
+
185
+ </details>
package/README.zh.md CHANGED
@@ -1,104 +1,130 @@
1
- # dsh-session
1
+ ---
2
+ description: "面向用户与维护者的事件溯源会话日志与内存存储说明,用于构建、检查或扩展每个 agent 交互背后的持久记录。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @deepseek-ai/dsh-session
2
7
 
3
8
  [English](README.md) | 中文
4
9
 
5
- 事件溯源的会话日志和内存存储。`Session` 是 agent(智能体)全部交互历史的仅追加真源,LLM(大语言模型)消息历史由它*派生*。原始日志之上维护一个 **surface** 层(产生消息事件的有序投影),以便高效派生和压缩(compaction)。
10
+ ## 概述
11
+
12
+ `dsh-session` 提供仅追加的会话日志,记录 agent(智能体)的完整交互历史——每个模型可见事实都流经的单一真源。LLM(大语言模型)消息历史由日志*派生*(`deriveMessages()`),从不另行存储,因此回放就是对同一批事件重新派生,压缩(compaction)也可以遮蔽较旧的表层条目而不删除历史。该包还提供内存存储(`ctx.sessions`)、插件通过声明合并扩展的类型化 `SessionEvent` 词汇,以及为产生消息的事件排序的 surface 层。持久化刻意是独立关注点:后端订阅 `session/event` 并在 `session/flush` 时刷新。作为任何 agent 会话的基础时请选择本包;它本身不运行模型调用。
13
+
14
+ ## 目录
6
15
 
7
- 可选配套入口 `@deepseek-ai/dsh-session/invariant` 将此包的关系轨迹检查注册到 `ctx.invariants`:序号单调递增、轮次/步骤闭合,以及同一步骤内的工具调用/结果配对。加载或重新加载时,它会回放现有会话;存储校验、快照、冻结、被引用的源事件校验和 surface 准入仍始终由根会话包负责。
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
8
22
 
9
- ## 服务:`SessionStore`(ctx 键:`sessions`)
23
+ -----
10
24
 
11
- 创建并持有事件溯源的 `Session` 实例。这里有意不实现持久化:插件订阅 `session/event`,在 `session/flush` 时刷新,并可镜像成对的 `session/created`/`session/disposed` 生命周期。
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
12
27
 
13
- ### 公共 API
28
+ 在必须存在会话的任何地方挂载 `dsh-session`。它在内存中创建并持有事件溯源的 `Session` 实例;持久存储由订阅 `session/event` 流的持久化插件叠加。
14
29
 
15
- - `ctx.sessions.create(id?, { seed?, meta? }?)` 校验持久种子/头部数据并生成脱离副本,补齐版本和 id,在未提供 `createdAt` 时使用当前时间,发布会话并将其绑定到调用方 fiber。持久化重建会提供原始的 `createdAt`、`seedLength` 和 `delegationDepth`。
16
- - `ctx.sessions.flush(session)` 通过会话捕获的作用域分发一个需等待完成的并行持久性检查点。每个监听器都会启动;调用会等待全部结算后才报告失败。未发布、已脱离和陈旧的对象会被拒绝。
17
- - `ctx.sessions.fork(source, boundary?, childSessionId?): Session`:解析实时会话对象或 id,选取截至 `boundary` 事件序号(含该事件)的种子(默认为当前最后一个事件),要求所选前缀结束时没有开放轮次,再创建带谱系元数据的实时子会话。
18
- - `ctx.sessions.get(id: SessionId): Session | undefined`
19
- - `ctx.sessions.list(): Session[]`
30
+ ### 创建与检查会话
20
31
 
21
- #### 高级:有序清理生命周期原语
32
+ `ctx.sessions.create()` 构建绑定到调用方 fiber 的实时会话;`get(id)` 与 `list()` 查找会话,`fork()` 从实时会话的稳定前缀创建子会话。
22
33
 
23
- 仅在清理必须与另一项资源排序时使用拆分生命周期:
34
+ ```text
35
+ const session = ctx.sessions.create(sessionId, { meta: { cwd: '/workspace' } })
36
+ ctx.sessions.get(sessionId) // the live session
37
+ ctx.sessions.list() // every live session, in creation order
38
+ ```
24
39
 
25
- - `prepare(id?, options?)` 校验并构造,但不发布。
26
- - `enter(session)` 执行冲突检查,在不通知的情况下发布,并返回一个绑定到该条目的幂等脱离函数。允许并发准备相同 id,但只有一个条目能够成功进入;陈旧的脱离函数无法移除其替代项。
27
- - `announce(session)` 发出唯一一次创建边,并拒绝重复或重入通知。该次分发期间请求的脱离操作会延后,之后再发出成对的释放边;未通知的条目不会发出任何生命周期边。
40
+ ### 追加与派生
28
41
 
29
- `dsh-agent-loop` 使用这一拆分,以保证循环的最终刷新先于会话脱离;详见[所有权 Agent Note](../../../.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.zh.md)。
42
+ `session.append(type, data, opts?)` 提交一个类型化事件——它先快照并冻结载荷、校验其为无损 JSON,再通知观察者。`session.deriveMessages()` 把日志投影为模型看到的 `Message[]`,采用增量且有缓存的方式:
30
43
 
31
- ### 实时服务事件
44
+ ```text
45
+ session.append('user/message', { role: 'user', content: [{ type: 'text', text: 'hello' }], source: { kind: 'user' } },
46
+ { surfaceOp: 'append' })
47
+ session.deriveMessages() // the derived model history
48
+ ```
32
49
 
33
- 会话存储会将已通知的创建与释放配对,在提交后发布追加通知并逐个监听器收容失败,同时提供受等待的持久性检查点。确切签名和作用域行为见 [session.md](../../../docs/subsystems/session.zh.md#cordis-surface) 的生成区块;载荷见[持久化目录](../../../docs/persistence-catalog.zh.md)。
50
+ 表层事件(`user/message`、`assistant/message`、`tool/result`)必须声明如何进入有序 surface;原始分片、边界与其他仅日志事件从不产生消息。
34
51
 
35
- ### 类:`Session`
52
+ ### 派生会话的 fork
36
53
 
37
- 普通类(不是 Cordis 服务)。活跃会话通过 `ctx.sessions.create()` 创建,脱离态的回放或检查会话通过 `Session.create()` 创建;脱离态工厂不会发布生命周期事件,也不会将会话绑定到 fiber。
54
+ `ctx.sessions.fork(source, boundary?, childSessionId?)` 选取截至 `boundary` 事件序号(含该事件)的源事件(默认:当前最后一个事件),要求所选前缀结束时没有开放轮次,再创建带谱系元数据的实时子会话。必须在轮次中途分支的工具时委派会裁剪到已完成前缀。
38
55
 
39
- - `session.append(type, data, opts?)` 会为持久数据和 surface 元数据制作快照并冻结它们,校验标记形态、被引用的源事件 seq、替换覆盖完整性,以及仅修改内容的单个 `tool/result` 重写,随后同步提交,再在彼此独立的失败收容下通知观察者。对已挂接会话的重入追加会被拒绝,运行时检查也覆盖扩宽后的联合类型和已加载日志。
40
- - `session.deriveMessages()` 对每个新的 surface 条目只做一次增量投影,并返回一个新数组,其中包含这些条目存储的完整、带标识且冻结的消息。assistant 消息的模型来源会保留生成该消息的提供方和模型,以及适配器私有回放状态。surface 重写会重建投影;不存在原始日志回退。
41
- - `session.deriveEventMessage(event)` 是重建和请求检查使用的规范逐事件投影。
42
- - `session.surface` 暴露只读 `SessionSurface` 视图,由会话唯一的增量 surface 管理器所有;每次提交重写,`replaceGeneration` 都会变化。
43
- - `session.events` 是按追加失效的缓存冻结快照;已接受事件保持深度冻结。
44
- - `session.seq`、`session.id`:当前序号和只读类型化身份。
45
- - `session.header: SessionHeader`:脱离、深冻结的创建元数据(`version`、`id`、`createdAt`,以及可选的 `cwd`/`parentSession`/`seedLength`/`delegationDepth`)。构造时会校验持久记录,并要求其中的 id 与 `session.id` 一致。
56
+ ### 刷新持久状态
46
57
 
47
- ### 无损 JSON 工具
58
+ `ctx.sessions.flush(session)` 分发需等待完成的持久性检查点:每个持久化监听器都会刷新,调用在所有监听器结算后完成。需要立即持久性屏障的生产方应等待它,而不是假定写后刷新已完成。
48
59
 
49
- 持久值需要一种已接受的表示,不能先检查再二次读取。`isJsonValue(value)` 是布尔判断函数;`snapshotJsonValue(value)` 在一趟迭代中校验并复制普通值,无效输入返回 `undefined`,getter 抛出的异常则向外传播。快照辅助函数接受除 `-0` 外的有限 JSON 数值(JSON 会将其改写为 `0`)、稠密普通数组、普通对象或 null 原型对象;它会在规范化前拒绝循环引用、不支持的标量和特殊原型,同时不施加调用栈深度限制。
60
+ -----
50
61
 
51
- 会话事件导入将所有权与消息校验分开处理。`snapshotSessionEvent(event)` 会先克隆借用的事件,再校验并冻结其中带标识的消息。`adoptSessionEvent(event)` 原地执行相同的消息处理并返回原事件;调用方只有在移交独占的对象图,且该对象图没有与其他事件共享可变子对象时,才可以使用此函数。
62
+ <a id="understand-the-implementation"></a>
63
+ ## 理解实现
52
64
 
53
- ### 分片行存储编解码器(`chunk-rows.ts`)
65
+ <details>
66
+ <summary>实现细节——点击展开</summary>
54
67
 
55
- 共享的[存储编解码器](src/chunk-rows.ts)在事件序列与紧凑行之间无损转换。它会逐字保留无法识别的事件,并拒绝形态错误的编码行;是否启用打包写入由持久化后端决定。
68
+ 本节解释该包如何实现上述行为;可观察约定已在[使用本包](#use-this-package)中完整说明。
56
69
 
57
- ### Surface 类型
70
+ ### 设计理念
58
71
 
59
- 此包拥有有序 surface 投影、替换校验、回放,以及区分追加来源事件与替换事件的类型守卫。[surface 类型目录](../../../docs/subsystems/session.zh.md#surface-types)拥有精确形状与字段语义。面向人的 transcript(文本记录)必须投影追加来源事件,而不是 `session.surface`,因为已落地的替换会遮蔽读者已经看到的历史;面向模型的消费方继续读取 `session.surface`。
72
+ 该包建立在事件溯源之上:`Session` 是类型化 `SessionEvent` 的仅追加日志,其他一切——模型历史、transcript、遥测、标题、持久化——都从这条流派生。surface 是派生投影:一个增量管理器校验追加候选、根据已提交事件推进有序视图,并跟踪每次已提交重写都会递增的 `replaceGeneration`。模型可见即已记录:任何到达模型请求的内容都必须能从日志重建。共享的[行编解码器](src/chunk-rows.ts)在事件序列与紧凑行之间无损转换,逐字保留无法识别的事件,并拒绝形态错误的行。持久化后端决定是否打包写入;有界历史传输可以使用同一种行,同时保留完整逻辑区间,并为需要 token 边界的消费方提供精确解码。
60
73
 
61
- ### 请求头重建(`request-header.ts`)
74
+ ### 请求 header
62
75
 
63
- `request/header` 记录非历史请求封装的完整规范快照,其原因为 `initial`、`resume` 或 `change`。其可选 `adapterDefaults` 映射会标记由精确模型解析填入的生效 `reasoningEffort` `maxTokens` 值,使下一次请求提议能够将它们与显式对话设置区分开。`foldRequestHeader()` 选择最新快照;旧版增量事件和已移除的 `fallback` 原因会被拒绝。详见[可重建请求 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md)
76
+ `request/header` 存储非历史请求 envelope 的完整规范快照,原因为 `initial`、`resume`、`change` 或 `series`。显式消息序列起点或表层替换会在 envelope 不变时写入 `series` 快照;同时发生变化时使用 `startsSeries: true`。同一序列内的步骤、重试与普通后续轮次继承最新快照。`adapterDefaults` 区分由适配器解析的值与显式设置,`foldRequestHeader()` 选择最新快照。这种自包含记录以每个消息序列增加存储为代价,支持局部窗口渲染与精确重建;细节由[可重建请求 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md)负责。
64
77
 
65
- `user/message` 会直接存储完整的 `UserMessage`,其中包括收件箱路由或进入步骤前创建的标识。无论它是直接人类提示词、合成注入,还是已进入的 Goal Round,都会原样呈现其 `content`;带类型的 `source` 是区分三者的唯一通道,并携带各领域专有的持久事实。`assistant/message` 和 `tool/result` 也会存储完整的消息值。轮次执行仍由 `turn/start` 与 `turn/end` 包围;`agent.inject()` 会把输入排队,直到后续某次 pre-step 领取它,并在 enter 决策中返回它。
78
+ ### 源码地图
66
79
 
67
- `tool/result` 持久保存一条带标识、user-role 的工具结果消息,以及可选内部失败标识和可选呈现元数据。工具成功时的规范 `value` 和便于人类阅读的规范失败消息只存在于执行本地;渲染后的错误内容是回放权威消息。
80
+ | 文件 | 职责 |
81
+ |---|---|
82
+ | [`src/index.ts`](src/index.ts) | 插件入口:`SessionStore` 服务、存储生命周期、`fork`、`flush` |
83
+ | [`src/types.ts`](src/types.ts) | `SessionEventMap`、`SessionEvent`、`UserMessage`、`SessionHeader`、`TurnEndReasonMap` |
84
+ | [`src/surface.ts`](src/surface.ts) | 有序 surface 投影、替换校验、`deriveEventMessage` |
85
+ | [`src/request-header.ts`](src/request-header.ts) | `request/header` 折叠与重建 |
86
+ | [`dsh-util-values`](../../util/values/README.zh.md) | 共享无损 JSON 校验与分离式快照 |
87
+ | [`src/chunk-rows.ts`](src/chunk-rows.ts) | 供持久化后端使用的共享紧凑行存储编解码器 |
88
+ | [`src/repair.ts`](src/repair.ts) | 崩溃遗留日志的冷修复 |
89
+ | [`src/invariant.ts`](src/invariant.ts) | 不变式配套:序号、轮次/步骤闭合、工具调用/结果配对 |
68
90
 
69
- ### 会话事件词汇(`types.ts`)
91
+ ### 追加校验
70
92
 
71
- 生成的[持久化日志事件目录](../../../docs/persistence-catalog.zh.md)逐成员列举仅追加日志的事件类型、载荷、surface 标记与声明位置。Token 记账读取每个步骤的 `assistant/chunk { type: 'usage' }` 记录;如果没有用量分片,则将 `assistant/message.usage` 作为已提交步骤的后备。失败的模型请求尝试没有 assistant 消息。每条 `assistant/message` 都会记录提供方、模型和可选回放状态。
93
+ 每次追加都会使用共享的迭代式 `snapshotJsonValue()` 流程,对每个嵌套值只读取、校验并复制一次,因此有状态的 getter 无法给校验提供一个值、给存储提供另一个值。非无损 JSON 载荷(BigInt、循环、稀疏数组、`-0`、特殊原型)会在追加位置被拒绝,先于任何后端刷新。表层事件还会校验标记形态、被引用的源事件 seq,以及替换的完整遮蔽节点覆盖。
72
94
 
73
- `SessionEventMap` 可通过合并扩展:插件使用声明合并添加自身类型(压缩 seam 的 `compaction/*`、有界恢复的非 surface `llm/retry`、钩子桥接层的 `hook/*`);合并成员会出现在同一目录中。插件拥有其合并事件的关系不变量,包括是否允许纯日志事件出现在轮次之间。需要持久性的生产方通过 `Session` 追加,再等待 `ctx.sessions.flush(session)`,无需虚构一个执行轮次。
95
+ ### 派生历史
74
96
 
75
- 此包还定义 `TurnEndReasonMap`,即用于轮次结束、可合并扩展且以 `kind` 为标签的和类型。`turn/start` 只携带轮次编号;随后已进入的 `user/message` 批次记录其输入,`llm/retry` 则记录请求恢复。
97
+ `deriveMessages()` 把每个 surface 节点的投影缓存一次,每次调用都返回共享、深度冻结消息之上的新数组;三种 surface 事件类型(`user/message`、`assistant/message`、`tool/result`)各自投影自己的消息种类——user 内容原样、带提供方与模型的组装 assistant 消息,或 user 角色的工具结果。surface 重写会重建投影——不存在原始日志回退,因此 surface 是派生历史的唯一来源。
76
98
 
77
- 被中断的实时轮次以 `{ kind: 'aborted', reason: AgentCancelCause }` 结束,在持久 transcript 中保留类型化取消原因。持久化会将受支持旧格式中的粗粒度中止结果导入为 `{ kind: 'aborted', reason: { kind: 'legacy' } }`,因为该记录没有保留调用方。轮次失败携带 `{ kind: 'error', error }`;只有崩溃恢复会合成 `{ kind: 'interrupted' }`。
99
+ ### 请求头
78
100
 
79
- 每个 `SessionEvent` 都有三个可选顶层字段(结构元数据):
101
+ 循环在每个循环实例边界及变更时记录完整规范 `request/header` 快照(调用配置、适配器默认值、渲染后的系统提示词、组装后的工具 schema);`foldRequestHeader(events)` 通过选择最新快照来重建它,使每个对话请求都成为日志的纯函数。路由元数据(`request/context`)是独立的已记录状态,仅在提供方、模型或容量变化时追加。
80
102
 
81
- - `sourceEventSeqs?: number[]`:被引用为来源的较早事件 seq(例如 `assistant/message` 引用的 `assistant/chunk` seq,或压缩替换条目引用的已遮蔽条目)。对于 `assistant/message`,存在的 `[]` 表示已知提供方流为空;省略则表示旧版或外部事件没有记录源流。其他 surface 事件若有此字段,则要求非空列表。
82
- - `surfaceOp?: SurfaceOp`:事件进入 surface 的方式。非 surface 事件(边界、分片、用量、错误)不含该字段。
83
- - `ignorable?: true`:标记读取器在不认识事件类型时可以安全跳过该事件;缺失表示必需,不认识的事件类型会使会话重建被拒绝([机制](../../../.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.zh.md))。
103
+ </details>
84
104
 
85
- ### 元数据类型(`types.ts`)
105
+ -----
86
106
 
87
- - `SessionHeader`:会话元数据,在发布为 `Session.header` 时写入一次;脱离和深冻结保证运行时不可变:`{ version, id, createdAt, cwd?, parentSession?, seedLength?, delegationDepth? }`。持久化 loader 可返回相同数据类型的可变脱离副本。该类型与 `SessionId` 一同归此包所有,因为 `Session.header` 以它为类型;持久化后端只是重新导出而不拥有它,否则会形成包循环依赖。
107
+ <a id="further-exploration"></a>
108
+ ## 进一步探索
88
109
 
89
- ### 扩展点
110
+ 包级约定对大多数消费方已经足够;需要周边领域时再阅读以下页面。
90
111
 
91
- - 持久化插件:订阅 `session/event`(延后写入),并在 `session/flush`(受等待)及 fiber dispose(资源释放)时排空。持久后端读取日志并重新加载到实时会话;这类后端会把元数据约定(`SessionHeader`、`session.header`)与日志一同存储。
92
- - 回放/fork:`create(id, { seed })` 校验并冻结连续的当前格式日志,再重建 surface;请求头必须包含提供方/模型,assistant 消息必须包含提供方/模型溯源信息。持久化层在构造该当前格式 seed 前负责读取兼容性处理。`fork(source, boundary?, childSessionId?)` 选择已完成轮次前缀并记录谱系。
93
- - 压缩:`dsh-compaction-basic` 为摘要检查点追加一个替换用 `user/message`,而 `dsh-compaction-tool-result-pruner` 追加仅修改内容的 `tool/result` 替换。工具配对边界策略及其缓存归 [`dsh-compaction` seam](../../compaction/compaction/README.zh.md) 所有;此包拥有有序 surface 成员关系、替换校验与 `replaceGeneration`。
112
+ - [会话子系统](../../../docs/subsystems/session.zh.md)——完整事件词汇、surface 类型与生成的服务 API。
113
+ - [持久化子系统](../../../docs/subsystems/persistence.zh.md)——后端如何让该日志持久化。
114
+ - [Core 子系统](../../../docs/subsystems/core.zh.md)——写入并派生会话的循环。
115
+ - [生成持久化目录](../../../docs/persistence-catalog.zh.md)——每个会话事件及其载荷与声明位置。
116
+ - [core 分组地图](../README.zh.md)——core 各包如何组合。
94
117
 
118
+ -----
119
+
120
+ <a id="model-experience"></a>
95
121
  ## 模型体验
96
122
 
97
123
  ### 派生消息历史
98
124
 
99
- #### 模型看到的内容
125
+ #### 模型看到什么
100
126
 
101
- 模型会原样接收 `user/message`、`assistant/message` `tool/result` surface 条目中的完整消息。其标识、角色、来源和内容块都与创建时确定的值相同;投影不会生成标识。直接提示词与注入上下文仍是彼此独立的 `user/message` 事件,各事件的来源会保留其出处。提示词封装只改变面向人的呈现;其前缀上下文和请求分隔符已经位于事件内容中。工具调用包含在 assistant 消息内。分片、边界、用量、钩子记录、todo 记录以及其他仅日志事件不会添加消息。
127
+ 模型会原样接收 `user/message`、`assistant/message` `tool/result` surface 条目中的完整消息——标识、角色、来源与内容块都与创建时确定的值相同,投影从不生成标识。直接提示词与注入上下文仍是彼此独立的 `user/message` 事件,各事件的来源会保留其出处。分片、边界、用量与其他仅日志事件不会添加消息。
102
128
 
103
129
  #### Token 影响
104
130
 
@@ -110,7 +136,7 @@
110
136
 
111
137
  ### 崩溃修复结果
112
138
 
113
- #### 模型看到的内容
139
+ #### 模型看到什么
114
140
 
115
141
  如果恢复发现 assistant 工具请求没有持久 `tool/call`,其合成 `TOOL_NOT_STARTED` 结果内容为 `The tool call was interrupted before the Harness recorded it as started. Retry it if it is still needed.`。如果持久 `tool/call` 没有结果,其 `TOOL_OUTCOME_UNKNOWN` 结果内容为 `The tool call was interrupted after it was recorded, but no result was durably recorded. Its outcome is unknown. Decide whether to retry from the tool semantics: retry only if the operation is read-only or idempotent; if it may have side effects, first verify external state or ask the user. Do not retry blindly.`。
116
142
 
@@ -124,21 +150,36 @@
124
150
 
125
151
  ### 已记录的请求头
126
152
 
127
- #### 模型看到的内容
153
+ #### 模型看到什么
128
154
 
129
- 会话会重建循环实际发送的系统提示词、工具 schema、调用配置和会话前缀。请求头事件不会向消息历史加入第二份副本;前缀在 `deriveMessages()` 外部前置。
155
+ 会话会重建循环实际发送的系统提示词、工具 schema、调用配置与会话前缀。请求头事件不会向消息历史加入第二份副本;前缀在 `deriveMessages()` 外部前置。
130
156
 
131
157
  #### Token 影响
132
158
 
133
- 日志记录不产生重复 token。重建的前缀、系统文本和 schema 仍会产生正常的逐请求开销。
159
+ 日志记录不产生重复 token。重建的前缀、系统文本与 schema 仍会产生正常的逐请求开销。
134
160
 
135
161
  #### KV Cache 影响
136
162
 
137
163
  记录日志不会导致失效,精确重建会保持请求前缀一致。后续请求头若更改前缀、提示词或 schema,可能从第一处差异开始使复用失效。
138
164
 
139
- ## 已知限制与暂缓事项
165
+ ## 已知限制与延期工作
166
+
167
+ <a id="known-limitations-and-deferred-work"></a>
168
+
169
+
170
+ 这些限制说明会话存储何时需要特别留意。它们是当前包约束,不是任务积压。
140
171
 
141
- - **会话分支/树结构**(pi 风格条目树):除非需要超越基于边界的 `fork()` 能力,否则暂缓。
142
172
  - **`fork()` 仅在实时会话的稳定边界处切分**:所选前缀结束时不得有开放轮次,且源会话必须位于存储中;[fork API](../../../.agents/notes/implemented/feature/2026-06-30-session-store-fork-api.zh.md) 不支持对已持久化但未加载的会话进行 fork。
143
- - **`SESSION_FORMAT_VERSION` 固定为 `0`**:预发布阶段不承诺广泛兼容性;`Session` 只接受当前 seed 形状,后端拒绝其他任何版本并说明方向(更新的版本提示"由更新的 harness 写入,请升级";更旧的版本说明尚无升级路径)。不认识的事件类型同样被拒绝,除非信封带 `ignorable` 标记;版本机制见 [session-log 版本机制 Agent Note](../../../.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.zh.md)。范围受限的存储导入升级应由持久化边界负责([政策](../../../AGENTS.md)、[消息标识机制引入前的消息恢复](../../../.agents/notes/implemented/bug-fix/2026-07-28-load-pre-identity-session-messages.zh.md))。
173
+ - **`SESSION_FORMAT_VERSION` 固定为 `0`**:预发布阶段不承诺广泛兼容性;`Session` 只接受当前 seed 形状,后端拒绝任何其他版本,不认识的事件类型也会拒绝重建,除非信封带 `ignorable` 标记([机制](../../../.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.zh.md))。
144
174
  - **`TurnEndReasonMap` 不含 ACP(Agent Client Protocol)命名的 `refusal`/`max_turn_requests` 变体**:受生产方约束;只有当适配器或循环首次产生这些变体时才加入。
175
+ - **fork 之外没有会话树**:基于分支会话的 pi 风格条目树被推迟,除非消费方需要超越基于边界的 forking 的能力。
176
+
177
+ <a id="dev-note"></a>
178
+ ### 开发备注
179
+
180
+ <details>
181
+ <summary>维护者的工作上下文——点击展开</summary>
182
+
183
+ 无。
184
+
185
+ </details>