782042369/dsh-model-compat-guard

dsh-model-compat-guard

DSH (DeepSeek Harness) compatibility guard plugin: fixes reasoning-model compaction truncation, doomed sandbox-escalation fail-close, and missing tool description for GPT/Qwen/third-party models

Uncategorizedcompatibilitydeepseek-harnessdsh

dsh-model-compat-guard

中文文档

A compatibility guard plugin for DeepSeek Harness (DSH) that repairs four frequent failure modes when driving DSH with non-DeepSeek models (GPT, Qwen-thinking, self-hosted reasoning models, ...). Zero-config out of the box; every fix can be toggled in ~/.dsh/compat-guard.json.

1. Reasoning-model auto-compaction failure

Symptom: summarization truncated at the token cap (incomplete checkpoint) — compaction aborts on every attempt (upstream Discussion #3465).

Root cause: dsh-compaction-basic summarizes with a hard maxTokens cap (default 8192) and fails closed when the summary hits it. Reasoning models spend invisible thinking tokens inside that same output budget, so any model that thinks heavily truncates and compaction aborts.

Fix: intercepts purpose === "compaction" LLM requests on the llm/stream waterfall and

  • raises maxTokens to 32768 (configurable); clamps only when a provider explicitly exposes hardMaxTokens, never from DSH's defaultMaxTokens;
  • drops the reasoning effort to the cheapest level the model supports (prefer off > minimal > low);
  • never forces an effort on models without reasoning support (avoids UNSUPPORTED_REASONING_EFFORT).

2. Doomed sandbox-escalation requests fail-closing tool calls

Symptom (upstream Discussion #3877):

Error: sandbox escalation to "danger-full-access" is not strictly wider than this call's current "danger-full-access" mode
Error: invalid justification: expected a non-empty sentence

Root cause: when a sandbox executor is attached, the bash/write/edit tool schemas advertise a sandbox_permissions field globally, but the effective mode is resolved per session. Execution requires the request to be strictly wider plus a non-empty justification, otherwise the whole call fails closed. GPT/Claude-class models attach the field speculatively and blow up in danger-full-access sessions.

Fix: the tools/execute hook resolves the session's standing mode and strips sandbox_permissions + justification when the request is doomed (same mode / narrower / no sandbox service / unknown mode), executing at the standing mode — semantically identical, because the model already has what it asked for. Strictly-wider requests are preserved and go through the normal approval flow.

stripEscalation: "redundant" (default — only strip doomed requests) | "always" (strip even legal escalations, for sessions with approval prompts disabled) | "off".

3. missing required property "description"

Root cause: bash / run_code / subagent declare a required description argument (used as the UI label); strict JSON-Schema validation rejects the whole call when the model omits it.

Fix: on tools/execute, before strict validation, synthesize the description from the call payload when required and missing:

  • bash → first line of the command (Run: cd /tmp && rm -rf build)
  • run_code → first effective code line (comments / braces skipped)
  • subagent / subagent_fork → first line of the prompt
  • workflow → fills the nested meta.description too

Calls that already carry a usable description are untouched.

4. Code Mode (PTC) run_code failures

Symptom (upstream Discussion #1605):

Error: code run failed (exception): Expected ',', got '<eof>'
Error: code run failed (exception): TypeError: b.stdout.slice is not a function

Root cause: (a) the model emits a syntactically incomplete TypeScript program — unbalanced quotes/backticks/brackets or truncated code, so the type-stripping parser (amaro/SWC) hits end-of-file while expecting a comma; (b) the model treats the bash result's stdout as a string — it is a structured object { text, truncated, spillPath? }; the correct read is res.stdout.text. Both stem from models unadapted to PTC's "call every tool from TypeScript" convention, amplified by the harness reporting only a one-line error with no position.

Fix: the llm/stream hook detects code-mode requests (when run_code is the single wired tool) and appends a compact discipline block to the end of the system prompt: tool results are plain JSON values (no .result() wrapper), the correct bash result shapes, and closure hygiene for complete programs. Appending at the end keeps every provider prefix (and prompt cache) intact.

codeDiscipline: "off" (default — use native AGENTS.md) | "auto" / "always" for legacy mutable request objects; frozen requests skip safely.

Configuration (optional)

~/.dsh/compat-guard.json:

{
  "compactionMaxTokens": 32768,
  "compactionEffort": "auto",
  "compactionStripTools": false,
  "compactionPurposes": ["compaction"],
  "fillDescription": true,
  "descriptionTools": ["bash", "run_code", "subagent", "subagent_fork", "workflow"],
  "codeDiscipline": "off",
  "stripEscalation": "redundant",
  "unknownPolicy": "preserve",
  "modelInfoTtlMs": 300000,
  "modelInfoTimeoutMs": 5000,
  "logFixes": true
}
  • compactionEffort: "auto" (cheapest level) | "keep" (leave untouched) | an explicit effort id.
  • compactionStripTools: when true, drops the tools list from compaction requests (prevents the model from calling tools mid-summary; costs the prefix KV cache).
  • codeDiscipline: "off" (default; native AGENTS.md is preferred) | "auto" | "always" for legacy mutable-request compatibility.
  • Same-named fields passed as cordis plugin config override the file.

Apply config changes with systemctl restart dsh-web (or restart your dsh web process). Look for compat-guard: lines in the logs to see every applied fix.

Install

From the marketplace (Settings → Plugins → Marketplace, searchable as dsh-model-compat-guard) or directly:

dsh plugin --profile web add github:782042369/dsh-model-compat-guard#v0.4.0

Restart dsh web afterwards. Uninstall: dsh plugin --profile web remove dsh-model-compat-guard.

Test

node test/smoke.mjs   # mock-driven assertions for all four fixes

License

MIT