deepseek-harness
deepseek-ai
DeepSeek Harness: Everything is a Plugin.
PROJECT TOPICS
PROJECT README
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 lifecycle mode (executionMode), and the route allowlist (allowedProviders). Later turns reuse the official send_message / list_agents / interrupt_agent tools from @deepseek-ai/dsh-tool-subagent-control.
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
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.
subagent_route requires all four fields. The model chooses only the LLM route; deployment configuration still owns the backend and lifecycle:
{
"description": "say hi",
"prompt": "Say hello briefly, then state which model you are using.",
"provider": "codex",
"model": "gpt-5.6-luna"
}
// continuable result: started continuable 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.
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.
Continuation controls are separate from this package and must be mounted from
the official @deepseek-ai/dsh-tool-subagent-control plugin:
| Tool | Purpose |
|---|---|
send_message |
Queue the next turn for a durable child (FIFO). |
list_agents |
List or recall started children. |
interrupt_agent |
Interrupt a running child turn. |
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 below.
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
executionMode: one-shot
toolName: subagent_fork_route
maxDepth: 3
Model call:
{
"description": "review prior design",
"prompt": "Review the design discussed above and identify correctness or maintainability risks.",
"provider": "openai",
"model": "gpt-5.6"
}
// one-shot 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.
spawn) prompt self-contained: it does not see the parent conversation.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.allowedProviders when deployment policy restricts routes; do not rely on prompt wording for enforcement.toolName for each loaded instance.maxTokens is not durable across activations.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 does not re-implement continuation, sessions, persistence, authority, or queues.
The model-facing tools subagent_route / subagent_fork_route take the same four parameters:
| Parameter | Required | Meaning |
|---|---|---|
description |
yes | Short (3-5 word) label of the delegated task. |
prompt |
yes | Complete standalone task (fresh child) or delta over completed turns (forked child). |
provider |
yes | Configured DSH LLM provider route for the child. |
model |
yes | Model id for the child conversation. |
Success returns { kind: 'continuable', subagentId } (durable id, resolved at inbox acceptance) or { kind: 'one-shot', runId, output } (final child output), depending on the instance's executionMode. Credentials, endpoints, headers, maxTokens, outputSchema, and backend selection are never exposed to the model.
| Key | Default | Meaning |
|---|---|---|
subagentProvider |
spawn |
ctx.subagents provider name. Continuable mode requires prepareContinuable; one-shot mode requires a start-capable provider (fork is the supported one-shot backend). |
executionMode |
continuable |
continuable calls startContinuable() and returns a durable subagent id; one-shot calls start() and returns the run's final output. Never model-selectable. |
toolName |
subagent_route |
Model-facing tool name; distinct per loaded instance. |
maxDepth |
3 |
Absolute delegation-depth cap, or 'provider-managed' for no cap. |
persona |
— | Per-child persona shadowing deployment:persona. |
toolFilter |
— | Per-child global-tool restriction; requires the toolFilter capability. |
allowedProviders |
— | Deployment-side LLM provider allowlist, enforced in execute() before any child work; explicit [] denies all. |
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).send_message (official control tool) delivers later FIFO turns, list_agents lists it, interrupt_agent interrupts it — all through ctx.subagents authority paths.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.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)
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 is identical:
| Tool | Child | Route | Lifecycle |
|---|---|---|---|
subagent |
fresh | fixed | continuable (send_message) |
subagent_route |
fresh | dynamic | continuable (send_message) |
subagent_fork |
inherits completed turns | fixed | one-shot |
subagent_fork_route |
inherits completed turns | dynamic | one-shot |
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).
| Backend | Mode | Status | Meaning |
|---|---|---|---|
spawn |
continuable |
✅ Recommended | Fresh child, send_message-continuable, cold-resumable |
fork |
one-shot |
✅ Recommended | Inherits parent completed turns, one execution then ends |
spawn |
one-shot |
⚪ Compatible | One-shot execution of a fresh child |
fork |
continuable |
⚠️ Not recommended | Upstream has not shipped fork as a formal continuable mode |
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.
toolName. A name already
registered in the tool registry fails the mount loud, before anything is
registered.subagent, subagent_fork,
send_message, interrupt_agent, list_agents) get a dedicated diagnostic
when configured as a router toolName.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.The registered router schemas (subagent_route / subagent_fork_route): description, prompt, provider, model, all required. 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. No api_key, base_url, max_tokens, run_in_background, or backend/mode parameters exist.
Fixed schema cost per request where the tool is visible; no system-prompt section is contributed by this package.
Prefix-stable while the registered tool schema is unchanged; provider registration lifecycle may invalidate reuse from the first changed tool definition.
started continuable subagent <id> (continuable mode) or the child's final text (one-shot mode). 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.
One short result appended per accepted creation (continuable) or the child's output (one-shot).
Append-only after the reusable request prefix.
provider/model are fixed at creation; the durable descriptor persists them, so a resumed Activation still uses the creation-time route.subagent_route resolves at inbox acceptance; the child's settlement arrives via the DSH notice mechanism, and its transcript by id is the detailed source.maxTokens is not durable — per-activation budgets are not persisted in the DSH continuable descriptor, so the tool does not expose them.provider/model validity may fail only when the child's route is resolved (no listModels() hard whitelist by design).send_message / list_agents / interrupt_agent come from @deepseek-ai/dsh-tool-subagent-control, mounted separately.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.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.
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/ CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。