dsh-agentic-router
> 🌐 Language / 语言切换: English · 简体中文
DeepSeek Harness (DSH) plugin: a learning agentic router with a data flywheel.
At the start of every turn (agent/inbox/claimed) it routes the request to the most appropriate model tier by task type and complexity, asks four experts in parallel and lets an EXP3 meta-selector decide who to trust. Every turn is scored with quality-proxy signals (tool failures, model retries, cost, latency) and the reward feeds back into the strategies — the router gets better as it runs. Every decision and reward is persisted locally: auditable and replayable.
Real switching since v1.4.0: v1.3.0 and earlier swapped models by returning a replaced config from the agent/request waterfall, but DSH's built-in model-selection listener overrides that value, so the swap never took effect. v1.4.0 instead appends a request/header event (reason: router) to the session log at turn start, going through DSH's official model-selection precedence chain (in-process selection → latest logged session request/header → default model).
Architecture
user input → classify (rules, <1ms) → 4 expert recommendations → EXP3 meta-pick → request/header append (real switch)
↑ │
UCB/LinUCB/k-NN/EXP3 updates ← quality-proxy reward ← turn end (tool errors/retries/cost/latency)| Expert | Algorithm | Status |
|---|---|---|
| rule | deterministic thresholds (complexity ≤0.3→fast, ≥0.7→strong, else mid) | cold-start prior, always in pool |
| UCB | cluster×tier multi-armed bandit; unexplored arms get ∞, ties rotate | active from day zero |
| LinUCB | linear feature scoring + exploration bonus, gradient updates | shadow; graduates at 50 samples |
| k-NN | k=5 nearest-neighbor aggregation over the experience pool | shadow; graduates at 30 experiences |
| EXP3 | meta-selector, importance-weighted updates | always on; cold-start biased to rule (weight 2.5) |
Design rationale and roadmap: [docs/DESIGN.md](./docs/DESIGN.md). The implementation was validated against a live runtime prototype first; eight defects found there are fixed and pinned as regression tests.
Install
dsh plugin --profile web add dsh-agentic-router> --profile is mandatory. Restart the web profile after installing so the tool schemas join prompt assembly.
Switch to real routing (active)
Default mode is shadow (records and learns, never swaps). To enable real switching, the easiest way is: after restarting, tell the agent in any DSH session:
> Switch the agentic router to active mode
The agent calls agentic_router_set_mode (mode=active) and persists it to ~/.dsh/storages/dsh-agentic-router/policy.json — it stays active across restarts.
Alternatively, start active on first load by adding config in the profile's cordis.patch.yml:
- id: agentic-router
config:
mode: active(Pick one; once a mode has been saved via the tool, policy.json takes precedence over config.mode.)
Tools
| Tool | Purpose |
|---|---|
agentic_router_stats | decision log, four-expert recommendations, EXP3 weights, flywheel state |
agentic_router_set_mode | shadow (default) / active (real swaps) / off (bypass) |
agentic_router_force | force a model id (active mode only); clear=true to release |
agentic_router_reset | wipe flywheel learning state, keep mode |
agentic_router_set_prices | set/query per-provider per-model (or per-tier) prices |
Data flywheel (persistence)
Directory: ${DSH_HOME:-~/.dsh}/storages/dsh-agentic-router/ (override with AGENTIC_ROUTER_HOME)
decisions.jsonl— one line per decision: task type/complexity, all four recommendations, meta pick, actual route, swapped?rewards.jsonl— one line per reward with breakdown (tool errors/retries/latency) + feature vector; replayed on startup to restore learning state; human-feedback settlements (feedback-settlelines) correct the replayed rewards exactly- Human feedback: 45s after a turn closes, the plugin polls
messageFeedback(the Web UI 👍/👎); a thumbs-down costs −0.5, a thumbs-up adds +0.1, applied as an exact correction to the already-applied reward policy.json— mode and forced model (atomic write)
Reward: clean close starts at 1; tool failures −0.2 each (cap 0.6), model retries −0.2 each (cap 0.4), cost min(0.5, 20 × turn cost in CNY), latency >30s −0.1 / >90s −0.3; errored turns score 0.
Cost signal (token billing): the plugin wraps llm/stream and accumulates each model call's usage chunk (input/output/cache-read/reasoning tokens); providers without usage fall back to length-based estimates (flagged estimated).
Three-level price resolution (CNY per million tokens): provider→model exact price > provider→tier price > * generic tier > conservative fallback. Unknown providers never inherit DeepSeek pricing — they use the generic tier and carry a pricingSource audit field. DeepSeek's official pricing ships as default; add other models at runtime via the agentic_router_set_prices tool (persisted to policy.json) or at install via config.prices:
{ 'deepseek-official': { 'deepseek-v4-pro': { in: 1, out: 12 } },
openai: { 'gpt-x-pro': { in: 15, out: 60 } },
'*': { fast: { in: 1, out: 4 }, mid: { in: 2, out: 8 }, strong: { in: 3, out: 16 } } }Known limits
- Model tiers are recognized by id keywords (flash→fast, pro→strong); real swaps happen only when more than one tier exists
- Feedback settles after a 45s window (configurable via
settleDelayMs); unsettled pending entries are lost on restart; late feedback may be attributed to a later turn - Feedback only works where the deployment's Web UI actually writes it (this plugin only reads)
- LinUCB / k-NN need dozens of turns before they graduate into the meta pool
Development, testing and release flow (maintainers): see [docs/MAINTAINING.md](./docs/MAINTAINING.md).
License
MIT