DSH / Atlas
2026-09-20implementedarchitecture

Active tool headers and stateful request projection

有效工具请求头与有状态请求投影

Plugin and MCP tools change during conversations. Providers differ in whether they activate deferred definitions or remove tools through history. Storing retained provider declarations as active tools makes comparisons repeat removals and miss restorations.

English

Problem

Plugin and MCP tools change during conversations. Providers differ in whether they activate deferred definitions or remove tools through history. Storing retained provider declarations as active tools makes comparisons repeat removals and miss restorations.

Decision

request/header.tools records assembled active tools independently of model capability. The loop compares names against the preceding header and emits one developer/message with additions and removals under the tool-registry source. Additions reference the new header through headerSeq. Definition changes remain header changes; unchanged names emit no update. Existing header equality and request-series rules remain in force.

Session.toolHistory() caches a capability-independent fold of committed headers and developer messages. First access restores inherited events; later reads consume only unseen events. Immutable snapshots carry initial declarations and ordered update identities with additions resolved through their historical headers. Explicit series starts and changed retained definitions rebuild declarations. Restoring a retained name with an unchanged definition continues the series: the projection keeps the declaration unchanged and lets the recorded addition re-offer the tool after its earlier removal, so a removal never disables a declared tool and a loading mode never rewrites an earlier declaration.

Loop and compaction requests carry this snapshot as GenerateOptions.toolHistory. At adapter dispatch, projectToolUpdates uses the prepared route's toolUpdate. Unsupported routes receive active definitions without deferLoading or developer messages. Supported routes defer historical additions; in-history retains removed definitions and removal blocks, while addition-only omits both. Only current-series update identities survive. Missing history or a request prefix omitting recorded updates uses current declarations without developer updates. Explicit deferred loading remains available on capable routes.

DeepSeek translates projected updates into system-role tool_addition and tool_removal blocks and defer_loading declarations, sending the tool-changes beta header when update blocks occur. The default deepseek-flash entry declares addition-only; pi-ai declares no mode. Chat and Trajectory display developer messages as context nodes, using localized tool-change notices: single changes show the tool name; multiple changes show counts and expandable name lists.

Alternatives considered

Store provider declarations in headers. Retained removed definitions are not active tools. Mixing those meanings makes durable updates capability-dependent and breaks change detection.

Reconstruct definitions in adapters from messages alone. Addition messages contain names, not schemas or event-level header references. Session owns that historical relationship; adapters translate projected requests.

Scan the complete log on every request. The cached fold consumes new events and supplies stable snapshots to ordinary and auxiliary calls without adding arbitrary synchronous history readers.

Reject same-name schema changes during tool registration. This restriction is not enforced. Immediate failure for a plugin registering an incompatible tool requires comparing the new registration with historical definitions of tools called on each affected current surface, while excluding calls removed by compaction. That registration-time historical and scope-aware check is deferred because of its implementation complexity. Same-name schema changes remain allowed; they rebuild declarations and invalidate prefix reuse even on in-history routes.

Consequences

Tool lists and logical update events are capability-independent; only outgoing declarations and messages vary. Calls to removed retained tools fail through the existing UNKNOWN_TOOL path. Changed definitions and incomplete auxiliary prefixes sacrifice prefix reuse for coherent declarations; unchanged restoration keeps the cached prefix on in-history routes only while the request series continues. Routes supporting tool updates and in-history system prompts preserve earlier messages when tools are added alongside a changed prompt or after an earlier prompt update. Routes without tool-update support still consolidate on tool changes; explicit series starts and surface replacements still consolidate on every route. No persisted fields or Session format revision are added.

The authored headless prompt/tool scenario pins an appended prompt beside a deferred tool addition and verifies that the first request's messages stay unchanged. It shares the stable-prompt scenario's tool lifecycle and schema expectations.

The real DeepSeek SDK test measures the first request after a native tool addition with both unchanged and simultaneously updated instructions. Unique user history prevents a shared system-prompt cache hit from satisfying the preceding-input assertion. Removal checks tool availability without requiring cache retention; each case disables retries.

中文

Problem

插件和 MCP 工具会在对话期间变化。不同提供方对通过历史激活延迟定义或移除工具的支持不同。把为提供方保留的声明当作有效工具存储,会使比较重复记录移除并遗漏恢复。

Decision

request/header.tools 记录组装得到的有效工具,不依赖模型能力。循环将名称与前一请求头比较,以 tool-registry 来源发出一条包含添加和移除的 developer/message。添加通过 headerSeq 引用新请求头。定义变化仍体现为请求头变化;名称未变时不发出更新。现有请求头相等判断和请求序列规则保持有效。

Session.toolHistory() 缓存对已提交请求头和 developer 消息的归约,不依赖模型能力。首次访问恢复继承事件;后续读取仅处理新增事件。不可变快照携带初始声明和有序更新标识,添加定义通过历史请求头解析。显式新序列和保留定义变化会重建声明。以未变化的定义恢复保留名称时序列继续:投影保持声明不变,由记录的添加在先前移除之后重新提供该工具,因此移除不会禁用已声明的工具,加载模式也不会改写先前声明。

循环和压缩请求通过 GenerateOptions.toolHistory 携带此快照。适配器分发时,projectToolUpdates 使用已准备路由的 toolUpdate。不支持的路由接收有效定义,不携带 deferLoading 或 developer 消息。支持的路由延迟历史添加;in-history 保留已移除定义和移除块,addition-only 则省略两者。仅保留当前序列的更新标识。缺少历史或请求前缀遗漏已记录更新时,使用当前声明且不发送 developer 更新。显式延迟加载在支持的路由上仍然可用。

DeepSeek 把投影后的更新转换为 system 角色的 tool_addition 和 tool_removal 块以及 defer_loading 声明,在存在更新块时发送工具变更 beta 请求头。默认 deepseek-flash 条目声明 addition-only;pi-ai 不声明模式。Chat 和 Trajectory 将 developer 消息显示为上下文节点,工具变更使用本地化通知:单项变更显示工具名,多项变更显示数量及可展开的名称列表。

Alternatives considered

在请求头存储提供方声明。 为提供方保留的已移除定义不是有效工具。混合两种含义会使持久更新依赖能力,并破坏变更检测。

仅凭消息在适配器重建定义。 添加消息包含名称,不包含模式或事件级请求头引用。Session 拥有该历史关系;适配器转换投影后的请求。

每次请求扫描完整日志。 缓存归约处理新事件,为普通和辅助调用提供稳定快照,不增加任意同步历史读取接口。

在工具注册时拒绝同名 schema 变化。 当前不执行此限制。要让注册不兼容工具的插件立即收到异常,需要将新注册与各受影响当前 surface 中已调用工具的历史定义比较,同时排除已被压缩移除的调用。由于实现复杂,注册时结合历史和作用域的检查留待后续实现。同名 schema 变化仍被允许;它们会重建声明,即使在 in-history 路由上也会使前缀复用失效。

Consequences

工具列表和逻辑更新事件不依赖能力;只有发出的声明和消息不同。调用已移除但保留声明的工具时,通过现有 UNKNOWN_TOOL 路径失败。定义变化和不完整辅助前缀为保证声明一致而放弃前缀复用;仅当请求序列继续时,未变化的恢复才在 in-history 路由上保留缓存前缀。同时支持工具更新和历史内系统提示词的路由,在工具添加与提示词变化同时发生或晚于此前提示词更新时,保留此前消息。不支持工具更新的路由仍在工具变化时归并提示词;所有路由仍在显式新序列和 surface 替换时归并。不增加持久字段或 Session 格式版本。

手写的无界面提示词/工具场景钉住追加提示词与延迟工具添加同时发生的行为,并验证首次请求的消息保持不变。它复用稳定提示词场景的工具生命周期和 schema 期望值。

真实 DeepSeek SDK 测试 分别在指令不变和指令同时更新时测量原生工具新增后的首次请求。独有的用户历史确保仅命中共享系统提示词缓存无法满足前序输入断言。移除场景检查工具可用性,不要求保留缓存;每个用例均禁用重试。