- Home
- Plugins
- Dev & Plugin Tools
- dsh-remedy
dsh-remedy
jjjjjjjjnnjnn/dsh-remedy
DeepSeek Harness plugin that intercepts stream errors caused by fused tool calls, splits glued top-level JSON objects, and re-throws unprovable buffers.
Install
dsh plugin --profile web add github:jjjjjjjjnnjnn/dsh-remedyREADME
dsh-remedy
dsh-remedy is a resident repair layer for DeepSeek Harness (DSH). It mounts on llm/stream, splits tool-call argument text that a relay glued into one index, and re-throws the original failure whenever it cannot prove the split. It also names the failure classes it refuses to repair, so a dead turn comes with a line explaining it instead of silence.
What it fixes
When an upstream provider relay packs two tool calls into one index, @earendil-works/pi-ai receives {...}{...} and throws. dsh-llm-pi-ai converts this into LlmError(..., 'MALFORMED_RESPONSE'), and dsh-agent-loop aborts the turn because MALFORMED_RESPONSE is not in retryableCodes. Upstream discussion deepseek-ai/deepseek-harness #4427 describes the same failure family.
Tool call "<name>": Unexpected non-whitespace character after JSON at position N
Received arguments: {...}{...}
Measured over this machine's session directory: 78 session files, 393 attempts, 38 fused calls, 44 MALFORMED_RESPONSE failures of which 39 are repairable, and 0 unreadable lines.
The turn census of the same directory counted 293 turns: 94 completed, 88 max_tokens, 100 error and 11 aborted. The error codes it named are MALFORMED_RESPONSE 44, UNSUPPORTED_REASONING_EFFORT 37, SERVER 16, UNKNOWN 1, MISSING_CREDENTIAL 1 and INVALID_REQUEST 1.
Two of those classes needed different treatment, and both are now visible:
MALFORMED_RESPONSEis a fusion, so it is repaired when the split can be proven.UNSUPPORTED_REASONING_EFFORTis a deterministic refusal: a profile asked for a level the adapter does not offer (provider "opendesign" model "deepseek-v4.1-flash-eco" does not support reasoning effort "xhigh"). Retrying it spends a request to learn the same answer twice, so the plugin names it once and refuses to touch it.
Install
Install the plugin into a DSH profile:
dsh plugin --profile <name> add dsh-remedy
Install as a project dependency:
pnpm add dsh-remedy dsh-remedy-core
Version 0.3.0 of the plugin cannot be installed from the registry, because npm published its raw workspace manifest with a literal workspace:* dependency. Every release after it ships a pnpm-packed tarball instead. Version 0.3.1 carried the same manifest fix but was superseded before it reached the registry.
Source workflow for contributors:
git clone https://github.com/jjjjjjjjnnjnn/dsh-remedy.git
cd dsh-remedy
pnpm install
pnpm run verify
How it works
dsh-remedy buffers incoming tool-call deltas on the llm/stream Cordis seam:
Input chunks:
block-start { id: "call_01", name: "ops_bash" }
tool-call-delta '{"command":"ls"}{"command":"pwd"}'
finish { kind: 'error', error: LlmError('MALFORMED_RESPONSE') }
Repaired output:
block-start { id: "call_01", name: "ops_bash" }
block-end { arguments: '{"command":"ls"}' }
block-start { id: "call_01#remedy-1", name: "ops_bash" }
block-end { arguments: '{"command":"pwd"}' }
finish { kind: 'stop' }
The repair is all-or-nothing. Every open buffer must parse into top-level JSON objects. If all of them are proven, each call gets its own block-start and block-end and the error finish becomes {kind:'stop'}. If any buffer cannot be proven, nothing is emitted and the original failure goes out untouched.
A second listener runs on agent/request-error, which the loop calls after a failed attempt:
no code, empty code, or an aborted signal → next()
UNSUPPORTED_REASONING_EFFORT, → name it once per step and code, then next()
INVALID_REQUEST, MISSING_CREDENTIAL,
EMPTY_RESPONSE
IMAGE_OFFLOAD_REQUIRED, INVALID_CREDENTIAL → next()
MALFORMED_RESPONSE with budget left → { kind: 'retry' }
anything else, or budget spent → next()
That order follows the contract the runtime documents: a handling listener returns { kind: 'retry' } without calling next(), and a listener that does not own the decision must return next(). Calling next() first and then overriding its answer would discard what the default chain decided, and context compaction authorizes its own retry on exactly that path.
Retry is off by default (retryOnMalformed: 0). The budget is a bounded cache of 256 entries per process, which is far more than one turn needs and small enough that a long-lived desktop session cannot grow it.
Ask it what is happening
dsh-remedy explain reads the registry, an optional profile layer and an optional session directory, and prints one row per failure class: how often this machine hit it, which patch covers it, and whether that patch is switched on in the profile you named.
dsh-remedy explain --sessions ~/.dsh/sessions --profile-file ~/.dsh/profiles/<name>/cordis.patch.yml
failure class turns covered by enabled here
--------------------------- ----- ------------------- -------------------
completed 124 (none) (none)
max-tokens 88 by design no (by design)
MALFORMED_RESPONSE 47 P-0001…P-0005,P-0011 P-0001…P-0005
UNSUPPORTED_REASONING_EFFORT 37 P-0013 P-0013
SERVER 16 by design no (by design)
MISSING_CREDENTIAL 1 P-0013 P-0013
Without --sessions it prints no counts rather than a row of zeros, and without --profile-file it says the answer is unknown rather than assuming the default. A class this machine hits, that a patch covers, but that your profile switched off, makes it exit 1.
Safety guarantees
- Top-level JSON objects are split strictly at depth zero;
}{inside a JSON string is not a boundary. - The first split call keeps its original id; synthetic calls receive
<id>#remedy-N. - If any buffer cannot be proven, nothing is emitted and the original failure is re-thrown.
- A single object that fails to parse is rejected, never split.
- The plugin mounts only its own rows, adding no route, no tool and no UI.
- The plugin writes nothing to disk unless
journalPathis configured. retryOnMalformeddefaults to 0 because retry is a stop-gap, not the fix.- An empty tool name is rejected (
missing-name) and never dispatched. - A tool name that changes inside one index aborts the rescue (
name-changed) and the original failure is re-thrown. - Adapter
replayStateis preserved across a repaired finish. - A
block-endrepair is held until the tool-call phase ends; a later rejection in the same stream withdraws it (rolled-back) and the original chunks go out. maxCallsbounds the calls synthesized for the whole stream, not per block.- A consumer that stops early is recorded as
consumer-abort, never as quiet. - A synthesized call is emitted as a
block-startplus ablock-end, never a bareblock-end. - A withdrawn repair returns its calls to the stream
maxCallsbudget. - A
max-tokensstream is counted as truncated, never as an unrepaired fusion. - A provider error that passes through is counted in
errors. - A pure refusal raises
refusedand neverfused. - A budget refusal (
too-many-calls) raisesfused,refusedandunrepaired, because no repair happened. - The four verdict words are unchanged (
unmeasured,quiet,degraded,repaired), evaluated in that order; only the counter line grew, to eight names plus the optionalresealed. - A bare member value is quoted only when exactly one segment reproduces the whole text as one plain object with a byte-identical payload.
- Zero candidates is refused as
bare-value-unproven; two or more is refused asambiguous-bare-valueinstead of guessed. - A stream whose only defect was a reseal reads
repaired, neverquiet. - The retry listener names a diagnosis exactly once per step and code, so a host-level retry of the same failure cannot fill the journal with the same sentence.
- A cancellation is never overridden by a retry.
- Control codes (
IMAGE_OFFLOAD_REQUIRED,INVALID_CREDENTIAL,MISSING_CREDENTIAL) are passed through and never retried. scanreads a multi-frame zstd log throughdecompressSessionLog, the same reader the plugin ships.- A directory scan keeps one auditable row per file.
--sincereports what it dropped asbefore_sinceinstead of hiding it.- An undecompressable file is a failure line and exit 3, never a silent zero.
- Redaction matches a home path escaped with one or two backslashes as well as the session directory that encodes it.
- The scan counts every
turn/end(completed,max_tokens, aborted, error) and names everyreason.error.code. - The census is reported and never promoted into a finding, so the exit code still turns on
fused_callsandfusion_failuresalone. - The byte budget is a gate, not a paragraph: it measures the modules a mounted plugin loads (residency), the modules only a command loads, and the total, and every emitted module must belong to exactly one of the first two.
What it does not do
| Limit | Reality |
|---|---|
| Attribution | Stops at the assembled text: the plugin cannot prove whether the relay or the adapter glued the objects. |
| Truncation | A max-tokens cut is missing bytes and is not repaired. |
| Localized messages | The fallback parses the English Received arguments: text; a localized host message disables that fallback, though delta buffering still works. |
| Headless session files | A headless profile does not persist assistant/attempt streams, so the artifact reports 0 fused calls; the journal counters and the synthetic id carry the proof. |
| Synthetic IDs | <id>#remedy-N are traceability aids, not durable identities. |
| Desktop completion | The desktop artifact records turn_end=aborted because the operator stopped after the answer. |
| App cache | Verification applies to the recorded tarball's sha256, not to whatever a running app has cached. |
| Verdict granularity | One verdict word cannot express "observed and correctly refused"; only the counter line can. |
| Journal migration | Existing journal lines are not migrated; in a mixed journal status --json reports the shape of the last line. |
| Custom index tracking | An adapter that closes blocks itself and keeps its own index bookkeeping is still unmeasured (BS-11). |
| Request retries | Off by default. SERVER, TRANSPORT and TIMEOUT are already retried upstream, so this plugin only retries MALFORMED_RESPONSE, and only inside a budget you set. |
| Reasoning effort | The plugin can tell you a configured effort will be refused, but it never changes it for you. agent/pre-step is deliberately not mounted: nobody has measured what a user sees when a plugin rejects a step. |
| Mid-stream withdrawal | Measured unreachable on this host: none of the persisted attempt streams carried a provable fusion inside a block-end, which is what rolled-back requires (BS-14). |
Verify it yourself
dsh-remedy verify
dsh-remedy scan ~/.dsh/sessions --json
dsh-remedy explain --sessions ~/.dsh/sessions
pnpm run verify
node scripts/gate.mjs --mutate
Every artifact in data/results/ is script-produced and has a .sha256 sidecar. Two are worth opening first: the live run data/results/live-2026-11-12T12-20-00.000Z.json and the corpus replay data/results/malformed-replay-2026-11-12T11-30-00.000Z.json.
The whole MALFORMED_RESPONSE corpus on one machine was replayed through the built engine: 47 turns, 39 repaired, 8 refused, and no repaired case dispatched a call whose arguments failed to parse. A generated stress pass of 300000 argument texts asserts the same invariant with a recorded seed.
Status
P-0001, P-0002 and P-0003 are verified on a real host: P-0001 on a headless profile and on the desktop app, P-0002 on a 2026-11-10 headless run that repaired a real two-call fusion and saw the synthesized call dispatched, and P-0003 on a 2026-11-10 headless run whose journal wrote a non-zero refused after the code declined to repair a budget refusal.
P-0004, P-0005, P-0006, P-0007, P-0008, P-0009, P-0010, P-0011, P-0012 and P-0013 are tested, not verified. Each has unit tests and negative controls, and a measured fixture, scan or corpus where one exists. P-0004, P-0006 and P-0009 change an offline reader; P-0005 has no live bare-value shape yet; P-0007 and P-0012 are build-side; P-0010 changes the CLI usage text; and P-0011 and P-0013 sit on a seam a live journal witnesses only when a turn fails in a class this plugin retries, which the default budget of 0 never triggers.
Release status: 0.3.8 is published and is the registry latest for both dsh-remedy and dsh-remedy-core. The previous release is 0.3.7.
Package sizes:
- 0.3.8 plugin: 35898 bytes compressed, 35 files, 117750 bytes unpacked (81740 bytes of
dist/*.js, 26605 bytes ofdist/*.d.ts, 3583 bytes offixtures/, 5822 bytes of package-root files), shasum3bc47df12f21fe7d68336f595d397ff868b3d4d7. - 0.3.8 core: 26551 bytes compressed, 21 files, 83461 bytes unpacked (58062 bytes of
dist/*.js, 22102 bytes ofdist/*.d.ts), shasum0df993f85589e101b15b830fa105e7ef8c66c5d5. - 0.3.7 plugin: 29462 bytes compressed, 29 files, 98001 bytes unpacked, shasum
69087301f5ff802e387061af61756b65b883b11d; core: 20000 bytes compressed, 19 files, 65961 bytes unpacked, shasum33eaaf0320eb474d5d7c5ce2761f10de79d17d21. - No sourcemaps are shipped; that removal is worth 41926 bytes of unpacked payload.
The emitted plugin budget is three checks: residency 41075 of 44000 bytes (the modules a mounted plugin loads), cli 40665 of 42000 bytes (the modules only a command loads), and a total of 81740 of 83000 bytes. Per-file caps: repair-stream.js 28000, session-scan.js 14000, cli.js 12000. A patch that crosses a per-file number splits that file instead of absorbing it.
Development
pnpm install
pnpm run verify
pnpm run gate
node scripts/gate.mjs --mutate
Six gates run on every change: ssot, registry, namespace, step-review, copy, size. --mutate breaks each one on purpose and requires every one to go red; it currently catches 6 of 6. The suite is 228 tests and 0 failing.
Contributing
- Issues and pull requests are welcome.
- A behaviour change needs a failing test first.
pnpm run verifymust pass, andnode scripts/gate.mjs --mutatemust catch every gate.- Gates use Node built-ins only; a new gate must be registered in
audit/gate_ledger.json. - No new runtime dependency in
dsh-remedy-core. - A patch cannot claim
verifiedwithout adata/results/live-*artifact. - Outward text is checked, not authored freely: every number in it must exist in
docs/copy/facts/public-repo-facts.md, and the bytes must matchdocs/copy/manifest.json.
Security
Security reports should use GitHub private vulnerability reporting on the public repository: https://github.com/jjjjjjjjnnjnn/dsh-remedy/security/policy
License
MIT. Independent project; not affiliated with DeepSeek.
Related plugins
deepseek-harness
deepseek-ai/deepseek-harness
dsh-market
dsh-market/dsh-market
dsh-web (dsh-plugin-manager)
zhu1090093659/dsh-web
dsh-web
zhu1090093659/dsh-web