DSH / Atlas
2026-09-22implementedarchitecture

Tool calls own preparation, dispatch, and results by callId

工具调用按 callId 管理准备、派发与结果

The model often provides a tool name and call ID while it is still generating arguments, but `tool/call` arrives only after the entire Assistant stream ends. When one response contains several calls, an earlier call waits for later calls even after its own arguments are complete. File content is part of the arguments to `write`, so this delay can last several seconds. Creating tool nodes only at `tool/call` presents

English

Problem

The model often provides a tool name and call ID while it is still generating arguments, but tool/call arrives only after the entire Assistant stream ends. When one response contains several calls, an earlier call waits for later calls even after its own arguments are complete. File content is part of the arguments to write, so this delay can last several seconds.

Creating tool nodes only at tool/call presents that interval as no tool activity. Putting a preparing call in an Assistant part instead requires the Group to switch references between Assistant and Tool nodes. Preparation and dispatch belong to one tool lifecycle and do not require two cooperating nodes.

Decision

One Tool owns three stages

The Tool Definition groups calls by callId and supplies explicit stage data to tool components.

StageCreation or update evidenceAvailable data and presentation
preparingA live delta with a call ID and tool nameCall identity, name, time, and a lazy partial-argument view; one non-expandable row.
starttool/callComplete arguments; enables the tool's existing call presentation.
resulttool/resultThe result and any complete arguments paired within the loaded window.

An argument block ending does not trigger dispatch presentation. Showing preparation changes neither model requests, tool dispatch timing, Session events, nor the persisted format. An empty argument string does not stand in for a dispatched call.

Start marks an initialization candidate

The Conversation Definition retains its match/start/update interface and existing roles. Durable events and transient deltas may both match as start. The earliest start in the current event sequence initializes State; every later Match, including another start for the same ID, goes through update.

Every tool delta and tool/call are initialization candidates. A named live delta creates the preparing row; a historical tool/call creates the dispatched stage directly. Matching still reads only the current event, without Context lookup, additional roles, registries, or separate caches.

Successful stream settlement publishes the final Assistant message while retaining transient deltas until step/end. Each tool/call updates its existing callId; the Assistant keeps its live ordering anchor until the same cleanup. Failed, interrupted, or abandoned streams retire their deltas immediately. On retirement the Assembler reselects starts and recomputes State from remaining Matches. Undispatched preparations hide without synthetic execution results.

Converge on final state without replaying preparation

Historical pages do not expand settled Assistant messages into live deltas. Reconnection restores only an attempt that is still live. Live presentation may follow preparing → start → result, while history follows start → result directly. Equal durable events produce equal final state; intermediate presentation and preparation-group identity need not match.

Groups include Tool nodes from preparation onward and classify and count them by tool name. AssistantNodeView does not search tool blocks, and Groups neither encode tool groupPart values nor take over tool lifecycles. Grouping retains the node-input grouping decision; the Builder does not interpret preparation business rules.

Shared rows reuse their existing presentation

ToolRow reuses the existing icon, title, and row component across all stages. The shared row model selects the tool title and combines any generic tool-name prefix with the available argument summary. All stages expose the lazy argument view; cards requiring dispatched arguments still return no call during preparation. ToolRow prevents preparation rows from expanding.

Read, read_image, write/edit, search, web, todo, question, details, and the generic fallback use this path. Bash shares its component across stages; Skill, Present, and Cordis retain their own preparation branches. No automatic/custom registration declaration or second tool-classification list in ToolTree is introduced.

Relationship to existing records

This decision replaces the business-node assembly restriction that starts must be durable and a Context may receive only one start. That record's stable IDs, business Definitions, Contexts, Locations, predecessor dependencies, and target Builder responsibilities remain applicable.

Alternatives considered

Create nodes only at tool/call. This cannot show a known tool identity while arguments are being generated and leaves a visible wait in multi-call responses.

Transfer presentation through Assistant parts and Groups. A tool would need two presentation sources, additional deduplication, and identity transfer. Lifecycle ownership becomes distributed, and renderers must recover Assistant content from part identifiers.

Persist or replay preparing. Preparation describes current generation; durable calls and results already suffice to reconstruct the final presentation. Reproducing the same intermediate process adds unnecessary state and event interpretation.

Add a preparation registration mechanism. Generic tools already share ToolRow and argument models, including reusable icons and titles. Additional declarations or configuration for this stage would maintain another representation of tool presentation.

Consequences

  • A tool may appear before its arguments are complete, but preparation never implies execution. Interactions use only fields already available.
  • Multiple starts with one ID belong to one lifecycle. Independent calls must use distinct IDs; a second-start error no longer detects identity reuse.
  • The shared row primitive is reused across stages. Write/edit and Bash keep their component identity; custom rows may switch internal components by stage.
  • Partial-argument access and its update policy follow the lazy argument view; no separate raw-prefix Hook is required.
  • Assembly tests separately cover live preparation, withdrawal, active-stream reconstruction, durable history, and paged convergence. Component tests cover argument-free presentation and stage changes; a recorded session covers browser preparation and reload results.

中文

问题

模型生成工具参数时,工具名和调用 ID 往往已经可用,但 tool/call 要等整条 Assistant 流结束后才到达。一次响应包含多个调用时,前一个调用即使已经生成完整参数,仍要等待后续调用。文件正文也属于 write 参数,这段等待可能持续数秒。

只从 tool/call 创建工具节点,会把这段时间表现为没有工具活动;把准备中的调用放在 Assistant 片段里,又需要 Group 在 Assistant 与正式 Tool 节点之间切换引用。准备态和正式调用属于同一个工具生命周期,不应由两个节点配合表达。

决策

同一个 Tool 拥有三个阶段

Tool Definition 按 callId 聚合调用,向工具组件提供明确的阶段数据。

阶段创建或更新依据可用数据与展示
preparing带调用 ID 和工具名的实时 delta调用身份、名称、时间和懒计算的部分参数视图;显示不可展开的一行。
starttool/call完整参数;启用工具原有的调用展示。
resulttool/result调用结果,以及当前窗口内可配对的完整参数。

参数块结束不触发派发阶段。展示准备态不改变模型请求、工具派发时机、Session 事件或持久化格式,也不把空参数字符串伪装成正式调用。

start 表示可创建入口

沿用 Conversation Definition 的 match/start/update 接口和既有角色。持久事件与瞬态 delta 都可以匹配为 start;当前事件序列中最早的 start 初始化 State,后续 Match,包括同 ID 的其他 start,都交给 update。

Tool 的每个 delta 和 tool/call 都是创建入口。具名实时 delta 创建准备行;历史 tool/call 直接创建派发阶段。匹配函数仍然只读取当前事件,不查询 Context,不增加角色、注册表或旁路缓存。

成功流结束时发布最终 Assistant 消息,瞬态 delta 保留到 step/end。每个 tool/call 更新已有 callId;Assistant 的实时排序锚点也保留到同一次清理。失败、中断或废弃的流立即移除 delta。清理时,Assembler 从剩余 Match 重新选择 start 并重算 State;未派发的准备节点隐藏,不合成执行结果。

最终状态收敛,不重现准备过程

历史分页不把已结束 Assistant 消息展开为实时 delta。重连只恢复仍在生成的活跃流。实时展示可以经历 preparing → start → result,历史回放可以直接经历 start → result;相同持久事件得到相同最终状态即可,不要求中间过程或准备态分组身份一致。

Group 从准备阶段起就聚合 Tool 节点,按工具名分类和计数。AssistantNodeView 不查找工具块,Group 不编码工具 groupPart,也不接替工具生命周期。分组仍遵循节点输入的分组决策,Builder 不解释准备态业务。

通用行复用现有外壳

ToolRow 在三个阶段复用既有图标、标题和行组件。公共行模型选择工具标题,并组合通用工具名前缀与已有参数摘要。所有阶段都提供懒计算参数视图;需要完整派发参数的卡片在准备阶段仍返回无调用。ToolRow 禁止准备行展开。

read、read_image、write/edit、search、web、todo、question、details 和通用回退使用这条路径。Bash 在各阶段共用组件;Skill、Present、Cordis 保留自己的准备态分支。不增加自动/自管注册声明,不在 ToolTree 维护另一份工具分类名单。

与已有记录的关系

本记录替代业务节点组装中“start 只能是持久事件,同一 Context 只能收到一次 start”的限制。该记录的稳定 ID、业务 Definition、Context、Location、前序依赖和目标 Builder 职责继续适用。

考虑过的替代方案

只在 tool/call 创建节点。 无法展示参数生成期间已经确定的工具身份,多调用响应仍有明显空等。

通过 Assistant 片段和 Group 转接。 工具需要两种展示来源、额外去重和身份衔接;生命周期归属分散,渲染器还要根据片段标识反查 Assistant 内容。

保存或回放 preparing。 准备态只描述当前生成过程,持久调用和结果已经足以重建最终展示;恢复相同中间过程会增加不必要的状态和事件解释。

新增准备态注册机制。 通用工具已经共享 ToolRow 和参数模型,已有图标与标题可以直接复用。仅为这一阶段增加声明或配置,会多维护一套工具展示信息。

影响

  • 工具可以在参数完整前出现,但准备展示不表示工具已经执行;交互只使用已可用的字段。
  • 同一 ID 的多个 start 被视为同一个生命周期;业务必须为独立调用提供不同 ID,不能再依赖第二个 start 报错检测身份复用。
  • 各阶段复用通用行基础组件。write/edit 和 Bash 保持组件身份;自定义外壳可按阶段切换内部组件。
  • 部分参数的读取与更新策略遵循懒计算参数视图,不需要独立的原始前缀钩子。
  • 组装测试分别覆盖实时准备态、撤回、活跃流重建、持久历史与分页收敛;组件测试检查无参数展示和阶段切换,录制会话覆盖浏览器中的准备态与重载结果。