DeepSeek Harness plugin

dsh-subagent-router

DSH model-facing delegation tool that routes continuable/one-shot subagents to a model-selected LLM provider and model

Jump to install

Source facts

Repository
XMoon/dsh-subagent-router
Latest update
Aug 21, 2026
Category
Workflow & Automation
GitHub stars
2
Format
plugin
Catalog evidence
Upstream dsh.bundle evidence
Evidence path
package.json#dsh.bundle
Checked against
0.1.0-rc.8
Upstream check date
2026-08-20

This evidence comes from the upstream catalog. This site has not installed, run, or security-reviewed the plugin.

Install

Start with a prompt that asks an agent to review the GitHub repository and source. Switch to the command if you want to install it yourself.

Copy this prompt into DSH, Codex, or another agent and ask it to review the GitHub repository and source first.

Do not install or run any commands yet. Read this plugin's GitHub repository, README, and relevant source code. Then answer the questions below clearly and directly so I can decide whether it fits my needs:

1. What is this plugin, and what problem does it solve?
2. Who is it for, and what are its typical use cases?
3. How is it used after installation? Include one minimal example.
4. What known limitations or privacy, security, compatibility, or maintenance risks does it have?
5. Give a clear recommendation: recommend, conditionally recommend, or do not recommend, with reasons.

Distinguish statements documented by the repository, inferences from source code, and unknowns. If evidence is insufficient, say so explicitly. Do not guess or simply repeat the README.

GitHub: https://github.com/XMoon/dsh-subagent-router
Plugin: dsh-subagent-router
Author: XMoon

Check the source files

Read the README and other files from this plugin directory before installing.

File explorer3 files
README.mdSource · read only
README language

@xmoon76/dsh-subagent-router

English | 中文

A model-facing DSH plugin that starts a subagent on a model-selected LLM provider and model. The model picks the route (provider/model); the deployment owns the subagent backend (subagentProvider, default spawn), the default scheduling policy (backgroundMode), and the route allowlist (allowedProviders). Per-call scheduling is overridable with the optional run_in_background parameter, exactly like the official @deepseek-ai/dsh-tool-subagent tools. Later turns of a continuable child reuse the official send_message / list_agents / interrupt_agent tools from @deepseek-ai/dsh-tool-subagent-control; background one-shot jobs are collected with the official job_output / job_kill tools from @deepseek-ai/dsh-tool-jobs.

Installation (as a DSH profile bundle)

This package ships as a DSH profile bundle: its cordis.patch.yml (dsh.bundle.patch) inserts TWO router rows into the profile composition automatically — tool-subagent-router (spawn + continuable, subagent_route) and tool-subagent-router-fork (fork + one-shot, subagent_fork_route). Requires a profile whose bundles include @deepseek-ai/dsh-base (every shipped web/headless template does) — the subagents registry and its spawn/fork backends come from that base layer.

dsh plugin --profile <name> add @xmoon76/dsh-subagent-router

The command installs the package into the profile and adds it to the profile's dsh.profile.bundles layer list; on the next boot its patch inserts both rows with the defaults below. To override defaults, patch the same row ids in the profile's own cordis.patch.yml (a patch replaces the row's whole config):

- id: tool-subagent-router
  config:
    allowedProviders:
      - deepseek-official

Usage walkthrough

The following flow is a practical model-facing invocation. Confirm that the profile has registered the router tools (subagent_route / subagent_fork_route) and that the selected provider/model route is configured before dispatching.

Start a continuable child (background by default)

subagent_route requires description / prompt / provider / model. The model chooses only the LLM route; deployment configuration still owns the backend and the default scheduling policy:

{
  "description": "say hi",
  "prompt": "Say hello briefly, then state which model you are using.",
  "provider": "codex",
  "model": "gpt-5.6-luna"
}
// continuable result: started subagent <id>

A continuable result acknowledges inbox acceptance and returns a durable id; it does not contain the child reply. Wait for the DSH settlement notice or inspect the child transcript by id.

Wait for a fresh child synchronously

Set run_in_background: false when the next action depends on the child's result. The call then runs the child in the foreground and returns its final output instead of an id:

{
  "description": "review implementation",
  "prompt": "Review the diff and report concrete risks.",
  "provider": "codex",
  "model": "gpt-5.6-luna",
  "run_in_background": false
}
// foreground result: the child's final output

Continue for more turns

Use the official send_message control tool to queue the next FIFO turn after the child has been accepted:

{
  "subagent_id": "<id>",
  "message": "What kinds of engineering tasks are you best at?"
}
// message queued as the next turn for subagent <id>

The child keeps its creation-time provider/model across later turns and cold resume. There is no mid-session route switch.

Companion controls

Continuation controls are separate from this package and must be mounted from the official @deepseek-ai/dsh-tool-subagent-control plugin; background job controls come from the official @deepseek-ai/dsh-tool-jobs plugin:

ToolPurpose
send_messageQueue the next turn for a durable child (FIFO).
list_agentsList or recall started children.
interrupt_agentInterrupt a running child turn.
job_outputCollect a background one-shot job's output.
job_killStop a background one-shot job.

Official delegation tools (subagent, subagent_fork) are separate: they are instances of @deepseek-ai/dsh-tool-subagent bound to one fixed deployment route. This router never replaces them — see [Official tool coexistence](#official-tool-coexistence) below.

Start a dynamic fork (one-shot)

subagent_fork_route is mounted by the bundle by default (fork + one-shot), so a forked child that inherits the parent's completed turns is available out of the box. The instance uses a non-conflicting name (never the official subagent_fork):

# what the bundle inserts (defaults; override by row id in your profile)
- id: tool-subagent-router-fork
  config:
    subagentProvider: fork
    toolName: subagent_fork_route
    backgroundMode: one-shot
    enableRunInBackground: true
    maxDepth: 3

Model call (waits for the result by default):

{
  "description": "review prior design",
  "prompt": "Review the design discussed above and identify correctness or maintainability risks.",
  "provider": "openai",
  "model": "gpt-5.6"
}
// foreground result: the child's final output

Fork prompt semantics: the child already sees the parent's completed turns, so the prompt only needs to state the new task; the current in-flight parent turn is not part of the fork seed.

Run a fork in the background

Set run_in_background: true to register a background Task and return its job id immediately:

{
  "description": "deep review",
  "prompt": "Perform a deep review of the design.",
  "provider": "codex",
  "model": "gpt-5.6-luna",
  "run_in_background": true
}
// background result: started background subagent job <id>

Collect the result with job_output and stop the work with job_kill. A background fork is a one-shot Task, not a continuable child: it cannot be continued with send_message.

Best practices

  • Make a fresh-child (spawn) prompt self-contained: it does not see the parent conversation.
  • Make a forked-child (fork) prompt a delta only: the child inherits the parent's completed turns, so state only the new task; the current in-flight turn is not in the fork seed.
  • Verify provider/model availability before dispatch. There is no model-discovery tool, and route errors may surface only when the child's first request resolves the route.
  • Treat a continuable start result as an acknowledgement, not as the child answer; use settlement notices and the transcript for the actual result.
  • Prefer the background default for independent delegations: start siblings together in one assistant turn and keep working while they run; use run_in_background: false only when the next action depends on the result.
  • Configure allowedProviders when deployment policy restricts routes; do not rely on prompt wording for enforcement.
  • Keep credentials, endpoints, and headers out of prompts and tool arguments. Use a unique toolName for each loaded instance.
  • Remember that maxTokens is not durable across activations.

Why this package

The official @deepseek-ai/dsh-tool-subagent binds one instance to one fixed child agentOptions (deployment-fixed provider/model). This plugin moves the LLM route choice into the model's hands while keeping every capability owned by the DSH seam: it is a thin Consumer over ctx.subagents.startContinuable() / ctx.subagents.start() and ctx.jobs, and does not re-implement continuation, sessions, persistence, authority, jobs, or queues. Scheduling and lifecycle semantics otherwise mirror the official tools.

Contract

The model-facing tools subagent_route / subagent_fork_route take the same parameters:

ParameterRequiredMeaning
descriptionyesShort (3-5 word) label of the delegated task.
promptyesComplete standalone task (fresh child) or delta over completed turns (forked child).
provideryesConfigured DSH LLM provider route for the child.
modelyesModel id for the child conversation.
run_in_backgroundnoScheduling override. Continuable instances default to true (durable id); one-shot instances default to false (final output). Absent when enableRunInBackground: false.

Success returns one of three canonical result kinds, depending on the instance's backgroundMode and the call's run_in_background:

KindWhenShape
continuablecontinuable mode, background (default){ kind: 'continuable', subagentId } — durable id, resolved at inbox acceptance
foregroundany mode with run_in_background: false (or one-shot default){ kind: 'foreground', runId, output } — final child output
backgroundone-shot mode with run_in_background: true{ kind: 'background', jobId } — collect with job_output, stop with job_kill

Credentials, endpoints, headers, maxTokens, outputSchema, and backend selection are never exposed to the model.

Config

KeyDefaultMeaning
subagentProviderspawnctx.subagents provider name. Continuable mode requires prepareContinuable; one-shot mode requires a start-capable provider (fork is the supported one-shot backend).
backgroundModecontinuableDefault scheduling policy: continuable calls startContinuable() and returns a durable subagent id; one-shot calls start() and returns the run's final output. run_in_background overrides it per call. Never model-selectable.
executionModeDeprecated legacy alias for backgroundMode. Configured together with backgroundMode they must agree, otherwise the plugin fails loud at startup.
enableRunInBackgroundtrueWhether the model-facing run_in_background parameter exists and is honored. false removes it from the schema and forces every call to the foreground; a forged run_in_background: true is rejected in execute.
toolNamesubagent_routeModel-facing tool name; distinct per loaded instance.
maxDepth3Absolute delegation-depth cap, or 'provider-managed' for no cap.
personaPer-child persona shadowing deployment:persona.
toolFilterPer-child global-tool restriction; requires the toolFilter capability.
allowedProvidersDeployment-side LLM provider allowlist, enforced in execute() before any child work; explicit [] denies all.

Routing policy

  • The model selects only the LLM route: provider must name a registered DSH LLM adapter route and model a model id on it.
  • allowedProviders is executor-level enforcement, not a prompt hint. provider/model validity is ultimately resolved by the DSH LLM/Agent resolution at the child's first request (no listModels() hard whitelist, preserving dynamic model routes).
  • The subagent backend and the default scheduling policy are deployment configuration; the model never selects them.

Continuation behavior

  • A continuable child is a durable conversation: send_message (official control tool) delivers later FIFO turns, list_agents lists it, interrupt_agent interrupts it — all through ctx.subagents authority paths.
  • Cold resume keeps the same agentProvider/agentModel: the durable descriptor persists them, so a resumed Activation still uses the creation-time route.
  • provider/model are fixed at creation; there is no mid-session model switching.
  • A background one-shot job is a Task, not a continuable child: job_output / job_kill (official @deepseek-ai/dsh-tool-jobs) are its controls, and send_message cannot continue it.

Official tool coexistence

This plugin does not replace the official subagent / subagent_fork tools. The bundle mounts BOTH router instances by default, and when the official tools are present the final tool set is:

subagent            -> official fresh child,  fixed route,   continuable
subagent_route      -> router  fresh child,  dynamic route, continuable
subagent_fork       -> official inherited context, fixed route,   one-shot
subagent_fork_route -> router  inherited context, dynamic route, one-shot
send_message        -> official (@deepseek-ai/dsh-tool-subagent-control)
interrupt_agent     -> official (@deepseek-ai/dsh-tool-subagent-control)
list_agents         -> official (@deepseek-ai/dsh-tool-subagent-control)
job_output          -> official (@deepseek-ai/dsh-tool-jobs)
job_kill            -> official (@deepseek-ai/dsh-tool-jobs)
job_list            -> official (@deepseek-ai/dsh-tool-jobs)

The two router tools differ from their official counterparts ONLY in the child route: the official instances use a deployment-fixed provider/model, while the router lets the model select provider/model on every call. Everything else — scheduling, run_in_background semantics, result kinds, system-prompt guidance — is identical:

ToolChildRouteLifecycle
subagentfreshfixedcontinuable (send_message)
subagent_routefreshdynamiccontinuable (send_message)
subagent_forkinherits completed turnsfixedone-shot (job_output / job_kill)
subagent_fork_routeinherits completed turnsdynamicone-shot (job_output / job_kill)

The router never shadows, replaces, or mutates official tool definitions: it registers only its own tool names and leaves every official schema and behavior intact (locked by the coexistence test suite).

Support matrix

BackendbackgroundModerun_in_background omitted / falserun_in_background: trueStatus
spawncontinuableforeground (waits for output)durable continuable child✅ Recommended
forkone-shotforeground (waits for output)background Task (job_output / job_kill)✅ Recommended
spawnone-shotforeground (waits for output)background Task⚪ Compatible
forkcontinuableforeground (waits for output)durable continuable child⚠️ Not recommended

The router is a generic provider Consumer, so fork + continuable is not hard rejected when a provider exposes prepareContinuable() — but the product documentation recommends fork + one-shot.

Tool name collision rules

  • Each loaded router instance needs a unique toolName. A name already

registered in the tool registry fails the mount loud, before anything is registered.

  • DSH official subagent/control names (subagent, subagent_fork,

send_message, interrupt_agent, list_agents) get a dedicated diagnostic when configured as a router toolName.

  • Never configure the router's toolName as subagent or subagent_fork.

The shipped bundle uses subagent_route (spawn + continuable) and subagent_fork_route (fork + one-shot); further instances must pick their own unique names.

Model Experience

Tool schema

#### What the model sees

The registered router schemas (subagent_route / subagent_fork_route): description, prompt, provider, model (all required) plus the optional run_in_background override. The description/prompt wording follows the backend provider's inheritsParentContext: a fresh child is told to provide a complete standalone prompt; a forked child is told it already sees completed turns. Continuable instances document the true default of run_in_background, the settlement notice, and the explicit foreground override; one-shot instances document the false default and the job id collected with job_output / job_kill. No api_key, base_url, max_tokens, or backend/mode parameters exist.

#### Token effect

Fixed schema cost per request where the tool is visible; no system-prompt section is contributed by this package except the continuable instances' tool:<toolName> guidance (see below).

#### KV Cache effect

Prefix-stable while the registered tool schema is unchanged; provider registration lifecycle may invalidate reuse from the first changed tool definition.

System-prompt guidance

A continuable instance with enableRunInBackground: true contributes a tool:<toolName> system-prompt section (order 116.5) telling the model to delegate in the background by default, start independent delegations together in one assistant message, keep working while they run, and choose run_in_background: false only when the next action depends on the result. The section renders empty while the tool is absent (provider not yet registered or already removed), so HMR cannot leave stale guidance. One-shot instances contribute no section.

Tool result

#### What the model sees

started subagent <id> (continuable), the child's final text (foreground), or started background subagent job <id> (background). Continuable results carry no child reply; the child's transcript by its id is the source of what it did, and its settlement notice arrives independently.

#### Token effect

One short result appended per accepted creation (continuable), per job registration (background), or the child's output (foreground).

#### KV Cache effect

Append-only after the reusable request prefix.

Known Limitations and Deferred Work

  • No mid-session model switchingprovider/model are fixed at creation; the durable descriptor persists them, so a resumed Activation still uses the creation-time route.
  • No model discovery tool — the model must already know the configured provider/model ids; a read-only discovery tool is deferred.
  • A continuable child started in the background cannot be synchronously collected by the initiating tool call — its settlement arrives through the continuation notice mechanism and its transcript remains available by subagent id; use run_in_background: false when the next action depends on the result.
  • maxTokens is not durable — per-activation budgets are not persisted in the DSH continuable descriptor, so the tool does not expose them.
  • Only configured LLM adapters/routes can be used — the child route must resolve at request time; provider/model validity may fail only when the child's route is resolved (no listModels() hard whitelist by design).
  • Continuation controls require the official control toolsend_message / list_agents / interrupt_agent come from @deepseek-ai/dsh-tool-subagent-control, mounted separately.
  • Background one-shot jobs require the official jobs stackctx.jobs (@deepseek-ai/dsh-jobs + a registry like @deepseek-ai/dsh-jobs-local) and the job_output / job_kill tools (@deepseek-ai/dsh-tool-jobs); a background call without them fails loud.
  • One-shot output is not streamed — the run's final output is returned once the child settles; intermediate steps stay in the child's transcript.
  • Output schema uses the DSH tools value-schema dialect — the canonical foreground output is { type: 'array', items: { type: 'json' } }, where 'json' is @deepseek-ai/dsh-tools's Schemastery-based value type (the same dialect the official tool-subagent uses), not a bare JSON-Schema keyword; only DSH's tool registry consumes it.

Development

Prerequisites

Node.js ≥ 22 and npm. All DSH peer dependencies resolve from the npm registry (@deepseek-ai/dsh-* 0.1.0-rc.x), so no deepseek-harness checkout is required.

Gates

npm run typecheck   # tsc over src + tests
npm run lint        # oxlint
npm run test        # vitest (package integration + Loader composition)
npm run test:coverage  # per-file 100% on src/
npm run build       # tsc emit to lib/
npm pack            # tarball smoke (structure, content, standalone install)