DSH Runtime Tuning
A long-running experiment on the agent runtime: find where time goes in real DeepSeek Harness sessions and what is wasted, then fix it with plugins, each change verified with data.
- Status
- Active
- Published
- Oct 29, 2026
- Updated
- Nov 6, 2026
- Tech
- DeepSeek Harness · Node.js · JavaScript
A long-running experiment on the agent runtime: find where time goes in real DeepSeek Harness (DSH) session logs and what is wasted, then fix as much of it as possible with plugins, each change verified with data.
It is both a set of installable DSH plugins and a record of that process, and more plugins will be added as the analysis continues. The full analysis and experiments are in the article “从一份 9.7 小时的真实会话开始” (in Chinese).
Starting point
One everyday DSH session: 119 turns, 2347 tool calls, spanning 9.7 hours. Counted event
by event, a large share of the time besides model generation went to blocking waits on
background jobs, waiting for answers to questions, waiting for approvals and foreground
sleep; the same GitHub token also appeared in dozens of commands and model replies.
Plugins so far
| Plugin | What it does |
|---|---|
wait-guard |
Blocks long foreground sleeps and long blocking waits; wakes the model as a backstop when a background job never reports back; an optional task_status tool |
ask-nonblocking |
Makes ask_user_question stop blocking once the question is asked |
secret-guard |
Keeps tokens out of the model’s context |
chat-answer-fix |
Replaces the DSH chat UI check so answers are not folded away when a turn ends with a tool call |
secret-files |
Hides credential files at the sandbox mount layer so file tools and child processes see a notice instead of the real contents |
fs-view |
Lets edit replace exact text even when the file was not read first; read_image detects image format from the file header |
session-tmp |
Makes file tools and bash share the same /tmp (only when bwrap /tmp has been remapped) |
stream-stall |
Once an answer has started streaming and only heartbeats arrive, cuts the request after stallSeconds (default 180) with no new content, and DSH retries it as a transport failure; thinking time and output length are not limited, and nothing is done before the first content arrives |
todo-reminder |
When the todo list still has unfinished items and has not been updated for 15 steps, appends a reminder and the current list after a tool result so the model can check progress; no reminder if no list was written or everything is done; frequency is configurable |
Takeaways
- The gain is “not holding the conversation”, not “faster”: once a long wait is split into “run in the background → end the turn → the completion event wakes a new turn”, the user can step in at any time while a build runs
- Don’t rely on the model to report its own state: in long sessions the model
almost never calls
task_status, so the runtime now judges state from facts andtask_statusis optional - Not every problem is worth fixing: a plugin can only deny a call, not change its arguments, so for cases that trigger often but are rarely a real problem, denying costs more than it saves, and those are left alone
Current status
- Version: 0.2.8, open source (MIT license)
- Environment: verified only on the
webprofile of DSH0.2.1-rc.5, on Windows 11 + WSL2;headless,sdkand other profiles are not supported yet - Tuned preset: an optional “tuned” mode that drops tools unused for everyday coding
and adds guidance for using the remaining ones well; the standard mode is unchanged; since v0.2.7 it is off by default, and if you have used the tuned preset, add
- id: runtime-tuning-preset-tunedanddisabled: falseto your owncordis.patch.yml, otherwise conversations started with that preset show “session unavailable” after the upgrade - Known limits:
ask-nonblockingrelies on DSH internals and may stop working after a DSH upgrade, in which case questions simply fall back to blocking; the hard backstop’s “wake when the timer fires” path is covered only by unit tests so far - Tests: 109 tests in total; they use only Node.js’s built-in
node:testwith no dependencies to install, and run on Node.js 22 / 24 through GitHub Actions - Completion notices and reminders come from the companion plugin DSH Sentinel (successor to dsh-task-notify), which also handles the tray, crash protection and plugin health checks
- DSH version requirement: from v0.2.6 it targets DSH 0.2.2-rc.1, and the required DSH
version is now
>=0.2.2-rc.1; if you are still on 0.2.1, keep using 0.2.5 - v0.2.8 adds: todo-reminder. Official
todo_writeonly records and never reminds, so in long tasks the list is often written once and then stuck on the first item; now, when unfinished items remain and the list has not been updated for 15 steps, a reminder and the current list are appended after a tool result, asking the model to update in the same step as the next tool call rather than spending a step on its own. No reminder if no list was written or everything is done; frequency is configurable - v0.2.7 changes: the tuned preset is now off by default. A conversation started with a preset stays bound to it, and if the preset is gone or fails to load those conversations cannot be opened; and there is still no same-task timing data showing that it saves time, so it should not be on by default. New test: it compares the tuned preset item by item against the local DSH’s standard preset and reports any difference other than the deliberate removals and additions, so after a DSH upgrade you run the tests first and then update the preset by hand
- v0.2.6 fixes: adapts to DSH 0.2.2-rc.1. 0.2.2 removed the
dsh-tool-subagent-control/list-agentssub-path (merged intodsh-tool-subagent-control), and the old tuned preset referenced it, so sessions using the tuned preset could not be opened (showing “session unavailable”) and new ones could not be created; the preset is updated to match 0.2.2’s standard. chat-answer-fix: the display-settings fields are read according to the current official Config (0.2.2 addedfoldedReplies); right after DSH starts, the loader is still inside a reload transaction and moving the settings failed, so it now retries after a short wait; the new loader hands back iterators, and the old code searched the same iterator twice and so rewrote the settings on every start, so it now converts to an array first - v0.2.5 adds: stream-stall. DSH’s own stream idle timeout is reset by heartbeats, so
when the server degrades the UI can sit on “thinking” forever; now, once an answer has
started streaming, the request is cut after 180 seconds (the default, adjustable through
stallSeconds) with no new content, and DSH retries it automatically. The docs also now name dsh-sentinel instead of dsh-task-notify - v0.2.4 fixes: display settings such as how tool steps are shown no longer reset to “detailed” on restart; the official chat UI is found even when DSH was not installed with pnpm