|
|
@@ -8,13 +8,13 @@ English | [中文](2026-08-09-client-conversation-node-assembly.zh.md)
|
|
|
|
|
|
Client Session owned transport windows, connection state, and pending interactions while also interpreting Assistant, Tool, message, command, compaction, retry, and turn-tail events in a centralized transcript fold. Adding one business node required changes to Session switches, history replay, indexes, caches, and React grouping; business identity, state evolution, and final presentation had no independent owner.
|
|
|
|
|
|
-The old path also placed running Assistant and Tool values outside the finalized flow. They entered the log-ordered node list only after settlement, so their React parent changed and remounted them even when the business ID and `key` remained stable. Full history loads, older prepends, live appends, and token streaming used separate update paths, leaving reference stability and local recomputation dependent on specialized caches spread across the client.
|
|
|
+Without target-neutral assembly, running Assistant and Tool values sit outside the finalized flow and enter the log-ordered node list only after settlement. Their React parent then changes and remounts them even when the business ID and `key` remain stable. Separate update paths for full history loads, older prepends, live appends, and token streaming also make reference stability and local recomputation depend on specialized caches spread across the client.
|
|
|
|
|
|
Business events also use different correlation models. Tool has call IDs, Assistant correlates by turn and step, Compaction has its own lifecycle and checkpoint, and an Inbox splice represents one instantaneous state in a sequence. Keeping all these distinctions in one fold would make every business change pass through a global lookup and invalidate unrelated caches.
|
|
|
|
|
|
## Decision
|
|
|
|
|
|
-Client Runtime provides a target-neutral Conversation Node assembly engine. Business plugins register Event Definitions, and view plugins register per-Session View Builders. `ui-conversation` registers the first built-in Definitions and the `chat` builder; Session only submits the current contiguous Event window to the engine and publishes its snapshot instead of interpreting individual conversation businesses.
|
|
|
+Client Runtime provides a target-neutral Conversation Node assembly engine. Business plugins register Event Definitions, and view plugins register per-Session View Builders. `ui-conversation` registers the first built-in Definitions and the `chat` builder; Session only submits the current contiguous `SessionEventLikeEntry` window to the engine and publishes its snapshot instead of interpreting individual conversation businesses. The entry's outer discriminator distinguishes standard and packed records, while both carry an aligned inner `SessionEventLike` for Definition dispatch.
|
|
|
|
|
|
This Note retains the derivation, business-by-business validation, responsibilities, algorithms, and trade-offs that remain relevant after implementation.
|
|
|
|
|
|
@@ -22,20 +22,20 @@ This Note retains the derivation, business-by-business validation, responsibilit
|
|
|
|
|
|
| Layer | Durable responsibility | Explicitly does not own |
|
|
|
|---|---|---|
|
|
|
-| Session | Maintain the contiguous Event window, distinguish replace, prepend, and append, and schedule snapshot notifications | Interpret Tool, Assistant, Compaction, or other business events |
|
|
|
+| Session | Maintain the contiguous logical-event window, distinguish replace, prepend, and scalar append, and schedule snapshot notifications | Interpret Tool, Assistant, Compaction, or other business events |
|
|
|
| Event Registry | Retain the unique-`kind` Definitions and sole fallback under Cordis lifecycles | Store one Session's Context or State |
|
|
|
-| Assembler | Match Events and maintain Contexts, Locations, dependencies, and the publication dirty set | Interpret business State fields or Chat ordering |
|
|
|
+| Assembler | Match standard events or packed runs and maintain Contexts, Locations, dependencies, and the publication dirty set | Interpret business State fields or Chat ordering |
|
|
|
| Node Definition | Define one business object's identity, State transitions, Location data, and target Node | Create Contexts, mutate another business's State, or scan all Contexts |
|
|
|
-| View Builder | Incrementally organize final target Nodes into that view's snapshot | Reinterpret raw Session Events |
|
|
|
+| View Builder | Incrementally organize final target Nodes into that view's snapshot | Reinterpret `SessionEventLike` inputs |
|
|
|
| React renderer | Render renderer-owned data by the final Node's `kind` and read business data from the current Node's Location | Pair business Events, scan global Nodes, or decide business lifecycle state |
|
|
|
|
|
|
Registry contributions are Cordis effects. Removing a Definition causes a low-frequency registry rebuild for existing Sessions; ordinary business Events do not change the Registry or rebuild every business type.
|
|
|
|
|
|
### Overall `ConversationNodeDefinition` contract
|
|
|
|
|
|
-Each [`ConversationNodeDefinition`](../../../../packages/client/ui-conversation/src/client/contract/conversation.ts) independently owns one business object's conversion from Events to State and final view Nodes. A Definition's `kind` is its unique Registry name and the namespace for its business IDs.
|
|
|
+Each [`ConversationNodeDefinition`](../../../../packages/client/ui-conversation/src/client/contract/conversation.ts) independently owns one business object's conversion from `SessionEventLike` inputs to State and final view Nodes. A Definition's `kind` is its unique Registry name and the namespace for its business IDs.
|
|
|
|
|
|
-One Event may be claimed by several ordinary Definitions. For example, an Assistant Event updates both the Assistant Node and Turn Tail, while a Retry Event updates Retry, Assistant, and Turn Tail. The Assembler asks the fallback only when every ordinary Definition returns `null`.
|
|
|
+One input may be claimed by several ordinary Definitions. For example, an Assistant event or packed run updates both the Assistant Node and Turn Tail, while a Retry Event updates Retry, Assistant, and Turn Tail. The Assembler asks the fallback only when every ordinary Definition returns `null`.
|
|
|
|
|
|
A Definition holds no mutable business data across Sessions. Each Session's Assembler isolates that Session's Contexts, State, dependencies, and View Builders.
|
|
|
|
|
|
@@ -49,9 +49,9 @@ Each `(kind, id)` has at most one start Match. A second start fails immediately;
|
|
|
|
|
|
#### `match(event)`
|
|
|
|
|
|
-`match(event)` reads only the current raw `SessionEvent` and returns `{ id, role: 'start' | 'update' }` or `null`. It cannot access a Context, history, a Reader, a Location, or the view envelope.
|
|
|
+`match(event)` reads only the current `SessionEventLike` and returns `{ id, role: 'start' | 'update' }` or `null`. It cannot access a Context, history, a Reader, a Location, or the view envelope. A `chunkrow/*` event can only be an update; the Assembler rejects it as a start, and `start()` receives a `ConversationStartMatch` containing a standard `SessionEvent`.
|
|
|
|
|
|
-This restriction makes one Event's routing cost depend only on the number of registered Definitions. The Assembler never scans a Definition's historical Contexts to decide which one owns an update.
|
|
|
+This restriction makes one scalar event or packed run's routing cost depend only on the number of registered Definitions. The Assembler never scans a Definition's historical Contexts to decide which one owns an update.
|
|
|
|
|
|
Start, result, resource, checkpoint, and business-owned terminal Events must carry or directly imply the same ID. If one Event cannot yield that ID, its producer extends the Event protocol; the Client does not guess from the "nearest unfinished object."
|
|
|
|
|
|
@@ -59,9 +59,9 @@ The `role` describes the State lifecycle, not visibility. A start may produce a
|
|
|
|
|
|
#### `ConversationMatch`
|
|
|
|
|
|
-After a successful match, the Assembler combines the raw Event, optional wire presentation view, `role`, and engine-computed `location` into a read-only `ConversationMatch`.
|
|
|
+After a successful match, the Assembler combines the standard or packed event, `role`, and engine-computed `location` into a read-only `ConversationMatch`. A packed run remains one Match and retains its fragment and timestamp-gap arrays.
|
|
|
|
|
|
-A Context's `matches` always remain in ascending Event `seq` order, not network arrival or pagination ingestion order. If a tail page supplies a result before an older page supplies its call, the final Match order still places the call before the result.
|
|
|
+A Context's `matches` always remain in ascending first-`seq` order, not network arrival or pagination ingestion order. The Session journal has already rejected overlapping logical ranges. If a tail page supplies a result before an older page supplies its call, the final Match order still places the call before the result.
|
|
|
|
|
|
Location can change when prepend fills a boundary or append closes one. The Assembler replaces the affected Matches' read-only Locations and replays the Context; business code does not retain an old Location copy as authority.
|
|
|
|
|
|
@@ -71,8 +71,8 @@ Location can change when prepend fills a boundary or append closes one. The Asse
|
|
|
|---|---|---|
|
|
|
| `key` | Assembler | Stable final identity derived from `kind + id` |
|
|
|
| `kind` / `id` | Definition + Assembler | Current business namespace and business ID |
|
|
|
-| `matches` | Assembler | Complete business evidence loaded in the current window and sorted by `seq` |
|
|
|
-| `start` | Assembler | Unique start Match, or `undefined` before it loads |
|
|
|
+| `matches` | Assembler | Complete scalar and packed business evidence loaded in the current window and sorted by first `seq` |
|
|
|
+| `start` | Assembler | Unique scalar start Match, or `undefined` before it loads |
|
|
|
| `state` | Returned by Definition, held by Assembler | Most recent `start`/`update` return value, or `undefined` before initialization |
|
|
|
| `current` | Assembler | Most recently materialized Node or `null` for each target |
|
|
|
|
|
|
@@ -108,7 +108,7 @@ Dependencies point strictly from earlier starts to later starts, so transitive r
|
|
|
|
|
|
#### `update(context, match)`
|
|
|
|
|
|
-`update()` handles a post-start Match that `match()` has already routed exactly to the current `(kind, id)`. It does not decide which Context owns the Event.
|
|
|
+`update()` handles a post-start scalar or packed Match that `match()` has already routed exactly to the current `(kind, id)`. It does not decide which Context owns the input. A Definition that consumes Assistant deltas folds each matching `chunkrow/*` value as one batch without constructing member events.
|
|
|
|
|
|
The Assembler invokes `update()` in ascending `seq` order. A live tail update can apply incrementally; any non-tail insertion, newly loaded start, or invalidated dependency causes a complete replay from `start()`.
|
|
|
|
|
|
@@ -126,9 +126,9 @@ The Assembler does not use State reference equality to decide publication or pro
|
|
|
| `animation-frame` | Coalesce high-frequency updates into materialization on the next frame |
|
|
|
| `none` | Do not schedule a flush for this Match; retain its State and dirty marker |
|
|
|
|
|
|
-Omitting `publication()` means `immediate`. Assistant token deltas use `animation-frame`, invisible Inbox Contexts use `none`, and finals, dependency replays, and Location boundaries publish the latest result through an immediate path.
|
|
|
+Omitting `publication()` means `immediate`. Assistant token deltas and packed runs use `animation-frame`, invisible Inbox Contexts use `none`, and finals, dependency replays, and Location boundaries publish the latest result through an immediate path.
|
|
|
|
|
|
-Every delta within a frame still executes update. Only `buildViewNode()`, View Builder work, and React snapshot notification are coalesced; no tokens are lost.
|
|
|
+Every live delta within a frame still executes `update()`, while one historical packed run executes one batch `update()`. Only `buildViewNode()`, View Builder work, and React snapshot notification are coalesced; no fragments are lost.
|
|
|
|
|
|
#### `buildLocationData(context, scope)`
|
|
|
|
|
|
@@ -160,7 +160,7 @@ IDs are never reused. Completed Contexts remain in the current window, providing
|
|
|
|
|
|
### Location is a first-class engine fact
|
|
|
|
|
|
-[`ConversationLocationIndex`](../../../../packages/client/ui-conversation/src/client/conversation/location-index.ts) maps Events to Locations from `turn/start`, `step/start`, explicit turn and step payloads, `step/end`, and `turn/end`.
|
|
|
+[`ConversationLocationIndex`](../../../../packages/client/ui-conversation/src/client/conversation/location-index.ts) maps standard events and packed runs to Locations from `turn/start`, `step/start`, explicit turn and step payloads, `step/end`, and `turn/end`. All members of a row share its turn, step, block index, and delta kind, so the row needs one Location entry at its first `seq`.
|
|
|
|
|
|
Location has four shapes: `session`, `turn`, `step`, and `unresolved`. Turns and Steps each carry `open`, `closed`, or `unknown` status plus any loaded start and end Events.
|
|
|
|
|
|
@@ -168,31 +168,31 @@ Each Turn and Step also carries a reference-stable Location data store. A Defini
|
|
|
|
|
|
`unresolved` means the current history window lacks sufficient preceding boundaries; it does not mean session-level. When older prepend supplies those boundaries, the index corrects Match Locations and replays only Contexts that own those seqs.
|
|
|
|
|
|
-An appended ordinary Event only inherits current coordinates, while an appended boundary recalculates only its owning Turn. Prepend rebuilds Location facts from the expanded contiguous window, but reference-stability logic retains unchanged Turn and Step objects.
|
|
|
+An appended standard Event only inherits current coordinates, while an appended boundary recalculates only its owning Turn. Prepend rebuilds Location facts from the contiguous `SessionEventLikeEntry` window, but reference-stability logic retains unchanged Turn and Step objects.
|
|
|
|
|
|
The Assembler also passes a reference-stable timeline to each View Builder. Businesses do not separately maintain turn order, step lists, last-step values, or boundary Maps.
|
|
|
|
|
|
-## Three Event-window paths
|
|
|
+## Three input-window paths
|
|
|
|
|
|
-"Backward history scanning" describes the UI loading pages from the newest tail toward the Session beginning; it does not mean a Definition executes `update()` in reverse. Regardless of history API order or page-loading direction, the Assembler canonicalizes each current window and each fresh page in ascending `seq` order.
|
|
|
+"Backward history scanning" describes the UI loading pages from the newest tail toward the Session beginning; it does not mean a Definition executes `update()` in reverse. The Session journal validates each record's logical range before publication. Regardless of page-loading direction, the Assembler orders every accepted standard event or packed run by its first `seq`.
|
|
|
|
|
|
| Scenario | Input range | Context and State handling | View Builder |
|
|
|
|---|---|---|---|
|
|
|
-| Initial history tail or resync | Current complete contiguous window | Clear and rebuild all Contexts in ascending `seq` order | `replace()` |
|
|
|
-| Load one older-history page | Only deduplicated fresh Events before the window | Retain existing Context identity, then add Matches, Locations, dependencies, and local replays | `apply(upserts)` |
|
|
|
+| Initial history tail or resync | Current complete contiguous logical window | Clear and rebuild all Contexts in ascending first-`seq` order | `replace()` |
|
|
|
+| Load one older-history page | Only range-validated fresh standard events or packed runs before the window | Retain existing Context identity, then add Matches, Locations, dependencies, and local replays | `apply(upserts)` |
|
|
|
| Live append | One contiguous tail Event | Match Definitions and update only the exact IDs; boundaries affect only their owning Turn | `apply(upserts)` |
|
|
|
|
|
|
### Initial history tail and logical backward scanning
|
|
|
|
|
|
-1. `Session.open()` loads the latest tail page and passes its contiguous History Entries to `replaceWindow(entries, hasMore)`.
|
|
|
+1. `Session.open()` loads the latest tail page and passes its contiguous `SessionEventLike` entries to `replaceWindow(entries, hasMore)`.
|
|
|
2. `replaceWindow` clears old Contexts, start-seq indexes, seq reverse indexes, Reader dependencies, and the input Map.
|
|
|
-3. It sorts every entry by Event `seq` and stores the resulting current window.
|
|
|
+3. It sorts every entry by its first logical `seq` and stores the resulting current window.
|
|
|
4. LocationIndex rebuilds Turn and Step facts for that window.
|
|
|
-5. The Assembler visits Events in ascending order and invokes every ordinary Definition's `match(event)`.
|
|
|
+5. The Assembler visits standard events and packed runs in ascending order and invokes every ordinary Definition's `match(event)`.
|
|
|
6. Each result gets or creates its `(kind, id)` Context and enters that Context's ordered Match array.
|
|
|
7. A start runs `start()`; a tail update on initialized State runs `update()` directly.
|
|
|
8. If the page contains only a result or resource and omits its start, the ID still creates a Context and collects Matches, while State remains `undefined`.
|
|
|
-9. After matching all Events, the Assembler rechecks Reader dependencies so earlier instantaneous states in the same window stabilize before later consumers read them.
|
|
|
+9. After matching all inputs, the Assembler rechecks Reader dependencies so earlier instantaneous states in the same window stabilize before later consumers read them.
|
|
|
10. Every Context becomes dirty, and the next flush fully rebuilds Location data in Step→Turn order before invoking `buildViewNode()` for every target.
|
|
|
11. Some businesses return `null` without a start; Compaction, Command, Tool result, and Turn Error can construct fallback Nodes from sufficient update evidence.
|
|
|
12. Each View Builder receives the complete Node set and timeline and establishes the initial snapshot through `replace()`.
|
|
|
@@ -206,12 +206,12 @@ If an update with the same ID is genuinely earlier than the start in log order,
|
|
|
### Prepending a newly loaded older page
|
|
|
|
|
|
1. `Session.loadOlder()` requests the immediately preceding page using the current `baseSeq` and first verifies continuity between the page tail and current window.
|
|
|
-2. Session prepends the raw Event and view arrays to its own window and passes only that page to `assembler.prepend(entries, hasMore)`.
|
|
|
-3. The Assembler removes seqs that overlap the current window, then sorts the fresh page internally in ascending order.
|
|
|
+2. Session prepends the accepted standard or packed entries to its own window and passes only that page to `assembler.prepend(entries, hasMore)`.
|
|
|
+3. The journal has already removed complete duplicate ranges and rejected partial overlaps; the Assembler sorts the fresh page by first `seq`.
|
|
|
4. Existing Contexts, State, current Nodes, and View Builder instances remain intact.
|
|
|
-5. LocationIndex rebuilds facts over the expanded complete input and reports seqs whose Location identity actually changed.
|
|
|
+5. LocationIndex rebuilds facts over the extended complete input and reports seqs whose Location identity actually changed.
|
|
|
6. Contexts owning those seqs update their Match Locations and replay from start; unrelated Contexts do not join Location replay.
|
|
|
-7. Fresh Events run Definition matchers and enter existing or new Contexts by stable ID.
|
|
|
+7. Fresh standard events and packed runs enter existing or new Contexts through the same Definition matcher and stable ID.
|
|
|
8. If the new page supplies a pending Context's start, that Context initializes from the start and then applies every already-collected update in ascending order.
|
|
|
9. If the page establishes a nearer Reader predecessor, changes a predecessor revision, or removes a window gap, the consumer recomputes from `start()`.
|
|
|
10. Reader dependencies propagate replay toward later start seqs; no Event is applied in reverse within the propagation batch.
|
|
|
@@ -226,7 +226,7 @@ Reader gap repair is the largest algorithmic difference between prepend and ordi
|
|
|
|
|
|
### Forward live append
|
|
|
|
|
|
-1. Session accepts only a live Event immediately after the current tail seq; it deduplicates overlap and runs tail-page repair before accepting a gap.
|
|
|
+1. Session accepts only a standard live Event immediately after the current logical tail seq; it deduplicates overlap and runs tail-page repair before accepting a gap.
|
|
|
2. A non-boundary Event enters the current Turn and Step coordinates incrementally; a boundary Event updates Location facts for its owning Turn.
|
|
|
3. The Assembler invokes `match()` once on every ordinary Definition for this Event and scans no Definition's Context set.
|
|
|
4. Each successful result directly locates one Context through `(kind, id)`.
|
|
|
@@ -249,7 +249,7 @@ All three paths preserve the same invariants: Context Matches are seq-ordered, S
|
|
|
|
|
|
`replaceWindow` is the low-frequency complete replacement for initial open, resync, gap repair, and registry changes; it does not implement ordinary load older. Both `prepend` and `append` retain existing Builder and Context identity.
|
|
|
|
|
|
-Page size, the number of history loads, and RAF coalescing affect only when evidence arrives or publishes. They do not change final Context State and Nodes for an equal Event window.
|
|
|
+Page size, record packing, the number of history loads, and RAF coalescing affect only when evidence arrives or publishes. They do not change final Context State and Nodes for equal logical evidence.
|
|
|
|
|
|
## How built-in businesses use Definitions
|
|
|
|
|
|
@@ -261,7 +261,7 @@ Page size, the number of history loads, and RAF coalescing affect only when evid
|
|
|
| Next-step Inbox / `inbox-next-step` | Splice Event seq | Each `agent/inbox/spliced` targeting next-step | None | Build the same per-instruction instantaneous state; Message reads its claimed set |
|
|
|
| Message / `input-message` | Message ID | Append-surface `user/message` | None | Use source for a context message, or read the nearest next-step Inbox to distinguish user from steering |
|
|
|
| Request Prompt / `request-prompt` | Header Event seq | Each `request/header` | None | Read the preceding Request Prompt through Reader, retain the full prompt state, and classify system/tool changes |
|
|
|
-| Assistant / `assistant-step` | `turn:step` | `step/start` | `assistant/chunk`, final `assistant/message`, and same-step Retry | Aggregate blocks, usage, first-token time, final evidence, and retry-hidden state, then publish same-key Step data |
|
|
|
+| Assistant / `assistant-step` | `turn:step` | `step/start` | Scalar or packed `assistant/chunk`, final `assistant/message`, and same-step Retry | Aggregate blocks, usage, first-token time, final evidence, and retry-hidden state, then publish same-key Step data |
|
|
|
| Tool / `tool-call` | Root call ID | Root `tool/call` | Root result and Code Dispatch start/result | Aggregate the root, children, and parent Map; Dispatch Events route exactly through `rootCallId` |
|
|
|
| Command / `command` | Command ID | `command/run` | `command/done` and compact lifecycle/checkpoint Events carrying a source command ID | Aggregate command outcome and manual-compaction evidence |
|
|
|
| Automatic Compaction / `compaction` | Compaction ID | `compaction/start` without a source command ID | Summary, end, and replacement checkpoint | Aggregate summary/checkpoint; sufficient checkpoint evidence supports fallback without a start |
|
|
|
@@ -278,7 +278,7 @@ Page size, the number of history loads, and RAF coalescing affect only when evid
|
|
|
| Inbox | `none` | No Node | Recompute instantaneous states along the Reader chain when prepend supplies earlier splices |
|
|
|
| Message | Immediate by default | `user`, `steering`, or `context` | Window-gap repair can reclassify the same message key |
|
|
|
| Request Prompt | Immediate by default | One `system-prompt` for every header carrying a non-empty system field | A step's first header anchors before its request messages; a later same-step series anchors after its surface rewrite; prepend of the preceding header can correct a partial-window anchor |
|
|
|
-| Assistant | RAF for chunks, immediate for final, none for pure usage/finish | Same-key `assistant-step` with running/settled/interrupted status | Matches support fallback without `step/start`; Location close produces interruption presentation |
|
|
|
+| Assistant | RAF for scalar chunks and packed runs, immediate for final, none for pure usage/finish | Same-key `assistant-step` with running/settled/interrupted status | Scalar and packed reducers are equivalent; Matches support fallback without `step/start`; Location close produces interruption presentation |
|
|
|
| Tool | Immediate by default | One recursive `tool-call` root containing all `subCalls` | A result-only history window supports fallback; running→settled retains its key |
|
|
|
| Command | Immediate by default | Ordinary `command` or integrated `manual-compaction` | Checkpoint arrival may change the anchor without changing the Context key |
|
|
|
| Compaction | Immediate by default | `compaction` marker | A checkpoint may render before start; an older start triggers forward replay |
|
|
|
@@ -326,20 +326,20 @@ Slot-level contextual Hooks and entry-owned `inject.hooks` remain independent pa
|
|
|
|
|
|
The standard `useSession` remains available to every session-scoped slot renderer. `useTurnData()` narrows the common read path rather than acting as a permission sandbox. Whole-window statistics or arbitrary object indexes may still read the Session snapshot explicitly, but they are not modeled as current-Node Turn data.
|
|
|
|
|
|
-Assistant streaming to final and Tool running to settled update only one Seat's data and necessary ordering properties. They no longer move from a tail running container into finalized flow, so settlement does not reset component-local State.
|
|
|
+Assistant streaming to final and Tool running to settled stay in one Seat while updating its data and necessary ordering properties. Settlement therefore does not reset component-local State through a parent move.
|
|
|
|
|
|
When business logic deliberately changes a materialized Node to hidden, it leaves visible order and remounts when visible again. This is explicit business withdrawal of presentation, distinct from the stable-Seat guarantee for running→settled.
|
|
|
|
|
|
The concrete Tool renderer remains governed by the [`ui-tool ownership decision`](2026-08-08-client-tool-presentation-ownership.md). Tool Definition supplies recursive root/subcall data, and `ui-tool` dispatches concrete presentation by the Tool-name keyed slot.
|
|
|
|
|
|
-Trajectory registers its own target and business Definitions against the same Assembler and Session event window as Chat. Its target builder preserves the stage-oriented read model without consuming the Chat Builder's legacy slice or running an independent history fold. The Chat Builder retains its legacy slice for StatsLine and the top-level public compatibility fields; target-specific Definitions do not change the shared Context, Reader, or Location contracts.
|
|
|
+Trajectory registers its own target and business Definitions against the same Assembler and `SessionEventLikeEntry` window as Chat. Its target builder preserves the stage-oriented read model without consuming the Chat Builder's legacy slice or running an independent history fold. Chat and Trajectory keep independent scalar and packed Assistant reducers; target-specific Definitions do not change the shared Context, Reader, or Location contracts.
|
|
|
|
|
|
The target-specific Trajectory Definitions, retained stage model, Steering adaptation, complexity bounds, and presentation hot paths are owned by the [Trajectory Context assembly decision](2026-08-11-trajectory-conversation-context-assembly.md).
|
|
|
|
|
|
## Runtime and render path
|
|
|
|
|
|
```text
|
|
|
-Session Event window
|
|
|
+SessionEventLike window
|
|
|
-> ConversationNodeAssembler
|
|
|
-> Definition.match(event) -> (kind, id, start/update)
|
|
|
-> Context matches + State + Location
|
|
|
@@ -361,7 +361,7 @@ Slot type/runtime tests pin required parent-provided common inject, the `hookCon
|
|
|
|
|
|
Assembled Web snapshots, GUI tests, and browser scenarios cover the real plugin graph. Browser evidence compares Assistant streaming→settled, Bash running→settled, and Code Mode root + nested subcalls against master layout.
|
|
|
|
|
|
-History-path tests cover complete replace, non-overlapping prepend, overlapping-seq deduplication, empty-page `hasMore` convergence, and live append. Equal Event windows ingested through different paths produce equal business State and final Nodes.
|
|
|
+History-path tests cover complete replace, non-overlapping prepend, complete-range deduplication, partial-overlap rejection, empty-page `hasMore` convergence, and scalar live append. Scalar and packed representations of the same Assistant history produce equal Chat and Trajectory State, timing boundaries, and final Nodes; one packed run remains one Match through replace, prepend, Location replay, and registry rebuild.
|
|
|
|
|
|
## Alternatives considered
|
|
|
|
|
|
@@ -377,6 +377,8 @@ History-path tests cover complete replace, non-overlapping prepend, overlapping-
|
|
|
|
|
|
**Define a reverse State fold for backward history scanning.** Rejected: every business would maintain two inverse algorithms, and deletion, non-invertible aggregation, and cross-Context dependencies would be difficult to keep equivalent. Ordered Matches followed by forward replay from start preserve one business meaning.
|
|
|
|
|
|
+**Add a separate chunk-run matcher and update lifecycle.** Rejected: a second Definition path would duplicate dispatch, replay, publication, and Context types. `ChunkRowEvent` uses the existing `match(event)` and `update(context, match)` lifecycle while making packed handling explicit through its `chunkrow/*` discriminant.
|
|
|
+
|
|
|
**Make Inbox a first-class engine concept or one window-wide Context.** Rejected: Inbox is ordinary business State and does not belong in the generic engine. Per-splice instantaneous State plus a strictly backward Reader supports prepend, append, and Message lookup together.
|
|
|
|
|
|
**Register specialized query methods for cross-business reads.** Rejected: consumers would still depend on provider APIs, and each new relationship would expand a central interface. Reader exposes a named kind's read-only predecessor Context; the provider writes useful State and the consumer interprets it.
|
|
|
@@ -399,14 +401,14 @@ A new business node can register its matcher, State transitions, optional Locati
|
|
|
|
|
|
Host business packages declaration-merge their durable Event members into `@deepseek-ai/dsh-session/types`, while Client Definitions type-only import the corresponding business package `/types` subpaths. Augmenting the declaring interface rather than a re-export barrel gives the independent Host and Client TypeScript programs the same Event narrowing without pulling Host runtime into the Client graph.
|
|
|
|
|
|
-Initial tail, older prepend, and live append share one set of Context invariants. Missing starts, Reader window gaps, unknown Locations, and high-frequency deltas are explicit engine states and require no direction-specific business cache.
|
|
|
+Initial tail, older prepend, and live append share one set of Context invariants. Missing starts, Reader window gaps, unknown Locations, and packed high-frequency deltas are explicit engine states and require no direction-specific business cache.
|
|
|
|
|
|
Append does not scan historical Contexts; prepend replays only Contexts whose Matches, Locations, or Reader answers actually changed. A structural Chat change may still recompute visible order and indexes, but does not rerun unrelated business folds or replace unchanged Node identity.
|
|
|
|
|
|
-Separating State updates from publication cadence folds every Assistant delta while materializing at most once per animation frame. Step or Turn close and final Events can immediately publish the latest State.
|
|
|
+Separating State updates from publication cadence folds every live Assistant delta and each historical packed run while materializing at most once per animation frame. Step or Turn close and final Events can immediately publish the latest State.
|
|
|
|
|
|
-Steps and Turns become stable homes for cross-business aggregates. Turn Tail and Deliverables no longer depend on renderers scanning global Nodes; slot-level `useTurnData()` narrows common reads to the current Node's Turn and uses selector equality to isolate unrelated updates.
|
|
|
+Steps and Turns are stable homes for cross-business aggregates. Turn Tail and Deliverables derive their values without renderer scans of global Nodes; slot-level `useTurnData()` narrows common reads to the current Node's Turn and uses selector equality to isolate unrelated updates.
|
|
|
|
|
|
-The cost is new Runtime contracts for Registry, Assembler, Location data, dependency replay, and per-target Builders, plus parent-owned common inject and per-occurrence `hookContext` in UI Slots. Definition authors must understand stable IDs, unique starts, forward replay, Step→Turn publication order, read-only Reader access, and the prohibition on Node withdrawal.
|
|
|
+The cost is new Runtime contracts for Registry, Assembler, Location data, dependency replay, and per-target Builders, plus parent-owned common inject and per-occurrence `hookContext` in UI Slots. Definitions that consume Assistant deltas also maintain equivalent scalar and packed update branches. Definition authors must understand stable IDs, unique scalar starts, forward replay, Step→Turn publication order, read-only Reader access, and the prohibition on Node withdrawal.
|
|
|
|
|
|
`useTurnData()` does not revoke the standard `useSession` capability from session-scoped renderers, so this boundary relies on API guidance and tests rather than capability isolation. Registry changes remain low-frequency full rebuilds; the Chat Builder still maintains a legacy slice for StatsLine and the top-level public fields, while Trajectory owns target-specific Definitions and a Builder over the shared Session window. Built-in Definitions remain in their respective UI packages, and these compatibility boundaries do not return business interpretation to Session.
|