DSH / Atlas
2026-08-27implementedarchitecture

Adjacent Agents share one Steer send_message operation

相邻 Agent 共享一个 Steer send_message 操作

Continuable Agents originally used direction-specific model controls. A parent called `send_message({ subagent_id, message })`, which delegated to a FIFO `followup` service operation. A child instead received a child-scoped `report({ output })` tool, a `tool:report` system-prompt section, and deployment-selected quiet or waking delivery. The tools described one adjacent-Agent operation through different schemas, serv

English

Problem

Continuable Agents originally used direction-specific model controls. A parent called send_message({ subagent_id, message }), which delegated to a FIFO followup service operation. A child instead received a child-scoped report({ output }) tool, a tool:report system-prompt section, and deployment-selected quiet or waking delivery. The tools described one adjacent-Agent operation through different schemas, service paths, source attribution, and scheduling.

A continuable child owns its own Session, so its parent does not automatically receive the child's transcript, tool output, or reasoning. The return path must therefore remain explicit and repeatable: a child may send progress before it finishes, remain available after sending, or fail before it can cooperate. Turning every final assistant message into an implicit result would conflate turn completion with model-selected communication and would not cover abnormal endings.

The child-only tool and system-prompt section also preceded every inherited fork turn. They made a continuable fork child's request head differ from its parent's before the history that fork exists to reuse, forcing the provider to prefill the entire copied transcript again.

Decision

SubagentRuntime.sendMessage(sender, targetId, content, { signal }) is the only public model-authored message operation. The continuation manager accepts only the exact live sender and a target on one adjacent edge:

  • parent to direct continuable child, authorized by the child's durable SessionHeader.parentSession;
  • resident continuable child to its exact live direct parent, authorized by the child's Activation.

Siblings, self-targets, ancestors beyond one edge, stale Agent objects, unknown targets, and one-shot children are not alternate routes. The operation has no caller-supplied source, delivery mode, offline parent mailbox, or provider dispatch.

Every accepted message uses Agent.steer(). A running target receives it at the nearest step boundary; an idle target starts a turn. An absent direct child is cold-resumed through the existing continuation lifecycle before the same Steer delivery. The manager retains waking-send accounting so a continuation-managed target cannot settle between synchronous inbox insertion and driver admission.

Every direction uses one durable source. The service derives senderSessionId from the authorized Agent and frames the model-visible content as Agent <sender-id> sent a message:, so attribution cannot diverge from authority.

import type { SessionId } from '@deepseek-ai/dsh-session'

interface AgentMessageSource {
  readonly kind: 'agent-message'
  readonly form: 'relay'
  readonly senderSessionId: SessionId
}

One model tool and one return instruction

The globally registered model tool is direction-neutral and has one fixed schema:

interface SendMessageInput {
  readonly agent_id: string
  readonly message: string
}

Parents and children inherit the same definition in the same registry order. The standard definition carries a process-stable internal identity that a scoped same-name tool does not satisfy. An independently selected preset may omit the tool, a historical descriptor filter may remove it on resume, and a scoped replacement may provide different semantics; neither case receives the standard call instruction. When the standard tool remains visible, the continuation manager appends the JSON-encoded direct parent id and the instruction to send one self-contained result before finishing, plus earlier actionable findings, to the child's initial user task. For a fork child this task follows the inherited completed-turn prefix; no child-only system-prompt section or tool schema precedes that prefix.

The instruction is guidance, not settlement enforcement. Sending does not end the child's turn, zero or several calls remain mechanically valid, and the runtime never rejects a child for staying silent. The manager-owned subagent-settled notice remains unconditional and separately attributed because it records how an Activation ended and preserves terminal output when the child cannot cooperate.

Human browser prompts are not model-authored Agent messages. The remote prompt path keeps a private Queue delivery so each human prompt remains a distinct turn. Interrupt behavior and settlement delivery remain independent.

Complete removal and reintroduction condition

The standalone @deepseek-ai/dsh-tool-subagent-report package, report schema, tool:report prompt section, reportDelivery configuration, report-specific message source, catalog entries, composition rows, and supported-behavior snapshots are absent. The unified tool gives up the recipient-free child shortcut and the old ability for a structural return tool to survive an explicit child allow-list. Those capabilities return only if a concrete use case requires semantics that an adjacent agent_id and fixed Steer cannot express; reintroducing them requires a distinct model operation and prefix-cost evidence, not an alias over sendMessage().

Alternatives considered

Keep followup and add child-to-parent routing. The name promises a later turn and inherits Agent.followup() semantics. It would obscure the chosen nearest-step behavior and preserve a parent-centric name for a direction-neutral capability.

Keep a recipient-free report wrapper over sendMessage(). This preserves a convenient child shortcut and lets a scope-local registration survive global tool filtering. It loses because the separate schema and prompt duplicate one operation, make parent and child request heads differ, and let equivalent directions drift again.

Make report global. Roots, one-shot children, remote children, and agentless callers cannot derive a report recipient. Advertising it globally would make schema visibility disagree with authority, while send_message already makes the recipient explicit.

Turn every child final message into an implicit send. A long-lived child may have nothing useful to send in one turn and several findings in another. Automatic delivery would merge model-authored communication with the runtime's settlement account and could not replace the unconditional notice on errors, cancellation, or token exhaustion.

Rely only on the tool description. A tool description helps after the model considers that tool; the failure mode is a child that believes it is finished without considering any return call. Initial-task guidance reaches that decision without changing the inherited system or tool prefix.

Keep quiet delivery as deployment policy. A quiet model-authored message can be accepted while an idle target never reads it. Fixed Steer gives both directions one delivery meaning and preserves accepted order with later settlement notices.

Consequences

  • Model consumers expose one send_message({ agent_id, message }) definition to parents and children, with no model-selected Queue versus Steer parameter.
  • The continuation manager remains the sole owner of adjacency authorization, residency, cold resume, waking admission, and teardown races.
  • Accepted messages may extend a running target's current turn; messages waiting together share next-step FIFO ordering.
  • Caller cancellation owns work only until inbox acceptance and does not retract an accepted message or dispose the target.
  • The initial task carries JSON-encoded dynamic parent addressing after a fork prefix, while the request-head system prompt and tool ordering remain reusable.
  • Human prompts, settlement notices, QueueDock, and the base bundle's one-shot fork policy remain separate decisions.

This decision consolidates and removes the fully superseded report-tool and child-report-obligation records. It supersedes the followup naming choice in Intent-named subagent continuation operations and retains the accepted-order guarantee in Child Agent messages precede their settlement notices.

中文

问题

可继续 Agent 最初使用方向专属的模型控制。parent 调用 send_message({ subagent_id, message }),委托给 FIFO followup 服务操作。child 则获得 child 作用域的 report({ output }) 工具、tool:report 系统提示词 section,以及由部署选择的静默或唤醒投递。两个工具用不同 schema、服务路径、来源与调度描述同一个相邻 Agent 操作。

可继续 child 拥有自己的 Session,因此 parent 不会自动收到 child 的 transcript(文本记录)、工具输出或推理。返回路径必须保持显式且可重复:child 可以在结束前发送进度、发送后仍保持可用,也可能在来得及配合前失败。把每条最终 assistant 消息变成隐式结果会混淆轮次完成与模型选择的通信,而且无法覆盖异常结束。

child 专属工具与系统提示词 section 还位于每个继承 fork 轮次之前。它们让可继续 fork child 的请求头在 fork 旨在复用的历史之前就与 parent 不同,迫使提供方重新预填充整份复制 transcript。

决策

SubagentRuntime.sendMessage(sender, targetId, content, { signal }) 是唯一公开的模型编写消息操作。继续执行管理器只接受确切在线 sender 与一条相邻边上的目标:

  • parent 到直接可继续 child,由 child 的持久化 SessionHeader.parentSession 授权;
  • 驻留的可继续 child 到其确切在线直接 parent,由 child 的 Activation 授权。

sibling、自身目标、超过一条边的 ancestor、陈旧 Agent 对象、未知目标与一次性 child 都不是替代路由。该操作没有调用方提供的 source、投递模式、离线 parent mailbox 或提供方分发。

每条被接受的消息都使用 Agent.steer()。运行中目标在最近 step 边界接收消息;空闲目标启动轮次。缺失的直接 child 会先通过现有继续执行生命周期冷恢复,再接受同一 Steer 投递。管理器保留唤醒发送记账,因此受继续执行管理的目标不会在同步 inbox 插入与 driver 准入之间结算。

两个方向使用同一种持久来源。服务从已授权 Agent 推导 senderSessionId,并把模型可见内容组装为 Agent <sender-id> sent a message:,因此来源信息不会偏离权限。

import type { SessionId } from '@deepseek-ai/dsh-session'

interface AgentMessageSource {
  readonly kind: 'agent-message'
  readonly form: 'relay'
  readonly senderSessionId: SessionId
}

一个模型工具与一条返回指令

全局注册的模型工具与方向无关,并使用一个固定 schema:

interface SendMessageInput {
  readonly agent_id: string
  readonly message: string
}

parent 与 child 以相同注册表顺序继承相同定义。标准定义携带进程稳定的内部身份,同名的作用域工具不满足该身份。独立选择的 preset 可以省略该工具,历史 descriptor 过滤器可在恢复时移除该工具,作用域替代工具也可以提供不同语义;两种情况都不会收到标准调用指令。当标准工具仍可见时,继续执行管理器会把经过 JSON 编码的直接 parent id、结束前发送一份自包含结果的指令,以及更早发送可操作发现的指令追加到 child 初始用户任务。对 fork child 而言,该任务位于继承的已完成轮次前缀之后;没有 child 专属系统提示词 section 或工具 schema 位于此前缀之前。

该指令是指导,不是结算强制。发送不会结束 child 轮次,机制仍允许零次或多次调用,runtime 绝不会因 child 保持沉默而拒绝它。由管理器负责的 subagent-settled 通知仍无条件发送并采用独立来源,因为它记录 Activation 如何结束,并在 child 无法配合时保留终态输出。

浏览器中的人类提示不是模型编写的 Agent 消息。远程提示路径保留私有 Queue 投递,使每条人类提示保持为独立轮次。中断行为与结算投递保持独立。

完整移除与重新引入条件

独立的 @deepseek-ai/dsh-tool-subagent-report 包、report schema、tool:report 提示词 section、reportDelivery 配置、report 专属消息来源、目录项、组合行和受支持行为快照均已不存在。统一工具放弃了无需接收方的 child 快捷方式,也放弃了让结构性返回工具绕过显式 child allow-list 的旧能力。只有具体用例需要相邻 agent_id 与固定 Steer 无法表达的语义时,这些能力才会重新出现;重新引入需要独立的模型操作与前缀成本证据,而非 sendMessage() 之上的别名。

考虑过的替代方案

保留 followup 并添加 child 到 parent 路由。 该名称承诺后续轮次并继承 Agent.followup() 语义。它会掩盖选定的最近 step 行为,并为方向无关能力保留以 parent 为中心的名称。

保留 sendMessage() 之上无需接收方的 report 包装层。 这会保留便利的 child 快捷方式,并让作用域局部注册绕过全局工具过滤。它落选是因为独立 schema 与提示词重复一个操作、使 parent 与 child 请求头不同,并允许等价方向再次漂移。

让 report 全局可见。 根 Agent、一次性 child、远程 child 与无 Agent 调用方无法推导 report 接收方。全局宣传它会让 schema 可见性与权限不一致,而 send_message 已显式给出接收方。

把每条 child 最终消息变成隐式发送。 长期运行的 child 可能在某个轮次没有值得发送的内容,在另一个轮次却有多条发现。自动投递会混合模型编写通信与 runtime 结算说明,而且无法替代错误、取消或 token 耗尽时的无条件通知。

只依赖工具描述。 工具描述会在模型考虑该工具后提供帮助;失败模式是 child 认为自己已经完成而根本没有考虑返回调用。初始任务指导能触及该决策,又不会改变继承的系统或工具前缀。

保留静默投递作为部署策略。 静默的模型编写消息可能被接受,但空闲目标永远不会读取它。固定 Steer 为两个方向提供一种投递含义,并保持与后续结算通知的接受顺序。

后果

  • 模型 Consumer 向 parent 与 child 公开一个 send_message({ agent_id, message }) 定义,不提供模型选择的 Queue 与 Steer 参数。
  • 继续执行管理器仍是相邻关系授权、驻留、冷恢复、唤醒准入与拆卸竞态的唯一所有者。
  • 被接受的消息可以延长运行中目标的当前轮次;一起等待的消息共享 next-step FIFO 顺序。
  • 调用方取消只在 inbox 接受前掌管工作,不会撤回已接受消息或 dispose(资源释放)目标。
  • 初始任务在 fork 前缀之后携带经过 JSON 编码的动态 parent 地址,而请求头系统提示词与工具顺序保持可复用。
  • 人类提示、结算通知、QueueDock 与 base bundle 的一次性 fork 策略仍是独立决策。

本决策合并并删除了已完全被取代的 report 工具与 child report 义务记录。它取代按意图命名的 subagent 继续执行操作中的 followup 命名选择,并保留Child Agent 消息先于其结算通知中的接受顺序保证。