Перейти к основному содержимому
B

dsh-routed-subagent

bpc-oss/dsh-routed-subagent

Run a one-shot subagent fully mounted on any agent preset from any session, with per-call model/provider override, model pre-check, and external CLI engines (codex / claude / codebuddy) with background jobs, live progress, kill, and continuable sessions.

Установка

dsh plugin --profile web add github:bpc-oss/dsh-routed-subagent

README

dsh-routed-subagent

License Version CI

A global DeepSeek Harness plugin that lets any session dispatch a one-shot subagent fully mounted on ANY agent preset, with per-call model/provider override and a model-availability pre-check.

The stock subagent / subagent_fork tools force children to inherit the PARENT's preset. This plugin replaces that with a custom subagent provider whose async child setup calls agentPresets.mount(childCtx, <preset>) — so the child adopts the TARGET preset's complete composition: persona, prompt sections, skill catalog, and tools.

Features

  • Background by default, parallel dispatch — the call returns a job id immediately (like the stock subagent tool); the conversation stays free to do other work or dispatch more children in parallel, and aborting the conversation does NOT cancel the child (stop it with job_kill). Set run_in_background: false to wait inline.
  • Full preset mount — the child runs under the target preset's standing composition (not a persona copy): identity, mission section, skills, tools.
  • Per-call model overridemodel / provider arguments route the child's LLM call to a different model than this session's (via the official resolveChildAgentOptions channel).
  • Model pre-check — an invalid model fails fast with the provider's candidate list instead of an opaque child failure.
  • Official subagent ecosystem — one-shot lifecycle events, UI rows, trajectory; returns the child's final output.
  • Idempotent provider registration — multiple presets can mount the row; the host-plane provider registry is never duplicated.

External engines (engine=...)

subagent_routed can also dispatch the subagent to an external CLI agent instead of an in-harness preset mount. The default engine is dsh (this plugin's kernel); alternatives:

enginedriverbackground joblive progresskillexplicit modelcontinuable
dsh (default)preset-mount provider
codexcodex CLI app-server --stdio✅ (process event stream)turn/interruptthread/start model✅ same-thread resume
claudeClaude Code SDKabortController/close⚠️ official Anthropic API only
codebuddyCodeBuddy Code CLI --print✅ (NDJSON stream)✅ process kill--model--session-id / --resume
// external codex subagent (background, explicit model, live progress)
await subagent_routed({
  engine: 'codex',
  provider: undefined,       // external engines ignore the DSH provider
  model: 'gpt-5.6-sol',      // explicit codex thread model
  prompt: '...',
  run_in_background: true,
})
  • codex engine: keeps one long-lived codex app-server --stdio process (lazy start, init-once); each run uses thread/start + turn/start, live progress from item/agentMessage/delta events, kill via turn/interrupt, continuable reuses the disk-persisted thread (same threadId). Unattended default is approval_policy: never. The codex CLI must be logged in (codex login).
  • codebuddy engine: spawns codebuddy --print --output-format stream-json --include-partial-messages --dangerously-skip-permissions; live progress from text_delta events; continuable creates a session with --session-id <uuid> then resumes with --resume <uuid> (sessions persist on disk). Default model hy3 (overridable via config.codebuddyModel / $CODEBUDDY_MODEL). CodeBuddy Code CLI must be installed (codebuddy --version).
  • binary discovery: CODEX_BIN env wins (may point at native codex.exe or bin/codex.js); otherwise auto-probes the npm global @openai/codex/bin/codex.js.
  • claude engine: drives @anthropic-ai/claude-agent-sdk; model, kill, progress and background all work. ⚠️ continuable depends on the official Anthropic API — when the claude CLI is configured with a custom backend (e.g. AnthropicBaseURL pointing at a third-party/local endpoint), sessionId + persistSession may hang or be unusable; point AnthropicBaseURL at the official API for reliable resumes.

Distribution

GitHub-only. This plugin is not published to npm. Install it by mounting the package directory (see below). peerDependencies are declared with real semver ranges as metadata; they are not used for npm resolution.

Compatibility: targets DeepSeek Harness rc.7+ (behavior verified against rc.7 sources and rc.8 runtime) (the async child setup that this plugin relies on is a recent harness behavior).

Install

The plugin is a plain ESM package with a cordis.patch.yml bundle declaration.

The plugin statically imports @deepseek-ai/* packages, which resolve via Node ESM from the package location. Create a node_modules junction/symlink in the package directory pointing at the harness install:

:: Windows
mklink /J "<plugin-dir>\node_modules" "<harness>\resources\host\node_modules"
# POSIX (Linux/macOS)
ln -s "<harness>/resources/host/node_modules" "<plugin-dir>/node_modules"

2. Add the bundle to a profile

Add the package to your profile's dsh.profile.bundles list (e.g. <dshHome>/profiles/web/package.json):

{
  "dependencies": { "dsh-routed-subagent": "link:<plugin-dir>" },
  "dsh": { "profile": { "bundles": ["...", "dsh-routed-subagent"] } }
}

cordis.patch.yml in this repo is the bundle layer that registers the plugin; it is applied automatically when the package is listed in bundles.

Tip: if your deployment provides a hot-assembly helper (e.g. a super-injector-style dev_install_package(dir=...)), you can use it instead of the manual steps above; restarts re-assemble from the bundles list either way.

Usage

subagent_routed(prompt="Use the dev engineer standard to review this repository", preset="dev", description="dev review")          # background one-shot
subagent_routed(prompt="Continue the review", preset="dev-reviewer", description="follow-up", fork=true)  # inherits THIS conversation
subagent_routed(preset="dev", prompt="Audit this repo", description="audit", continuable=true)            # send_message(<subagentId>, ...) later

Behavior:

Modes (one tool, four shapes):

modehowreturns
one-shot background (default)run_in_background: true (default)job id immediately; collect with job_output (live progress) / stop with job_kill; aborting the conversation leaves the child running
one-shot foregroundrun_in_background: falseblocks until the child returns its final output
forkfork: truejob id / run result — the child is seeded with this conversation's COMPLETED turns (inherits the context) then mounts the requested preset on top
continuablecontinuable: truedurable subagent id — continue it later with send_message(subagentId, ...); the child mounts the requested preset and keeps it across resumes

Parameters:

inputbehavior
preset invalid / unresolvableerror, with the roster's available preset ids
model / providerper-call model override (fail-fast pre-check lists the provider's candidates; original error preserved as cause)
max_tokensoutput/token cap for the child's LLM calls (positive integer)
tool_filterDENY-only tool mask on top of the preset's tool surface ({ deny: string[] }, e.g. deny shell tools for a read-only audit)
max_depth not a positive integertool-layer validation error
fork + continuable togethersupported (continuable fork seeds the parent's completed turns)
valid callchild fully mounted on the target preset

How it works

  1. A custom subagent provider (routed-mount) re-implements the official one-shot in-process driver (dsh-subagent-in-process-driver's startInProcessRun) with one load-bearing change: the child setup is async and awaits agentPresets.mount(childCtx, targetPreset) instead of composing from the parent.
  2. agents.create awaits the setup (verified in dsh-agent-loop), so the async mount runs inside the unpublished creation window; a failure rolls the whole child back.
  3. The child's session header records agentPreset: <target> (overriding the parent value), so cold reads rebuild the child under the composition it actually ran.
  4. The tool settles the run in two sequential fault-tolerant phases — result first, then dispose — matching the official settleForegroundRun ordering (racing dispose against result would skip the child's turn and return "aborted").

Known limitations

  • preset generation drift (known limitation)mount re-resolves the preset by id on every creation/resume, so editing a preset file between continuable turns hands later turns a NEWER generation of that preset (the official composeFrom path joins the parent's exact standing instance instead). Documented behavior; restore the preset to its original state to keep turns consistent.
  • Failure semantics — like the official foreground subagent tool, a child that ends with error / refusal / max-tokens makes the tool call THROW (with any partial output attached); only completed and caller-initiated aborted return as values. Low-level LLM error details live in the child session log.
  • Pre-check is conditional — the model pre-check runs only when the harness exposes an llm service AND a provider route exists (explicit provider or the parent's). Without either, it is skipped and the call proceeds.
  • Provider availability is environment-specific — the pre-check validates against the runtime model catalog, but a reachable provider with a valid key is still required for the call to succeed.

Platform patch (continuable + preset mount)

continuable mode mounts the requested preset on the child and keeps it across resumes — that required a small, additive patch to the open-source @deepseek-ai/dsh-subagent:

  • install-level junction (single assembly point): resources\host\node_modules\@deepseek-ai\dsh-subagent → the patched fork (the original package is backed up beside it). The plugin's startup assertion logs a loud warning if the loaded instance is not the patched fork — continuable+preset will silently degrade to the parent composition, so check the log on upgrade. Do NOT add a profile-local link: dependency to @deepseek-ai/dsh-subagent (that would split module identity and defeat the patch).
  • patch surface (additive only — official paths with no preset are byte-identical):
    • applyChildComposition: composition.preset mounts the TARGET preset instead of joining the parent's (composeFrom skipped — a second bind would throw; delegation context / persona / toolFilter kept); appends agent-preset/selected(target) so fork seeds replaying the parent's selection events cannot shadow the header on cold rebuild
    • materializeTracked setup is async (awaited by the agent factory) and still returns the { commit } contract
    • continuable descriptors gain an optional preset field (version 2 → 3 for continuable only; one-shot stays 2 — a rollback rejects v3 descriptors cleanly as NOT_RESUMABLE, and legacy v2 continuable descriptors still parse)
    • coldResume rebuilds the child under the SAME preset from descriptor.preset; a missing/broken preset surfaces a named-preset error instead of a generic "unavailable"
  • rollback: delete the install junction (resources\host\node_modules\@deepseek-ai\dsh-subagent), copy <fork-dir>\@deepseek-ai\dsh-subagent.orig back into place, restart — official behavior returns (pre-existing preset-continuable children become NOT_RESUMABLE, as designed). Note: keep the fork directory present if any other profile tree junctions to it (dsh-continuous-worker\node_modules\@deepseek-ai\dsh-subagent) still exist, or re-point them.

Disabling the stock subagent tools

Once the full stock surface is ported, subagent_routed becomes the single delegation entry point: a global tools.guard denies subagent / subagent_fork at execution with a redirect message (config.disableStockSubagent ?? true; set false to keep them). Scope: every preset that mounts this plugin row.

Development

node --check lib/index.js   # syntax

The plugin is a plain ESM package (zero build step): lib/index.js plus per-engine providers under lib/engines/. CI runs node --check on every push.

License

MIT

Похожие плагины