dsh-daruma
Keeps long DSH sessions alive when a model channel starts failing: daruma takes over after the in-box retry gives up, trips a self-healing circuit breaker on repeated 429/5xx or a terminal error, and moves the next request to another channel in your chain. Works on DSH 0.1.x and 0.2.x hosts, includi
Install
dsh plugin --profile web add dsh-daruma
Sandbox test results
| Stage | Result |
|---|---|
| 1 install | ✓ |
| 2 list | ✓ |
| 3 mount | ✓ |
| 4 boot | — |
Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).
README
dsh-daruma
Daruma resilience plugin for DeepSeek Harness — detects model-request failures
and fails over to another channel to keep long tasks alive.
How it works
dsh-daruma is a native Cordis plugin. It sits **downstream of the in-box
dsh-llm-retry** on the agent/request-error waterfall: retry owns the
same-channel retry budget, and when it gives up it delegates (via next()) to
daruma. daruma then:
1. records the failure against the current channel's circuit-breaker state;
2. when the channel trips (failureBudget consecutive failures, or a terminal
code like QUOTA / INVALID_CREDENTIAL / CONTEXT_WINDOW_EXCEEDED), it
arms the next routable channel (the user-chosen backup first, then the
configured chain) and returns { kind: 'retry' };
3. on the retry turn, the agent/request waterfall swaps the request config
onto the armed channel — dropping the caller's reasoningEffort when that
target does not advertise the level (see below);
4. appends one JSON line per decision to the audit log (see below).
A failover target has to accept the request. Two different refusals hide
behind that sentence.
A channel-level refusal is the usual one: DSH validates an explicit
reasoningEffort against the target's own capability and refuses the
request before any provider I/O; that refusal is raised outside
agent/request-error, so a swapped request carrying an effort the target
cannot take kills the turn without ever reaching the provider — the failover
channel never dispatches, and the channel that is actually broken keeps looking
healthy. daruma therefore reads the target's model metadata on every swap: a
supported effort is kept, an unsupported (or unresolvable) one is dropped so
the target can use its own default, and the drop is logged —
dsh-daruma: mt::glm-5.3 does not accept reasoning effort "high" …. Every
other field (maxTokens, temperature, stop sequences) is passed through
untouched.
A request-level refusal is the provider answering 400 INVALID_REQUEST
because of what the conversation contains. The observed case: DeepSeek's
Messages API rejects any assistant turn that carries tool_use but no
thinking block while thinking mode is on, so a conversation that started on a
non-DeepSeek model cannot be replayed there. That channel is healthy for every
other conversation, so daruma does not open its circuit; it records the channel
as unusable for that conversation only (INVALID_REQUEST is request-scoped,
see daruma-core) and picks a different target. The exclusion lasts until the
conversation's scope is disposed, so one rejected step cannot drag the turn
back to the same dead route and burn the give-up budget.
Self-healing: the host exposes no request-success event, so success is
inferred — an agent whose previous model request never tripped
agent/request-error earns its channel a success record at the next
agent/pre-step or agent/turn-stopping. The circuit closes and the failure
counter resets; tripped channels never linger as zombie COOLDOWN entries.
Channel health persists to ~/.dsh/daruma/channel-health.json, so a tripped
channel stays cooled-down across restarts.
Visible failover notices in the conversation
Older builds appended a daruma/failover session event on each channel
switch, and the web client renders that as a small row inside the
conversation (`mt::glm-5.3 failed (RATE_LIMIT) → trying
mt::deepseek-v4-flash`, budget usage on hover; a red-dotted give-up row when
recovery is exhausted). Current builds do not append it. The host cannot mark
a plugin event ignorable, and a session containing an unmarked daruma/*
event fails to load. New switches are recorded only in the audit log.
Usage screenshots / 使用截图
Captured from live DSH Web sessions: the composer and the channel picker on a
real mt provider, the failover row from the mock-a → mock-b demo flow.
The images are linked by absolute URL on purpose: npm's web UI does not resolve
relative README image paths, so a ./assets/… link renders broken on the
package page (it still works on GitHub). The copies shipped in the tarball
(files) are for readers of the package itself.
English
The composer. The daruma control sits at the right end of the tool row, next
to the model selector, and shows the armed backup channel (`Backup:
mt/glm-5.3`):
Picking a backup channel. Clicking the control opens the picker: the current
backup, a provider box, and the candidates discovered from that provider's
adapter:
The model selector the control sits next to:
A failover notice rendered inline in the conversation, right where the switch
happened:
中文
输入框。 daruma 控件在工具行最右端、模型选择器旁边,显示当前已设置的备用渠道
(备用: mt/glm-5.3):
选择备用渠道。 点开控件即出现选择面板:当前备用、provider 输入框,以及从该
provider 适配器发现的候选模型:
模型选择器(控件就在它旁边):
切换提示,直接渲染在会话流里发生切换的位置:
Audit log
Every failover / give-up / boot decision is appended to
~/.dsh/daruma/failover-log.jsonl (rotates at 2 MiB
…(truncated)
Rendered from the README published on npm. Formatted only, not edited.
Versions
Published on npm::12,最后更新 2026-11-04