Trajectory assembly from registered Conversation Contexts
Trajectory 基于注册式 Conversation Context 组装数据
Trajectory maintained an independent Session History source and folded the complete loaded Event window into Assistant, Tool, message, Request-header, and Compaction state. Chat already assembled the same Event families through registered Conversation Definitions. The two paths duplicated business correlation and pagination behavior, and a Trajectory structural update copied or rescanned work proportional to the raw
English
Problem
Trajectory maintained an independent Session History source and folded the complete loaded Event window into Assistant, Tool, message, Request-header, and Compaction state. Chat already assembled the same Event families through registered Conversation Definitions. The two paths duplicated business correlation and pagination behavior, and a Trajectory structural update copied or rescanned work proportional to the raw Event count even when one business object changed.
Reusing Chat's final Nodes would not solve the ownership problem. Trajectory needs request lifecycles, running Assistant state, prompt inheritance, Tool schemas, timing records, and a stage-oriented read model that Chat does not consume. Sharing final Node payloads would couple both views to the union of their requirements.
The migration also had to preserve durable steering classification. A user/message does not say whether it opened a Turn or was claimed from the next-step inbox, and an older page can supply the missing inbox predecessor or Location after the message has already materialized.
Decision
Trajectory registers target-owned Conversation Definitions and a trajectory View Builder against the shared ConversationNodeAssembler. Session owns one contiguous Event window and publishes both Chat and Trajectory snapshots through Session.views; it does not run a second Trajectory history source or business fold.
Each Definition belongs to one target. Chat and Trajectory may recognize the same durable Event family, but they keep separate State and final Node payloads. They share only the Assembler's exact-ID matching, ordered Matches, Location facts, Reader dependencies, publication scheduling, and replace/prepend/append lifecycle.
The existing Trajectory inspection ledger remains the view model. The Trajectory Builder converts materialized target Nodes into its established eventNodes, Requests, Tool schemas, running calls, and Location map; layout, table virtualization, selection, Overview, and inspector behavior do not become generic Conversation contracts.
Business Definitions
| Business | Context identity | State assembly | Trajectory contribution |
|---|---|---|---|
next-step inbox | splice Event seq | Apply the splice to the nearest preceding inbox Context | State only; no visible Node |
| User, steering, or injected message | message Event seq | Read the preceding inbox State and classify the durable message | Input or context Node |
| Assistant and ordinary Request | turn:step | Fold step/start, chunks, final message, retry, and step/end | Final Assistant, partial Assistant, and Request |
| Root Tool call | root call ID | Fold root call/result and nested Code Dispatch events into one call tree | Final or running Tool tree |
| Compaction | compaction ID | Fold start, summary, end, and replacement checkpoint | Compaction Request |
| Request header | header Event seq | Read the preceding header and retain effective prompt plus the actual change | Prompt and Tool-schema source |
| Session and Turn boundaries | boundary Event seq | Retain closure time and error facts | Interrupted Compaction or failed ordinary Request |
Every correlating Event must expose the same business ID directly. Code Dispatch uses rootCallId, Compaction uses its compaction ID, and ordinary Tool and retry events retain their protocol identities even when a specific Definition correlates by turn:step. Legacy records that lack the required correlation ID are ignored by that Definition rather than merged into an undefined Context or crashing the Session.
Assistant chunks update only their turn:step Context. Content-bearing chunks request animation-frame publication; usage and finish chunks update State without forcing their own frame. A final message, retry, or boundary publishes immediately. Completed Assistant State retains assembled blocks, timing, usage, and retry facts rather than copying the raw chunk ledger into the target snapshot.
Steering from predecessor Contexts
Trajectory reconstructs steering from durable inbox history, using the same identity rule as the Chat steering decision without sharing Chat's final Node.
Each agent/inbox/spliced Event targeting next-step starts an invisible Context identified by its Event seq. Its start() reads the nearest earlier inbox Context, applies the splice, and stores the pending identities plus the cumulative set of claimed message IDs. A later user-origin user/message reads the nearest earlier inbox Context: a claimed ID produces a Steering Node, while every other user-origin message produces an ordinary User Node.
A Reader miss while older history remains records a window-gap dependency. When prepend supplies the missing predecessor, the Assembler replays the affected inbox chain and message Contexts in forward Event order. Historical page direction therefore cannot permanently misclassify a message.
The message Event's Location places steering in the owning Step. If the loaded history window lacks enough boundary Events to resolve that Location, layout uses the following Assistant step as the positional fallback. A running Request marker follows leading steering input in the same Step, so the marker denotes the model Request caused by that input rather than appearing before it.
Window paths and complexity
Let E be the loaded raw Event count, P one newly prepended page, D the number of Trajectory Definitions, C the number of materialized Trajectory Context contributions, and Mᵣ the total Matches held by Contexts invalidated by a prepend. D is a small registered set; streaming chunks aggregate into one Assistant Context, so C is normally much smaller than E.
| Path | Context work | Target snapshot work | Result |
|---|---|---|---|
| Initial tail or reconnect replace | Match the loaded window in O(E × D) and build State in forward Event order | Build and order C contributions | A full replace remains proportional to the loaded window |
| Older-page prepend | Match only fresh Events and replay only Contexts whose Match, Location, or Reader answer changed, in O(P × D + Mᵣ) | Rebuild the stage snapshot from C contributions | Business folding does not restart over all E Events |
| Live append | Match in O(D), locate the keyed Context in O(1), and update only that State | Replace a same-anchor contribution in O(1) before snapshot assembly | Business correlation is independent of loaded Event history |
The Builder stores contributions by Context key and keeps a key-to-position index. A content update with the same anchor replaces one contribution in place; a new contribution or anchor change rebuilds and sorts contribution order. Snapshot assembly then walks C contributions, indexes Request headers and Tool schemas with Maps, and handles Compaction boundaries and Turn errors with linear cursors or indexes.
Final Event and Request ordering keeps a publication's current upper bound at O(C log C). The migration removes repeated reverse lookups and the old raw-history refold, but it does not claim end-to-end O(1) publication. Chat retains its existing keyed snapshot behavior and complexity; adding the Trajectory target does not make Chat scan Trajectory Contexts or Nodes.
Independent presentation hot paths
The Context migration and the following presentation optimizations solve different costs. These reductions preserve the existing view model and are theoretical from call counts and asymptotic behavior; this decision does not claim benchmark measurements.
| Hot path | Retained behavior | Expected reduction |
|---|---|---|
| Markdown summaries | Layout retains source Markdown; each stable Table record memoizes its displayed summary by content, while Detail parses only the selected record | A one-record append reparses the changed visible record instead of every Markdown record |
| Search text | TrajectorySearchIndex linearly checks stable Record IDs and source signatures, but normalizes Markdown only for changed records and commits updates in three-second batches | Signature comparison remains O(C); expensive normalization follows the changed-record count, and continuous frame updates collapse into one batch per interval |
| Timeline tooltip | Timing text is computed after the delayed tooltip opens | A render with no open tooltip performs no per-span label formatting |
| Following Assistant lookup | One reverse pass records the next Assistant for every input position | The former repeated forward lookup falls from worst-case O(C²) to O(C) |
| Group duration | Fixed decimal grouping replaces toLocaleString('en-US') for the invariant English numeric shape | Complexity remains linear in Groups, but the Intl formatter leaves the repeated render path |
Display memoization and search indexing stay separate. Search must include off-screen records and may lag live changes by the throttle interval; Table rendering must update the visible changed record immediately and must not inherit the index's commit cadence.
Alternatives considered
Keep the independent Session History fold and optimize it locally. Rejected: caches could reduce selected hot paths, but Trajectory would still own a second Event window, pagination repair, request inspection fold, and business-correlation implementation beside Chat.
Reuse Chat Definitions and branch on a target argument in buildViewNode(). Rejected: Trajectory needs different State and intermediate records, not only another React renderer. One Definition would carry both views' payloads and conditionals and would invalidate unrelated target data when either view changed.
Create a Trajectory-specific Assembler. Rejected: exact-ID routing, update-before-start collection, prepend replay, Location repair, Reader dependencies, and publication cadence are not Trajectory-specific. A second engine would recreate the lifecycle duplication this change removes.
Add generic Surface, rewind, fanout, or settled lifecycle concepts. Rejected: the current durable Event stream does not require a generic Surface branch, and Session or Turn boundaries are target business inputs rather than a reason to fan out one Event over every historical Context. Completion remains business State interpreted with Location closure.
Replace the Trajectory stages with generic Conversation Nodes. Rejected: stages organize requests, timing, schemas, and table layout for one view. Making them engine contracts would constrain a future plain Session-log view and return view-specific composition to Client Runtime.
Share one Markdown cache between display and search. Rejected: display is immediate and viewport-bound, while search covers the complete loaded record set and intentionally batches updates. A shared cache would couple correctness and scheduling across unrelated consumers.
Verification
Runtime tests pin target registration, exact-ID append, update-before-start replay, prepend identity, Reader window-gap repair, Location replay, and isolation between Chat and Trajectory snapshots.
Trajectory Definition and Builder tests pin Assistant streaming and interruption, nested Tool calls and parallel interruption, Compaction and prompt inheritance, Steering classification and Step placement, Request marker order, stable contribution replacement, and prepend expansion. Table, layout, Timeline, and search tests pin deferred Markdown work, throttled index updates, tooltip-time formatting, and stable search results across append and prepend.
Consequences
Trajectory business assembly now scales with the changed page or keyed Context instead of restarting from the complete raw Event window. Target-owned Definitions can evolve independently from Chat while retaining one Session window and one set of lifecycle rules. Steering becomes a first-class Trajectory record at its actual Step position without adding steering-specific state to Session.
The retained stage-oriented Builder still performs work proportional to materialized Trajectory contributions and may sort on publication. The search index still performs a light linear signature pass when its input layout changes. These costs are explicit target-view work, not hidden full Event refolding.
Definition authors must provide stable protocol identities. Old Events without a required ID can disappear from the affected Trajectory business view, which is preferable to joining unrelated records or failing history load; producers that require faithful display must log the identity.
The Conversation assembly decision remains the authority for the generic Context, Reader, Location, and publication contracts. The Trajectory ledger decision remains the authority for table hierarchy, virtualization, inspector, and interaction behavior. This Note owns how Trajectory adapts those two decisions and why the adaptation does not share final Nodes with Chat.
中文
问题
Trajectory 曾维护独立的 Session History 数据源,并把完整的已加载 Event 窗口折叠为 Assistant、Tool、消息、Request header 和 Compaction 状态。Chat 已经通过注册式 Conversation Definition 组装相同的 Event 族。两条链路重复实现业务关联与分页行为;即使只改变一个业务对象,Trajectory 的结构更新仍会复制或重新扫描与原始 Event 数量成正比的数据。
复用 Chat 的最终 Node 无法解决职责问题。Trajectory 需要请求生命周期、运行中 Assistant 状态、提示词继承、Tool schema、计时记录和 stage-oriented read model,而 Chat 不消费这些数据。共享最终 Node payload 会让两个视图都依赖双方需求的并集。
本次迁移还必须保留持久 steering(中途引导)分类。user/message 本身不说明它是开启了一个 Turn,还是从 next-step inbox 被领取;更早页面还可能在消息已经物化后,才补齐缺失的 inbox 前驱或 Location。
决策
Trajectory 针对共享的 ConversationNodeAssembler 注册 target 自有的 Conversation Definition 和 trajectory View Builder。Session 只维护一份连续 Event 窗口,并通过 Session.views 发布 Chat 与 Trajectory 快照;它不再运行第二套 Trajectory history source 或业务 fold。
每个 Definition 只属于一个 target。Chat 与 Trajectory 可以识别同一持久 Event 族,但分别维护自己的 State 和最终 Node payload。它们只共享 Assembler 的精确 ID 匹配、有序 Match、Location 事实、Reader 依赖、发布调度,以及 replace/prepend/append 生命周期。
既有的 Trajectory 检查记录表继续作为视图模型。Trajectory Builder 把已物化的 target Node 转换为原有的 eventNodes、Requests、Tool schema、运行中调用和 Location map;layout、表格虚拟化、选择、Overview 与检查器行为不会成为通用 Conversation 约定。
业务 Definition
| 业务 | Context 标识 | State 组装方式 | Trajectory contribution |
|---|---|---|---|
next-step inbox | splice Event seq | 把 splice 应用到最近的前序 inbox Context | 只维护状态,不产生可见 Node |
| 用户、steering 或注入消息 | message Event seq | 读取前序 inbox State,并对持久消息分类 | Input 或 context Node |
| Assistant 与普通 Request | turn:step | 折叠 step/start、chunk、最终消息、retry 和 step/end | 最终 Assistant、partial Assistant 与 Request |
| 根 Tool call | root call ID | 把根 call/result 与嵌套 Code Dispatch Event 折叠为一棵调用树 | 最终或运行中的 Tool tree |
| Compaction | compaction ID | 折叠 start、summary、end 和 replacement checkpoint | Compaction Request |
| Request header | header Event seq | 读取前一个 header,保留生效提示词及真实变化 | Prompt 与 Tool-schema 来源 |
| Session 与 Turn 边界 | boundary Event seq | 保留关闭时间和错误事实 | 被中断的 Compaction 或失败的普通 Request |
每个关联 Event 都必须直接提供相同的业务 ID。Code Dispatch 使用 rootCallId,Compaction 使用 compaction ID;即使某个 Definition 按 turn:step 关联,普通 Tool 与 retry Event 仍保留各自的协议标识。缺少必要关联 ID 的旧记录由该 Definition 忽略,不会合入 undefined Context,也不会导致 Session 崩溃。
Assistant chunk 只更新对应的 turn:step Context。带内容的 chunk 请求 animation-frame 发布;usage 与 finish chunk 更新 State,但不单独强制刷新一帧。最终消息、retry 或边界立即发布。已完成 Assistant State 只保留组装后的 block、计时、usage 与 retry 事实,不会把原始 chunk ledger 复制进 target snapshot。
通过前序 Context 恢复 steering
Trajectory 从持久 inbox 历史恢复 steering,使用与 Chat steering 决策相同的标识规则,但不共享 Chat 的最终 Node。
每条目标为 next-step 的 agent/inbox/spliced Event 都会启动一个以 Event seq 标识的不可见 Context。它的 start() 读取最近的前序 inbox Context,应用 splice,并存储待处理标识以及累计的已领取 message ID 集合。后续用户来源的 user/message 读取最近的前序 inbox Context:已领取的 ID 生成 Steering Node,其余用户来源消息生成普通 User Node。
仍有更早历史时,Reader miss 会记录 window-gap 依赖。prepend 补齐缺失的前驱后,Assembler 按 Event 正序重放受影响的 inbox chain 与 message Context。因此,历史分页方向不会永久错误分类消息。
消息 Event 的 Location 会把 steering 放进所属 Step。如果已加载历史窗口缺少足够的边界 Event,无法解析该 Location,layout 就以后续 Assistant step 作为位置回退。同一个 Step 中,运行中 Request 标记排在前置 steering 输入之后,因此该标记表示由这条输入触发的模型 Request,而不会出现在输入前面。
窗口链路与复杂度
记 E 为已加载原始 Event 数,P 为一次新 prepend 的页面,D 为 Trajectory Definition 数,C 为已物化的 Trajectory Context contribution 数,Mᵣ 为一次 prepend 使其失效的 Context 所持有的 Match 总数。D 是较小的注册集合;流式 chunk 会聚合到同一个 Assistant Context,因此通常 C 明显小于 E。
| 链路 | Context 工作量 | Target snapshot 工作量 | 结果 |
|---|---|---|---|
| 初始尾页或重连 replace | 以 O(E × D) 匹配已加载窗口,并按 Event 正序构造 State | 构造并排序 C 个 contribution | 完整 replace 仍与已加载窗口成正比 |
| 更早页面 prepend | 只匹配新 Event,并只重放 Match、Location 或 Reader 答案发生变化的 Context,成本为 O(P × D + Mᵣ) | 从 C 个 contribution 重建 stage snapshot | 业务 fold 不会从头重跑全部 E 个 Event |
| 实时 append | 以 O(D) 匹配,以 O(1) 找到 keyed Context,并只更新对应 State | snapshot 组装前,以 O(1) 替换 anchor 未变的 contribution | 业务关联成本与已加载 Event 历史无关 |
Builder 按 Context key 保存 contribution,并维护 key-to-position index。anchor 相同的内容更新会原位替换一个 contribution;新增 contribution 或 anchor 变化才会重建并排序 contribution 顺序。随后,snapshot assembly 遍历 C 个 contribution,用 Map 索引 Request header 与 Tool schema,并以线性游标或索引处理 Compaction boundary 与 Turn error。
最终 Event 和 Request 排序使单次发布的当前上界保持为 O(C log C)。本次迁移移除了重复反向查找和旧的原始历史 refold,但不声称端到端发布达到 O(1)。Chat 保持既有 keyed snapshot 行为与复杂度;增加 Trajectory target 不会让 Chat 扫描 Trajectory Context 或 Node。
独立的表现层热点优化
Context 迁移与下列表现层优化解决的是不同成本。这些优化保留既有视图模型;收益来自调用次数和渐进复杂度推算,本决策不声称存在 benchmark 实测结果。
| 热点 | 保留的行为 | 预期减少的工作 |
|---|---|---|
| Markdown 摘要 | Layout 只保留源 Markdown;每个稳定 Table record 按内容 memo 展示摘要,Detail 只解析当前选中记录 | 单条 record append 只重解析发生变化的可见记录,而非全部 Markdown record |
| 搜索文本 | TrajectorySearchIndex 仍线性核对稳定 Record ID 与来源签名,但只为变化的 record 标准化 Markdown,并以三秒批次提交更新 | 签名比较仍为 O(C);昂贵标准化只随变化 record 数量增长,持续 frame update 每个时间窗合并成一个批次 |
| Timeline tooltip | 延迟 Tooltip 打开后才计算计时文案 | 没有打开 Tooltip 的 render 不执行逐 span label 格式化 |
| 后继 Assistant 查找 | 一次反向遍历为每个输入位置记录后续 Assistant | 原先重复向前查找的最坏复杂度从 O(C²) 降为 O(C) |
| Group duration | 以固定十进制分组替代固定英文数字形态下的 toLocaleString('en-US') | 复杂度仍与 Group 数线性相关,但重复 render 路径不再调用 Intl formatter |
展示 memo 与搜索索引彼此独立。搜索必须覆盖屏幕外 record,并允许实时变化延迟一个 throttle 周期;Table 必须立即更新发生变化的可见 record,不能继承索引的提交节奏。
考虑过的替代方案
保留独立 Session History fold,只做局部优化。 不予采纳:缓存可以降低部分热点,但 Trajectory 仍会在 Chat 之外拥有第二套 Event 窗口、分页修复、request inspection fold 与业务关联实现。
复用 Chat Definition,并在 buildViewNode() 中按 target 分支。 不予采纳:Trajectory 需要不同的 State 与中间 record,不只是另一套 React renderer。单一 Definition 会携带两个视图的 payload 与条件,并在任一视图变化时让无关 target 数据失效。
创建 Trajectory 专属 Assembler。 不予采纳:精确 ID 路由、先 update 后 start 的收集、prepend replay、Location 修复、Reader 依赖与发布节奏都不是 Trajectory 特有行为。第二套引擎会重新制造本次改造要消除的生命周期重复。
增加通用 Surface、rewind、fanout 或 settled 生命周期。 不予采纳:当前持久 Event stream 不需要通用 Surface branch;Session 或 Turn boundary 是 target 业务输入,不构成把一个 Event fanout 到全部历史 Context 的理由。完成条件仍由业务 State 结合 Location closure 判断。
用通用 Conversation Node 替换 Trajectory stage。 不予采纳:stage 为单一视图组织 Request、计时、schema 和表格 layout。把它变成引擎约定会限制未来的朴素 Session-log 视图,并把视图专属组合重新放回 Client Runtime。
在展示与搜索之间共享一套 Markdown cache。 不予采纳:展示要求立即更新且受 viewport 约束,搜索则覆盖全部已加载 record,并有意批量提交更新。共享 cache 会把两个无关消费方的正确性与调度节奏耦合起来。
验证
Runtime 测试固定 target 注册、精确 ID append、先 update 后 start 的 replay、prepend identity、Reader window-gap 修复、Location replay,以及 Chat 与 Trajectory snapshot 隔离。
Trajectory Definition 与 Builder 测试固定 Assistant streaming 与 interruption、嵌套 Tool call 和并行 interruption、Compaction 与 prompt 继承、Steering 分类和 Step 位置、Request 标记顺序、稳定 contribution 替换与 prepend 扩展。Table、layout、Timeline 与搜索测试固定延迟 Markdown 工作、节流索引更新、Tooltip 展示时格式化,以及 append/prepend 期间稳定的搜索结果。
后果
Trajectory 业务组装的成本随变化页面或 keyed Context 增长,不再从完整原始 Event 窗口重新开始。target 自有 Definition 可以独立于 Chat 演进,同时继续共享一份 Session 窗口和一套生命周期规则。steering 会在实际所属 Step 位置成为一等 Trajectory record,不需要向 Session 增加 steering 专属状态。
保留的 stage-oriented Builder 仍会执行与已物化 Trajectory contribution 数量成正比的工作,并可能在发布时排序。输入 layout 变化时,搜索索引仍会执行一次轻量线性签名检查。这些成本是显式的 target view 工作,不是隐藏的完整 Event refold。
Definition 作者必须提供稳定的协议标识。缺少必要 ID 的旧 Event 可能不会出现在受影响的 Trajectory 业务视图中;与合并无关记录或让历史加载失败相比,这是更安全的退化方式。要求完整展示的生产方必须记录该标识。
Conversation assembly 决策继续作为通用 Context、Reader、Location 与发布约定的真源。Trajectory ledger 决策继续负责表格层级、虚拟化、检查器和交互行为。本 Note 负责说明 Trajectory 如何适配这两项决策,以及为何该适配不与 Chat 共享最终 Node。