# Conflicts: # docs/architecture.md # docs/config-catalog.md # docs/cordis-catalog/services.md # docs/core-data-structures/bash.md # docs/event-producer-consumer.md # docs/rfc/implemented/feature/2026-07-06-sandbox.md # docs/rfc/proposed/architecture/2026-06-20-generic-long-running-tool-runtime.md # examples/AGENTS.md # examples/acp-agent/tests/snapshots/advanced-toolchain/session.1.jsonl # examples/acp-agent/tests/snapshots/advanced-toolchain/session.2.jsonl # examples/acp-agent/tests/snapshots/advanced-toolchain/session.jsonl # examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.golden.md # examples/acp-agent/tests/snapshots/both-mode-turn/session.jsonl # examples/acp-agent/tests/snapshots/both-mode-turn/system-prompt.golden.md # examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.golden.md # examples/acp-agent/tests/snapshots/permission-switching/system-prompt.golden.md # examples/acp-agent/tests/snapshots/skill-load/session.jsonl # examples/acp-agent/tests/snapshots/skill-load/system-prompt.golden.md # examples/acp-agent/tests/snapshots/text-turn/session.jsonl # examples/acp-agent/tests/snapshots/text-turn/system-prompt.golden.md # examples/coding-agent/cordis.yml # examples/sandbox-acp-agent/tests/snapshots/escalation-approved/session.jsonl # examples/sandbox-acp-agent/tests/snapshots/escalation-rejected/session.jsonl # examples/sandbox-acp-agent/tests/snapshots/mode-switching/session.jsonl # packages/bash/bash-local/README.md # packages/bash/bash-local/src/index.ts # packages/bash/bash-local/tests/executor.spec.ts # packages/bash/bash-sandbox/README.md # packages/bash/bash-sandbox/src/index.ts # packages/bash/bash/README.md # packages/bash/bash/src/index.ts # packages/bash/bash/src/types.ts # packages/bash/tool-bash/README.md # packages/bash/tool-bash/src/index.ts # packages/bash/tool-bash/tests/tools.spec.ts # packages/cordis/tool-cordis/src/api-catalog.ts # packages/core/agent-core/README.md # packages/core/agent-core/src/index.ts # packages/subagent/subagent/README.md # packages/subagent/tool-subagent/README.md # packages/subagent/tool-subagent/src/index.ts # packages/ui/acp-agent/README.md # packages/ui/acp/README.md # packages/ui/stdio-agent/README.md # packages/util/brand/src/index.ts # scripts/doc-budgets.manifest.json
71 lines
5.6 KiB
Markdown
71 lines
5.6 KiB
Markdown
# @deepseek-ai/dsh-subagent
|
|
|
|
The subagent seam lets one agent delegate work to a child through a named provider. Callers use one service API (`ctx.subagents`); providers decide whether the child runs in this process, in another process, or through a future transport.
|
|
|
|
## Package roles
|
|
|
|
The family separates the stable interface from implementations and model-facing tools:
|
|
|
|
| Package | Role |
|
|
|---|---|
|
|
| `@deepseek-ai/dsh-subagent` | Provider registry, request/result types, and lifecycle events. |
|
|
| `@deepseek-ai/dsh-subagent-spawn` | Fresh in-process child. |
|
|
| `@deepseek-ai/dsh-subagent-fork` | In-process child seeded with completed parent turns. |
|
|
| `@deepseek-ai/dsh-subagent-acp` | Fresh out-of-process ACP child. |
|
|
| `@deepseek-ai/dsh-tool-subagent` | Model-facing tool over one configured provider. |
|
|
|
|
Multiple providers may coexist under different names. This lets a deployment expose, for example, a cheap in-process child and an isolated ACP child without changing the service contract.
|
|
|
|
## Service API
|
|
|
|
`SubagentService` has four main operations:
|
|
|
|
| Member | Meaning |
|
|
|---|---|
|
|
| `registerProvider(provider)` | Register one trusted same-process implementation by name. Registration is effect-scoped; removing it prevents new starts but does not revoke runs already returned to callers. Duplicate names fail loud. |
|
|
| `getProvider(name)` | Return the provider, or `undefined` when absent. |
|
|
| `list()` | Return provider names in insertion order. |
|
|
| `start(name, request)` | Validate requested capabilities and semantic values, then await the provider until a real child is ready. Fulfillment returns a holder-owned `SubagentRun`; rejection means the provider has already cleaned every partial startup resource. |
|
|
|
|
`SubagentStartRequest.signal` is required and is the canonical cancellation channel. An abort before publication makes `start()` reject after rollback; an abort after publication cancels the live child. The request may also select a model, require structured output, cap delegation depth, restrict child tools, or set a child persona.
|
|
|
|
Same-process requests, descriptors, results, and event payloads are trusted typed values borrowed as immutable. The service does not clone or freeze them; serialization and hostile-input validation belong at actual process, worker, persistence, and model boundaries.
|
|
|
|
## Capabilities
|
|
|
|
Start-time features are advertised in `provider.capabilities` because the service must reject an unsupported request before child creation:
|
|
|
|
- `outputSchema` — enforce a structured final result.
|
|
- `depthLimit` — enforce `maxDepth`.
|
|
- `toolFilter` — apply the requested child tool restriction.
|
|
- `persona` — apply a per-child persona.
|
|
|
|
Runtime features are optional methods on `SubagentRun`: `sendMessage?` steers a live child, while `resume?` asynchronously creates a continuation run. Method presence is the capability check.
|
|
|
|
`inheritsParentContext` is descriptive rather than enforceable. It says only whether the child sees completed parent conversation history (`fork` does; `spawn` and ACP do not), not whether it inherits tools, services, or authority.
|
|
|
|
## Ownership and lifecycle
|
|
|
|
`provider.start(request): Promise<SubagentRun>` is the ownership-transfer boundary. Before fulfillment, the provider owns setup and must cancel, roll back, and quiesce partial resources on every failure. After fulfillment, the caller owns the run and must call `dispose()` on every path.
|
|
|
|
`SubagentRun.result` resolves to `{ output, structured?, stopReason }`. Child-level failures resolve with a non-`completed` reason; only an infrastructure fault that the seam cannot represent may reject. `dispose()` is idempotent, cancels remaining work, and waits for the child resources to quiesce.
|
|
|
|
The service emits `subagent/start` only after `start()` has fulfilled. It attaches the result observer before that synchronous notification, so even an already-settled child still produces `subagent/start` before `subagent/end`. In-process start observers can resolve the published child through `ctx.agents.get(info.id)`; remote providers need not publish a local agent.
|
|
|
|
Run events are scoped to the delegating parent. Every listener is independently contained: a synchronous throw or rejected returned promise is logged without starving peer listeners or changing the run.
|
|
|
|
Provider additions and removals also emit `subagent/provider-added` and `subagent/provider-removed`. Consumers such as the model-facing tool use those events because Cordis may load sibling plugins concurrently; configuration order does not prove registration order.
|
|
|
|
## Collection model
|
|
|
|
The model-facing tool collects synchronously by default: it awaits the child result and disposes the run before returning. Background delegation does not change this seam; the consumer registers startup and the eventual run with the generic `ctx.tasks` runtime, then collection and cancellation use the shared task tools. See the [background subagent tasks RFC](../../../docs/rfc/implemented/feature/2026-07-08-background-subagent-tasks.md), the [capability-seam RFC](../../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md), and `src/types.ts` for the complete contracts.
|
|
|
|
## Model Experience
|
|
|
|
Indirectly, through `dsh-tool-subagent`, which renders provider-specific schemas and foreground or generic-background results while child working context remains child-only.
|
|
|
|
## Known Limitations and Deferred Work
|
|
|
|
- **Runtime steering and continuation are seam-only capabilities** — `sendMessage` and `resume` have no model-facing consumer in the current tool.
|
|
- **Lifecycle events are observe-only** — a run-affecting `subagent/end` continuation or decision surface waits for a concrete consumer.
|