DSH / Atlas
2026-09-15implementedarchitecture

First-class tool-role messages

一等 tool-role 消息

The released Session format stores tool results inside a user-role `tool-result` content block, while the model conversation and provider protocols treat a tool result as a separate role. Keeping both descriptions active makes validation, replay, and adapter projections disagree.

Source compatibility: Chinese title uses a compatible non-canonical header

English

Problem

The released Session format stores tool results inside a user-role tool-result content block, while the model conversation and provider protocols treat a tool result as a separate role. Keeping both descriptions active makes validation, replay, and adapter projections disagree.

Decision

Session format V4 stores one tool result as a first-class role: 'tool' message. Its toolCallId, tool source call id, result content, and optional error flag are validated together. The V3-to-V4 edge lifts exactly one released wrapper and refuses nested wrappers before publication, retaining the source generation. Current V4 decoding is native-only; wrapper rows are accepted only by the adjacent migration edge.

Historical message and result extension fields keep distinct plugin:message: and plugin:result: names when their wrapper is removed. Complete original field names remain in each suffix, preventing collisions without adding core metadata fields. Opaque retention avoids inventing a shared field owner when the wrapper disappears. It retains duplicate values from distinct owners, and plugin consumers must use the V4 names. The wrapper alone supplies the interpreted call id and error flag; an old outer field with the same spelling remains opaque. The conversion specification owns exact preservation and refusal rules.

The conversation role map is closed. A model-visible role must have a persisted Session event and an adapter projection, so adapters do not silently turn an unlogged extension role into user text. Provider-specific source kinds remain merge-extensible and are owned by their producers.

Alternatives considered

  • Keep wrapper-shaped V4 rows — this preserves the released payload but leaves the canonical role and provider wire formats inconsistent.
  • Accept both wrapper and native V4 rows — dual current representations make encoding, decoding, recovery, and replay depend on which path produced the artifact.
  • Flatten historical nested results — leaf text does not retain nested call IDs or error status, and providers did not interpret nested content uniformly. Unsupported migration preserves the original data until a lossless conversion is available.
  • Keep role extensions open — an extension could reach a model request without a Session event and could not be reconstructed on replay.

Consequences

The adjacent migration changes the logical message role without changing message identity, tool-call identity, or result content. Native V4 semantic failures are hard admission failures; recoverable decoding still applies to released V3 canonical tails. Adapters map the first-class role to their provider-specific tool-result representation. The duplicated message toolCallId and source callId remain as an explicit equality-checked provider field and source-identity field pair.

中文

问题

已发布的 Session 格式把工具结果存储在 user-role 的 tool-result 内容块中,而模型对话与提供方协议把工具结果视为独立角色。两种描述同时存在会使校验、回放和适配器投影互相不一致。

决策

Session format V4 将一次工具结果存储为一等 role: 'tool' 消息。它的 toolCallId、工具来源调用标识、结果内容和可选错误标记一起校验。V3 到 V4 的边缘迁移只提升一个已发布的包装,并在发布前拒绝嵌套包装,保留源代际。当前 V4 解码仅接受原生形式;包装行只由相邻迁移边缘接受。

移除历史 wrapper 时,消息与结果的扩展字段分别保留为 plugin:message: 和 plugin:result: 名称。后缀保留完整原字段名,避免冲突且不添加核心 metadata 字段。不透明保留避免在 wrapper 消失时虚构共同字段 owner。不同 owner 的重复值仍分别保留,插件消费者必须使用 V4 名称。只有 wrapper 提供具有解释语义的调用 id 和错误标志;同名的旧外层字段保持不透明。转换规范拥有精确的保留与拒绝规则。

对话角色 map 是封闭的。模型可见角色必须有持久 Session 事件和适配器投影,因此适配器不会把未记录的扩展角色静默转换为 user 文本。提供方特有的来源 kind 仍然可合并扩展,并由各生产方拥有。

曾考虑的替代方案

  • 保留包装形式的 V4 行——保留已发布载荷,但规范角色与提供方 wire 格式仍不一致。
  • 同时接受包装和原生 V4 行——两种当前表示会让编码、解码、恢复和回放依赖于产出 artifact 的路径。
  • 展平历史嵌套结果——叶子文本无法保留嵌套调用 ID 或错误状态,而且提供方对嵌套内容的解释并不一致。拒绝不支持的迁移会保留原始数据,等待能够保留全部信息的转换。
  • 保持角色扩展开放——扩展角色可能在没有 Session 事件的情况下到达模型请求,也无法在回放时重建。

后果

相邻迁移会改变逻辑消息角色,但不改变消息身份、工具调用身份或结果内容。原生 V4 语义失败属于硬准入失败;可恢复解码仍适用于已发布 V3 的规范尾部。适配器把一等角色映射为各提供方自己的工具结果表示。消息 toolCallId 与来源 callId 的重复仍作为一对显式且经过相等性校验的提供方字段与来源字段保留。