Address four findings from the first Codex review round: - Contain subagent/start|end listener throws (emitContainedStart/End): a thrown lifecycle listener could escape SubagentService.start() before the caller received the live run to dispose it (a leaked child), and a thrown subagent/end listener could surface as an unhandled rejection on the detached result-settle hook. Both emits now log-and-contain, mirroring the agent registry's agent/created|disposed containment. - Make the model-facing tool name configurable (Config.toolName, default subagent). The docs say to load dsh-tool-subagent once per provider to expose multiple transports, but the hardcoded name made the second load throw a duplicate-tool-name error; a distinct toolName per load is now required and documented. - Reach the per-file 100% coverage gate: tests for the subagent/end error branch, lifecycle-listener containment, every stopReasonError arm + the merge-extensible default, the multi-provider toolName path, agentOptions forwarding, and the direct-apply schema-bypass fallbacks. - Document the seam vocabulary in docs/core-data-structures/subagent.md with verbatim type-equiv blocks + manifest entries, and link it from core.md (a brand-new core/seam type the doc-sync gate cannot detect on its own).
20 lines
2.0 KiB
Markdown
20 lines
2.0 KiB
Markdown
# @deepseek-ai/dsh-tool-subagent
|
|
|
|
The model-facing `subagent` tool: delegate a self-contained task to a child agent and return its final output. Pure schema + lifecycle shaping over the [`ctx.subagents`](../subagent/README.md) provider registry — an in-process, ACP, or future A2A backend swaps in without changing what the model sees.
|
|
|
|
## Provider selection is config, not model-facing
|
|
|
|
This plugin binds to **exactly one** provider (`Config.provider`). The model sees only `{ description, prompt }` — there is no provider/type parameter in the schema. To expose more than one transport, load the plugin more than once, each bound to a different provider **and a distinct `toolName`** (the tool registry rejects a duplicate name, so a second load that kept the default `subagent` name would throw). Keeping selection in config (not the schema) is the deliberate split: the *service* holds a multi-provider registry; the *tool* picks one.
|
|
|
|
| Config key | Meaning |
|
|
|---|---|
|
|
| `provider` (required) | The `ctx.subagents` provider name to start runs on (`spawn`, `fork`, `acp`, …). |
|
|
| `toolName` | The model-facing tool name to register (default `subagent`). Set a distinct value per load when exposing multiple providers, e.g. `subagent` + `subagent_acp`. |
|
|
| `agentOptions` | Default per-child `{ model?, systemPrompt? }` applied to every spawned child. |
|
|
|
|
## Lifecycle (synchronous collect)
|
|
|
|
`execute` starts a run on the configured provider and **awaits `run.result` inside a `try/finally` that always `dispose()`s the run** — the owned child agent/session is torn down on every path (success, error, abort), never leaked. The tool's abort signal (`exec.signal`) is bridged to `run.cancel()`. A non-`completed` stop reason (aborted/error/max-tokens/refusal) maps to an `isError` tool result rather than returning partial output as success.
|
|
|
|
Background / poll collection is deferred (see the [RFC](../../../docs/rfc/proposed/feature/2026-06-21-subagent-capability-seam.md)); this cut blocks the parent turn until the child finishes.
|