DSH / Atlas
2026-08-21implementedfeaturearchived 2026-09-04

Out-of-process subagents expose minimal actionable diagnostics

进程外 subagent 公开最小可行动诊断

An ACP or DSH SDK child can stop because it reached a remote limit, denied a required permission, ended with a non-completed child turn, lost its protocol transport, or exited as a process. The shared result historically reduced these outcomes to a stop reason such as `error`, while startup and cleanup rejection messages could expose the original exception. A parent could not choose between narrowing the task, adjust

English

Problem

An ACP or DSH SDK child can stop because it reached a remote limit, denied a required permission, ended with a non-completed child turn, lost its protocol transport, or exited as a process. The shared result historically reduced these outcomes to a stop reason such as error, while startup and cleanup rejection messages could expose the original exception. A parent could not choose between narrowing the task, adjusting permission policy, or repairing the child deployment without Host logs.

Copying exceptions, stderr, task content, tool input, paths, environment values, credentials, or protocol payloads into SubagentResult.diagnostic would make untrusted child text model-visible. Reusing a complete product-specific error union would also duplicate independently versioned authorities in the provider-neutral subagent seam.

Decision

Each out-of-process provider owns a small mapping from facts it already receives at its protocol and process lifecycle points to fixed safe display text. The ACP provider derives it from closed stop reasons, current operation, closed tool kind, configured permission policy, selected permission outcome, and the managed subprocess exit code or signal. The DSH SDK provider derives it from the child turn/end reason, current SDK operation, and exported SDK error class. Consumers continue to use the existing optional SubagentResult.diagnostic; they do not parse its punctuation or provider-private category names.

Safe failure text

Generic error diagnostics have this fixed field order:

Subagent failure (provider: <provider>; stage: <stage>; category: <category>; stop reason: <reason>; exit code: <code>; signal: <signal>)

Unavailable optional fields are omitted. The complete result is limited to 4096 UTF-8 bytes by the shared settlement boundary. Successful results and local cancellation carry no failure diagnostic. Partial assistant output remains in SubagentResult.output and is presented separately.

When an ACP permission request contributes to a non-completed result, a fixed line records policy, the closed ACP tool request kind, and decision. Tool titles, raw input, locations, option names, and metadata are excluded. For max-tokens, refusal, or remote aborted, the public stop reason already carries the terminal fact, so the permission line is the complete diagnostic; generic error paths append it after the failure line. A diagnostic-bearing remote aborted result keeps its public stop reason; the one-shot Job adapter treats it as failed, while diagnostic-free local cancellation remains killed.

ACP facts

StageOwned operationSafe categories and facts
initializeParent workspace resolution and ACP initializeconfiguration, transport, or process-exit
new-sessionACP session/new and returned session-id validationprotocol, transport, or process-exit
promptACP prompt request, remote stop reason, and permission callbackremote-limit, transport, unknown, or a permission-only diagnostic
processChild-process spawn failure, or a managed child exits before a prompt terminal responseprocess-start, or process-exit plus independently observed exit code and signal
teardownEOF quiescence and managed process-tree terminationFixed teardown facts; the original cleanup failure remains internal

max_turn_requests remains the shared error stop reason and adds remote-limit. An unknown stop reason remains error and becomes the fixed unknown category without copying the value. max_tokens, refusal, and cancelled keep their existing shared stop reasons; they add a diagnostic only when a permission decision must be explained.

DSH SDK facts

StageOwned operationSafe categories and facts
initializeParent workspace resolution, SDK runtime spawn, and initialize handshakeconfiguration, protocol, transport, or unknown
session-runPrompt acceptance, session notifications, and final child reasonchild-error, child-disposed, child-unknown, missing-terminal, protocol, transport, or unknown
shutdownBounded SDK shutdown and runtime process releaseunknown; protocol-shutdown failures remain Host-only in the SDK client

Child completed, max-tokens, and ordinary aborted results keep their existing shared stop reasons without extra text. An aborted turn whose closed cause is disposed keeps aborted and adds child-disposed. blocked reuses refusal; error adds child-error. Persistence repair alone produces interrupted, so this fresh-session provider leaves it as generic error without a diagnostic. A missing terminal event adds missing-terminal; an unknown reason uses child-unknown without copying the value or the child's structured failure message.

During initialize or session run, SdkProtocolError and JSON-RPC error responses map to protocol, and TransportClosedError maps to transport; the provider never reads their messages. Other exceptions and shutdown rejection use unknown. Request timeout classification remains deferred because this provider does not configure or propagate a request timeout.

Ownership and lifecycle

Fact or resourceOwnerConsumer behavior
Protocol terminal factACP server or child Harness SessionEach provider maps only its owned closed values and uses fixed unknown fallbacks
Current failure stage and operation-local detailOne provider runDerived at the failure point and discarded with the run; concurrent runs share no diagnostic state
Exit code and signalACP's dsh-subprocess handleDisplayed only after the managed outcome is observed; stderr is never parsed
SDK error categoryTypeScript SDK client error classClassified with instanceof; the Error message and stderr tail remain internal
Diagnostic bytes and presentationdsh-subagent, foreground tool, and Job runtimeThe same bounded text stays separate from assistant output in foreground and one-shot background modes
Raw failureChild runtime, Error cause chain, and Host loggerAvailable for Host diagnosis only, never copied into the parent model result

Startup publishes no run until the provider's handshake completes. Successful startup cleanup rolls the private child back to quiescence before rejection. Cleanup failure preserves startup plus teardown/shutdown for an ordinary failure, or cleanup alone after cancellation, without claiming complete managed-process quiescence. A published run settles its result without rejection, and dispose() independently reports safe teardown or shutdown facts while still using the backend's existing process cleanup ladder.

Verification

ACP package tests drive a real stdio protocol child and pin every stop-reason mapping, remote-limit and unknown fallbacks, permission allow/deny facts, configuration, initialize, new-session, prompt, process, and teardown stages, startup rollback, successful-result and local-cancellation omission, partial output, concurrent-run isolation, Host-only raw errors, process quiescence, and the shared multibyte diagnostic limit. DSH SDK package tests drive the real SDK client against its stdio fake runtime and pin every reachable child reason, current typed SDK category, initialize/session-run/shutdown stages, SDK-owned failed-start cleanup, cancellation cleanup, partial output, concurrency, sanitization, and quiescence. Loader compositions prove each real configured provider reaches the model-visible foreground result. Keyless ACP and JSON-RPC snapshots pin each provider's exact foreground and one-shot background diagnostic text.

Alternatives considered

Return raw exceptions, stderr, or protocol payloads. These values can contain task content, tool input, paths, environment values, credentials, and upstream prose. Fixed allowlisted facts preserve the actionable distinction without expanding the model-visible trust boundary.

Add a shared structured error enum. ACP and other process-backed providers own different lifecycle points and closed termination vocabularies. A shared enum would invent false equivalence and force unrelated consumers to track provider releases.

Parse exception messages or stderr into categories. Free-form text is neither stable nor safe. Only closed protocol values, typed errors, current call sites, and managed process outcomes qualify as diagnostic inputs.

Change existing stop reasons. The stop reason remains the provider-neutral terminal result. The optional diagnostic explains why a non-completed result needs a different next action without adding new public result states.

Add retries, recovery state, or interactive approval. Diagnostics report a failure; they do not own remediation. Retry policy, session recovery, and human interaction require separate user contracts and lifecycle owners.

Consequences

The parent can distinguish an ACP remote limit or permission decision and a DSH child-turn, protocol, transport, or shutdown failure without receiving child-controlled text. Startup and cleanup errors use the same safe facts as published results, while Host observation retains the original cause.

The diagnostic remains display text rather than a public protocol. Consumers may present it but must not branch on its format. This decision adds no retry policy, recovery controller, shared provider-error enum, stderr classifier, authentication taxonomy, session persistence, progress stream, or new ACP capability.

中文

Problem

ACP 或 DSH SDK 子进程可能因为达到远端限制、拒绝必需权限、以非完成子轮次结束、失去协议传输或进程退出而停止。共享结果以往只把这些结果压成 error 等结束原因,而启动和清理拒绝的消息还可能暴露原始异常。父 agent 若不读取 Host 日志,就无法决定应缩小任务、调整权限策略还是修复子运行时部署。

若把异常、stderr、任务内容、工具输入、路径、环境值、凭证或协议 payload 复制进 SubagentResult.diagnostic,不受信任的子进程文本就会变成模型可见内容。若复用完整的产品专属错误联合,又会在提供方无关的 subagent seam 中复制彼此独立版本化的权威。

Decision

每个进程外提供方分别拥有一份小型映射,把其协议与进程生命周期位置已经收到的事实转换成固定安全展示文本。ACP 提供方使用闭集结束原因、当前操作、闭集工具种类、已配置权限策略、选中的权限结果,以及受管子进程退出码或信号来派生。DSH SDK 提供方使用子 turn/end 原因、当前 SDK 操作与导出的 SDK 错误 class 来派生。消费方继续使用现有可选 SubagentResult.diagnostic,且不解析其标点或提供方私有 category 名称。

安全失败文本

通用 error 诊断采用以下固定字段顺序:

Subagent failure (provider: <provider>; stage: <stage>; category: <category>; stop reason: <reason>; exit code: <code>; signal: <signal>)

不可用的可选字段会被省略。共享结算边界会把完整结果限制在 4096 个 UTF-8 字节以内。成功结果和本地取消不携带失败诊断。部分 assistant 输出继续保留在 SubagentResult.output 中,并与诊断分开呈现。

当 ACP 权限请求参与非完成结果时,一个固定行会记录 policy、ACP 闭集工具 request 种类和 decision。工具标题、raw input、位置、选项名称与 metadata 均被排除。对于 max-tokens、refusal 或远端 aborted,公共结束原因已经携带终态事实,因此权限行就是完整诊断;通用 error 路径则把它附在失败行之后。带诊断的远端 aborted 结果仍保持公共结束原因;一次性 Job adapter 会把它判为 failed,而不带诊断的本地取消仍是 killed。

ACP 事实

Stage归属操作安全 category 与事实
initialize父工作区解析与 ACP initializeconfiguration、transport 或 process-exit
new-sessionACP session/new 与返回 session id 校验protocol、transport 或 process-exit
promptACP prompt 请求、远端结束原因与权限回调remote-limit、transport、unknown 或仅权限诊断
process子进程 spawn 失败,或受管子进程先于 prompt 终态响应退出process-start,或 process-exit 以及分别观测到的退出码与信号
teardownEOF 停稳与受管进程树终止固定 teardown 事实;原始清理失败仍留在内部

max_turn_requests 继续映射到共享 error,并附加 remote-limit。未知结束原因继续映射到 error,category 固定为 unknown,不会复制原值。max_tokens、refusal 与 cancelled 保持既有共享结束原因;只有需要解释权限决定时才会附加诊断。

DSH SDK 事实

Stage归属操作安全 category 与事实
initialize父工作区解析、SDK 运行时 spawn 与 initialize 握手configuration、protocol、transport 或 unknown
session-runprompt 接受、会话通知与最终子轮次原因child-error、child-disposed、child-unknown、missing-terminal、protocol、transport 或 unknown
shutdown有界 SDK shutdown 与运行时进程释放unknown;协议 shutdown 失败仍留在 SDK 客户端的 Host 诊断中

子 completed、max-tokens 与普通 aborted 结果保持既有共享结束原因,不附加文本。闭集原因是 disposed 的 aborted 轮次仍保持 aborted,并附加 child-disposed。blocked 复用 refusal;error 附加 child-error。只有持久化修复会产生 interrupted,因此本全新会话提供方把它保留为不带诊断的通用 error。缺失终态事件会附加 missing-terminal;未知原因使用 child-unknown,且不复制原值或子进程结构化失败消息。

在 initialize 或 session run 期间,SdkProtocolError 与 JSON-RPC 错误响应映射为 protocol,TransportClosedError 映射为 transport;提供方绝不读取其消息。其他异常和 shutdown 拒绝使用 unknown。由于本提供方没有配置或传播 request timeout,请求超时分类继续推迟。

所有权与生命周期

事实或资源Owner消费方行为
协议终态事实ACP server 或子 Harness Session每个提供方只映射自身拥有的闭集值,并使用固定 unknown 回退
当前失败 stage 与 operation-local 细节单次提供方运行只在失败点派生,并随运行丢弃;并发运行不共享诊断状态
退出码与信号ACP 的 dsh-subprocess 句柄仅在观测到受管结果后展示;绝不解析 stderr
SDK 错误 categoryTypeScript SDK 客户端错误 class仅通过 instanceof 分类;Error 消息和 stderr tail 留在内部
诊断字节与呈现dsh-subagent、前台工具与 Job 运行时前台和一次性后台模式都把同一份有界文本与 assistant 输出分开
原始失败子运行时、Error cause 链与 Host logger只供 Host 排障,绝不复制进父模型结果

启动只有在提供方握手完成后才发布运行。启动清理成功时,私有子进程会先回滚到完全停稳再拒绝。清理失败时,普通失败会保留启动与 teardown/shutdown,取消后只保留清理事实,且不会宣称受管进程已经完全停稳。已发布运行的结果不会拒绝,而 dispose() 会独立报告安全 teardown 或 shutdown 事实,并继续使用后端既有的进程清理阶梯。

Verification

ACP 包测试通过真实 stdio 协议子进程固定全部结束原因映射、远端限制与 unknown 回退、权限 allow/deny 事实、configuration、initialize、new-session、prompt、process 与 teardown stage、启动回滚、成功结果与本地取消省略、部分输出、并发运行隔离、仅 Host 可见的原始错误、进程完全停稳,以及共享多字节诊断限制。DSH SDK 包测试通过真实 SDK 客户端驱动其 stdio 伪运行时,固定全部可达子轮次原因、当前 typed SDK category、initialize/session-run/shutdown stage、SDK 自有失败启动清理、本地取消清理、部分输出、并发、脱敏与停稳。Loader 组合证明两个真实配置的提供方都能到达模型可见前台结果。无密钥 ACP 与 JSON-RPC snapshot 会固定各自提供方的准确前台与一次性后台诊断文本。

Alternatives considered

返回原始异常、stderr 或协议 payload。 这些值可能包含任务内容、工具输入、路径、环境值、凭证和上游文本。固定白名单事实能够保留可行动差异,而不扩大模型可见信任边界。

增加共享结构化错误 enum。 ACP 与其他进程外提供方拥有不同生命周期位置和闭集终止词汇。共享 enum 会制造虚假的统一,并迫使无关消费方跟随提供方版本。

解析异常消息或 stderr 来分类。 自由文本既不稳定也不安全。只有闭集协议值、typed 错误、当前调用位置与受管进程结果可以成为诊断输入。

修改既有结束原因。 结束原因继续表示提供方无关的终态结果。可选诊断说明非完成结果为何要求不同的下一步,而不增加新的公共结果状态。

增加重试、恢复状态或交互审批。 诊断只负责报告失败,不拥有修复动作。重试策略、会话恢复与人工交互需要独立用户约定和生命周期责任方。

Consequences

父 agent 可以区分 ACP 远端限制或权限决定,以及 DSH 子轮次、协议、传输或 shutdown 失败,同时不会接收子进程控制的文本。启动和清理错误与已发布结果使用同一套安全事实,而 Host 观测仍保留原始 cause。

诊断仍是展示文本,不是公共协议。消费方可以呈现它,但不得按格式分支。本决策不增加重试策略、恢复控制器、共享提供方错误 enum、stderr 分类器、认证分类、会话持久化、进度流或新的 ACP 能力。