DSH plugin subagent provider: DeepSeek Harness ctx.subagents
Adding a subagent provider to a DSH plugin means registering a transport implementation in the named ctx.subagents registry; what sets it apart from other capability seams is that multiple provider implementations can coexist in one context, rather than only a single executor being allowed (source). This article covers the registry and its two capability kinds, the SubagentProvider interface to implement and how it is exposed to the model, and the differences and selection among six official backends; user-side usage and backend choice are in subagent delegation config, while this article covers the contract a developer must implement.
The DSH plugin ctx.subagents registry and its two capability kinds
The subagent seam lets one agent delegate work to a child agent; it is an optional capability, not part of the agent loop, and because "multiple provider implementations can coexist in one context" it differs from single-executor capabilities like bash — it follows the LLM adapter registry pattern (source). The official roles split into three layers:
- Service Definition —
dsh-subagentprovidesctx.subagentsand its vocabulary. - Service Provider — six sibling packages:
dsh-subagent-spawn-in-process,dsh-subagent-fork-in-process,dsh-subagent-acp,dsh-subagent-codex,dsh-subagent-claude-code,dsh-subagent-dsh-sdk. - Consumer — the model-facing
dsh-tool-subagent(delegates by provider) and the optionaldsh-tool-subagent-control(globalsend_message,interrupt_agent,list_agents).
Capabilities come in two kinds with different discovery:
- Start-time capabilities are published by the static
SubagentCapabilitiesdescriptor (agentOptions,outputSchema,depthLimit,toolFilter,persona) and checked by the service before any one-shot run exists; a request needing a capability the chosen provider lacks is rejected with a typed error (SubagentError('UNSUPPORTED_CAPABILITY')) and never accepted-then-ignored (the fail-loud, no-silent-degradation rule). These flags describe the one-shotstart()path only. - Continuable capability is gated by a single optional method:
SubagentProvider.prepareContinuable. Method presence IS the capability, with TypeScript type narrowing as the discovery mechanism; a provider without it is refused for continuable starts.
The DSH plugin SubagentProvider interface and model exposure
Each provider is one named child-agent transport and multiple providers may coexist; the service may call one provider concurrently for distinct children, and providers isolate operation-local mutable state (source). The interface to implement:
name— the unique registry name (e.g.spawn,fork,acp).capabilities— the start-time capability descriptor above.inheritsParentContext— describes conversation seeding only (fork: true;spawnandacp: false), so consumers can derive truthful model-facing wording; it implies no inherited tools, services, or authority.agentRouteDefaults?— an optional, immutable provider-ownedprovider/modelroute that consumers merge model and tool overrides over before preflight.start(request)— establishes a one-shot child and returns its handle after publication; the service has already validated every requested capability and resolvedrequest.descriptor.prepareContinuable?(request)— the continuable gate, returning only detached creation inputs (whether the child is seeded with parent history) and carrying no Agent, handle, prompt delivery, result, disposal, or resume operation.
Register and expose: after implementing, call ctx.subagents.registerProvider(provider), which returns the exact Cordis effect disposer; registration is effect-scoped and HMR safe. Removing a provider blocks new starts but does not revoke runs already returned to their holders. A provider handles transport only; the model-facing side is the consumer — dsh-tool-subagent delegates by provider, exposing one configured provider.
A provider's start() fulfills with a published run: the service mints a unique runId, snapshots local, observes the result, emits subagent/start, and returns that same run. A start() rejection means unpublished resources were cleaned and no lifecycle event pair is emitted, while a post-publication result rejection closes a pair already emitted. subagent/start and subagent/end are observation-only and each isolates listener failures.
Differences and selection across DSH plugin subagent providers
Behind the same SubagentProvider interface, everything from "start a new child agent" to "delegate a turn to another product" has an implementation (source). The differences concentrate on seeding and capability matching:
- In-process spawn / fork — create an ordinary one-shot agent through
parent.ctx, pass the cancellation signal into the core creation flow, and dispose viaAgentHandle. fork passes a balanced completed-turn prefix of the parent's log (up to and including its lastturn/end, so the seed is contiguous from seq 0 and replay-acceptable); spawn does not seed. - External product backends (acp / codex / claude-code) — reject
agentOptionsbefore starting a transport, because they cannot merge the parent Agent's options into an external product. - DSH SDK backend — merges the four Agent route fields over its instance defaults, validated during child runtime initialization.
- One-shot vs continuable — one-shot goes through
start()returning aSubagentRun(one disposable foreground delegation with one result;resultresolves with a non-completedstop reason on a child-level failure, and only unrepresentable infrastructure faults reject); continuable conversations have no run at all — the continuation manager holds theirAgentHandledirectly, and the provider participates only throughprepareContinuablefor the initial creation spec.
Selection guidance: if you only need one-shot delegation (one result, then dispose), use start(); if you need multi-turn continuable conversation, have the provider implement prepareContinuable and hand later lifecycle to the continuation manager.
Three self-checks when done: is name unique and does capabilities truthfully describe the options the start() path supports; when continuable conversation is needed, did you implement prepareContinuable instead of pretending inside start(); and does inheritsParentContext describe conversation seeding only, without implying inherited authority. To see which subagent providers already ship as DSH plugins, search the DSH Plugin Hub; for extension-point ownership see the feature-to-mechanism map.
FAQ
A DSH plugin registers a provider through the named ctx.subagents registry: implement the SubagentProvider interface (name, capabilities, inheritsParentContext, start, and optional prepareContinuable), then call ctx.subagents.registerProvider(provider), which returns the exact Cordis effect disposer. Registration is effect-scoped and HMR safe; removing a provider blocks new starts but does not revoke runs already returned to their holders.
A DSH plugin's SubagentProvider has two capability kinds: one-shot capabilities are published by the static SubagentCapabilities descriptor (agentOptions, outputSchema, depthLimit, toolFilter, persona) and checked by the service before start, so a request needing a capability the provider lacks is rejected with SubagentError('UNSUPPORTED_CAPABILITY') rather than accepted-then-ignored; continuable capability is gated by the optional prepareContinuable method, where method presence IS the capability and TypeScript narrowing is the discovery mechanism.
A DSH plugin has six official subagent provider sibling packages: dsh-subagent-spawn-in-process, dsh-subagent-fork-in-process, dsh-subagent-acp, dsh-subagent-codex, dsh-subagent-claude-code, and dsh-subagent-dsh-sdk. In-process spawn/fork create an ordinary child through parent.ctx, with fork seeding the parent's balanced completed-turn prefix; acp, codex, and claude-code reject agentOptions before starting a transport, while dsh-sdk merges route fields over its instance defaults.
In a DSH plugin, the consumer dsh-tool-subagent exposes a provider to the model — it delegates by provider, exposing one configured provider; the optional dsh-tool-subagent-control adds global send_message, interrupt_agent, and list_agents control tools. The provider itself only handles transport and does not face the model directly, so model-facing behavior is composed by the tool layer.
In a DSH plugin, a one-shot run is established through SubagentProvider.start and returns a SubagentRun: one disposable foreground delegation with one result, which consumers await and must always dispose to quiescence; continuable conversations have no run at all, because the continuation manager holds their AgentHandle directly and orders every turn through the child's own inbox, with the provider participating only through prepareContinuable for the initial creation spec.
Related Terms
- ctx.subagents
- ctx.subagents, the SubagentRuntime, is the subagent seam's named provider registry supporting one-shot runs, durable discovery, and continuable-child operations through registerProvider / getProvider / list / start(name, request), managing multiple coexisting provider implementations unlike the single-executor bash service.— https://deepseek-harness.github.io/deepseek-harness/en/reference/subsystems/subagent
- SubagentProvider
- SubagentProvider is one named child-agent transport: a unique name, a start-time SubagentCapabilities descriptor, an inheritsParentContext flag describing conversation seeding only, optional agentRouteDefaults, a one-shot start(request), and an optional prepareContinuable(request); multiple providers may coexist in the same context.— https://deepseek-harness.github.io/deepseek-harness/en/reference/subsystems/subagent
- SubagentCapabilities
- SubagentCapabilities is the static descriptor a provider publishes for its start-time features (agentOptions, outputSchema, depthLimit, toolFilter, persona), checked by the service before any one-shot run exists; a request needing a capability it lacks is rejected with UNSUPPORTED_CAPABILITY under the fail-loud, no-silent-degradation rule.— https://deepseek-harness.github.io/deepseek-harness/en/reference/subsystems/subagent
- prepareContinuable
- prepareContinuable is the optional method on SubagentProvider and the sole gate for continuable-child capability: method presence IS the capability (with type narrowing as discovery), and it returns only the provider's detached creation inputs (whether the child session is seeded with parent history), holding no Agent, handle, prompt delivery, result, disposal, or resume operation.— https://deepseek-harness.github.io/deepseek-harness/en/reference/subsystems/subagent
Sources
- DeepSeek Harness official docs - Subagent· deepseek-ai
- DeepSeek Harness official docs - Cookbook: extension patterns· deepseek-ai