Routes agent requests to configurable default, planner, and subagent models, automatically using the planner role in plan mode, with per-role reasoning effort and Web UI configuration.
Install
# from npm (prebuilt)
dsh plugin --profile web add @snowamberx/dsh-role-router
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:SnowAmberX/dsh-role-router
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time. Only install sources you trust, and pin a commit (github:owner/repo#sha).
README
English | 中文
Role Router Plugin (dsh-role-router)
Tired of manually switching models between planning and execution?
dsh-role-router does it for you: type /plan and requests are routed
automatically to the configured planner model; leaving plan mode switches
back to the default model — no manual intervention needed.
- Role routing:
default,plannerandsubagentare configured independently; unconfigured roles pass through (follow the session default).planneris triggered automatically by plan mode (/planand friends);defaultalways tracks the official session model selection. - Web UI: a "Multi-role model routing" card on the settings page with
three model pickers (each with an optional reasoning-effort picker; same
source as
/model— the host's live, provider-grouped catalog, auto-refreshed), plus a composer-adjacent pill that shows the current selection at a glance. - Two configuration layers: cordis.yml (composition) and the
role-routersettings namespace (user layer, which takes precedence); saved settings apply to the next request without a restart.
Screenshots


Routing semantics
Every model request is routed by role; the listeners are registered on the root context, so they observe top-level agents and every in-process subagent:
| Role | Requests | Model source |
|---|---|---|
default |
top-level agents outside plan mode | Configured → forced to that model; unset → pass-through, the official layered selection applies |
planner |
top-level agents while plan mode is active | Configured → forced to that model; unset → pass-through, the official layered selection applies |
subagent |
every in-process subagent request (any depth) | Configured → forced to that model; unset → pass-through, the official layered selection applies |
An unset role passes the request through untouched: the harness's official
per-session model-selection layer decides, with its usual precedence —
explicit in-session switches (composer / /model) > the session's own latest
logged request > the global default (agent-default-model settings). So
in-session model switches take effect for unset roles on the next turn (the
official layer snapshots the selection at prompt assembly, so a mid-turn
switch never splits the running turn), and the composer summary always
agrees with the actual requests.
Switching models drops an inherited adapter-owned reasoningEffort unless
the role configures an explicit one (the routed model may not support the
previous model's effort; prepareCall rejects unsupported explicit efforts);
an explicit effort is applied and validated by prepareCall. Pass-through
requests keep everything the official layer assembled, including its effort.
Plan mode is folded from the durable plan/mode session events
(foldPlanMode); ctx.planMode is consulted first when visible
(pending-aware).
Auxiliary model calls (compaction, session-title) do not dispatch through
agent/request and are unaffected, as are out-of-process subagent providers
(acp, codex, …).
Web UI (client half)
The package declares dsh.client (platform: web) and provides two surfaces:
- Settings → plugin configuration → "Multi-role model routing" card: three
model pickers (default / planner / subagent) fed by the host's live model
catalog (provider-grouped, same source as
/model, refreshed onllm/adapters-updated); after picking a model each field offers an optional reasoning-effort picker whose levels come from that model'sreasoning.effortsin the catalog (adapter-declared, not hard-coded).- All three fields (default / planner / subagent) write the
role-routersettings namespace; a saved setting applies to the next request without a restart. A configured role forces its model; an unset role follows the official model selector. The section is registered with the composition entry as its base layer, so composition-configured routes are shown and can be overridden from the card.
- All three fields (default / planner / subagent) write the
- Composer-adjacent summary: a pill showing
Default model: <configured default or session selection> · planner: <configured>. The official model seat and/modelstay untouched.
Configuration
On install, the bundle inserts the model-router row with no config — all
three roles start unset: requests pass through and the official layered
selection applies. Two ways to personalize, settings first:
Settings page (user layer, recommended)
Settings → plugin configuration → "Multi-role model routing" card: saving
writes the role-router namespace into settings.yaml
({ default?, planner?, subagent? }, each role being
{ provider, model, reasoningEffort? }), applying to the next request
without a restart. The settings page does not write cordis.patch.yml,
and its values win over the composition layer.
cordis.patch.yml (composition layer, optional)
The bundle already inserts the plugin row under the id model-router; the
user layer only needs to override its config by id. In a profile, write
the profile's cordis.patch.yml:
- id: model-router
name: '@snowamberx/dsh-role-router'
config:
default: # optional; omit the key to keep it unset (pass-through)
provider: deepseek-official
model: deepseek-v4-flash
reasoningEffort: high # optional; unset follows the target model default
planner: # optional
provider: deepseek-official
model: deepseek-v4-pro
reasoningEffort: max # optional
subagent: # optional
provider: deepseek-official
model: deepseek-v4-flash
Unknown keys and blank provider/model/reasoningEffort values fail loud at load. All three roles are optional: an unset role passes requests through to the official layered selection; a configured role forces its model.
settings (user layer)
role-router namespace: { default?, planner?, subagent? }, each role being
{ provider, model, reasoningEffort? }. Settings-document values win over the
composition layer.
Install
dsh plugin --profile web add @snowamberx/dsh-role-router
# local development:
dsh plugin --profile web add link:/path/to/this/repo
Restart dsh web (client-modules rescans package metadata at boot).
A standard DSH community plugin package
This package is a standard DSH community bundle: its manifest declares a
dsh.bundle configuration layer plus a dsh.client web half, matching the
official packaging & installation
guide
and the conventions of the packages/client/* client plugin packages.
dsh.bundlemanifest:"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }inpackage.json.cordis.patch.ymlis a patch layer inserting the plugin row by id (model-router), resolved by package name (@snowamberx/dsh-role-router);dsh plugin addrecognizes the declaration and appends the package to the profile'sdsh.profile.bundleslayer stack (a package withoutdsh.bundleinstalls as a plain dependency and warns).- Web client half:
"dsh": { "client": { "platform": "web", "inject": [...] } }declares the browser half, andexports["./client"]points atlib/client.js— a standard closure-factory artifact (window.__ModuleLoader__.load({ id, factory })) served by client-modules at/plugins/@snowamberx/dsh-role-router/client.js.injectlists the client half's dependency edges (informational: preflight display and HMR diffing; activation order is driven by cordis service injection). - Build:
tsc(node half + type declarations) plustsdown(vendor/tsdown.client.ts, the same clientBundle preset as the officialpackages/client/tsdown.client.ts: inlined CSS Modules, platform modules as externals, sourcemaps mapped back to repository source paths).
Development
pnpm install # @deepseek-ai/* runtime deps are symlinked from the harness checkout (see below)
pnpm build # tsc (host half + types) + tsdown (client bundle)
pnpm test # vitest (host routing integration + config/classify units)
@deepseek-ai/*, react, tsdown and lightningcss are provided through
node_modules symlinks into the DeepSeek Harness checkout (the same
flat-fallback mechanism official profiles use) — no npm installs. tsconfig
enables preserveSymlinks so type resolution rides the same flat chain.
Known limitations
- The catalog is advisory (adapters may accept unlisted model ids); the pickers only list catalog models.
- The composer summary shows
default+planneronly (notsubagent). - With no current session the card pickers defer loading ("open a session to
load the model list"); the catalog is fetched through the current session's
session.modelsRPC (the groups are global). - A
planner/subagentprovider without a registered adapter fails the request with the normal NO_ADAPTER turn error (loud, no silent fallback). - Forced routes are persisted into the session's request header, and the
official "latest logged request" layer treats that as the session's current
model. So with a forced
planner/subagentand an unsetdefault, one plan-mode round leaves the session's default following the last planner model (the composer summary shows it too; switch back in the composer at any time). This matches the harness's own per-session precedence.
Links
More in this category
Mars-Sea/dsh-commandcode-provider★ 61
Unofficial Command Code LLM provider: registers a `commandcode` route with a live model catalog and reasoning-effort support.
franksong2702/dsh-codex-connect★ 27
Connect ChatGPT OAuth and OpenAI Codex models to DeepSeek Harness, with opt-in search and image tools.
WSL043/dsh-codex-subscription★ 14
Built-in ChatGPT OAuth provider for Codex models, selectable subscription web search, backend quota for standard Codex and Spark, and a DSH settings UI; no API key or Codex CLI required.
suntianc/dsh-codex-auth★ 11
Reuses the Codex CLI ChatGPT login as an `openai-codex` LLM route and adds GPT Auth controls to DSH Web settings.
btspoony/dsh-llm-fallbacks★ 10
Role-based LLM retry & fallback strategies.
WNJXYK/dsh-codex-oauth★ 10
Use a ChatGPT/Codex subscription in DeepSeek Harness with GPT models, image generation, web search, subscription quota reporting, model and feature controls, and browser or device-code OAuth sign-in.