dsh-model-router
English | 中文
A DeepSeek Harness plugin that sets the reasoning effort of the DeepSeek flash model per step. The model never changes — this plugin only decides how hard a step should think.
Host-only: no browser UI, no client bundle. It runs silently in the background.
Routing table
| Step class | Decided by | Reasoning effort | Internal point |
|---|---|---|---|
trivial |
a clearly cheap intent (translate, rename, reformat) in a short step | off |
thinking disabled |
standard |
a plain short request with no engineering cue | low |
~50 |
engineering |
engineering cues, code/diff/XML structure, or an agent tool loop | high |
~75 |
hard |
a dense engineering brief, or failures earned inside the task | high (max when allowMax) |
~75 / 100 |
high is the default ceiling. V4.1-Flash drives effort from an internal 1–100 scalar and exposes
three preset names; high corresponds to roughly 75, which is where the published effort curve is
still steep. Pushing past it costs about 1.6–1.8x the output tokens for a marginal gain.
The four rules
- No ratchet. Turn depth contributes no score by default (
scoring.turnPerPoint: 0) and tool calls are counted per task, so a long agent run does not drift toward the most expensive effort. A long run is not a harder task. - Escalation needs evidence. Inside the current task,
escalateOnErrorsfailing tool results, or the same tool call retried with identical argumentsescalateOnRepeatstimes, step the class up — at mostmaxEscalationstimes. Nothing else moves it. maxis opt-in. Automatic routing never emitsmaxunlessallowMaxis set. The ceiling is enforced at the effort level, so even a hand-written route table asking formaxis clamped.- A manually selected
maxis demoted too (demoteManualMax), but only for models this plugin manages. Other models are left alone. EnablingallowMaxturns both clamps off.
Task boundaries come from agent/inbox/claimed, which is what actually opens a new piece of work.
When a task ends on an unresolved failure, the next one inherits a single hesitant step up — and only
if it is engineering or hard work. A one-line "translate this" never inherits a crash.
Configuration
The plugin reads its config from its row in the profile's cordis.patch.yml:
- id: model-router
config:
mode: auto # auto | off
model: deepseek-flash
allowMax: false # true enables max for auto routing and manual selection alike
maxFallback: high # where max collapses when allowMax is false
escalateOnErrors: 2 # failed tool results needed to step up
escalateOnRepeats: 3 # identical retries needed to step up
scoring:
turnPerPoint: 0 # raise this to let long sessions weigh more (not recommended)
routes:
trivial: { effort: off }
standard: { effort: low }
engineering: { effort: high }
hard: { effort: max }
DSH_MODEL_ROUTER=off|auto overrides mode at boot.
| Key | Default | Meaning |
|---|---|---|
mode |
auto |
off passes every request through, except the manual-max clamp |
provider |
deepseek-official |
only this provider is ever touched |
model |
deepseek-flash |
the one model this plugin drives |
familyPattern |
^deepseek-(flash|v4) |
conversation models it may take over; a pro session is pulled back to flash |
allowMax |
false |
whether max is reachable at all |
maxFallback |
high |
what max collapses to |
demoteManualMax |
true |
also demote a manually selected max |
leaveImageSteps |
true |
steps carrying images keep the caller's model |
imagePolicy |
keep |
set to flash to route image steps too (the flash model has native vision) |
escalateOnErrors |
2 |
failure-evidence threshold |
escalateOnRepeats |
3 |
repeated-call threshold |
maxEscalations |
2 |
most classes a single task may climb |
carryUnresolved |
true |
carry one step up from an unresolved failure |
Install
dsh plugin is a pnpm forwarder, so any pnpm specifier works. From the npm registry:
dsh plugin --profile web add @neptune810/dsh-model-router
or straight from the repository:
dsh plugin --profile web add github:Neptune810/dsh-model-router
Restart dsh web afterwards. Because the router registers listeners at boot, a reload is not
enough.
Requirements
- Node 20 or newer.
- Verified against
@deepseek-ai/dsh0.1.5-rc.2. The plugin usesagent/request,agent/inbox/claimed, andsession.deriveMessages(). Noengines.dshrange is declared, so the plugin market keeps this entry visible rather than guessing it incompatible.
Limitations
- Host-only. There is no client bundle, so nothing appears in the browser UI.
- No config schema. Settings are read from the profile patch layer shown above and do not render
as a form in the settings UI. This is deliberate: a schema would require importing
@deepseek-ai/*packages, which a plugin installed beside the profile cannot resolve. - The router only touches the
deepseek-officialprovider and models matchingfamilyPattern.
Tests
node --test
36 tests. test/policy.test.js (26) covers classification, the absence of a ratchet, the
unreachable max, evidence escalation, effort clamping, and tool-result error parsing;
test/plugin.test.js (10) drives the host wiring with ctx/agent doubles — registering listeners,
claiming messages, routing each step, pulling a pro conversation back to flash, and demoting a
manually selected max.
License
MIT
No comments yet. Be the first to write one.