dshbase

Blog · Analysis

DeepSeek Harness from the inside, and a three-model fix test

August 25, 2026 · dshbase · a deep-dive for installers and agent builders

The agent world has a tidy formula — Agent = Model + Harness. DeepSeek open-sourced the harness half on August 13, and pairing it with its own models makes a complete agent system. What's actually interesting isn't the round-the-bend feature list; it's the architectural bet. As the project headline puts it, everything is a plugin — the model adapter, the file system, the sandbox, the session log, even the main loop that drives the agent are all plugins.

The DeepSeek Harness GitHub README: everything is a plugin, powered by Cordis, developer preview, one-command start

The shape of the system

Running dsh splits into two hosts plus a CLI. The Node Host loads the agent core, capability providers, execution policy and local state; the Browser handles the UI and pushes operations into Node through the Web Host (HTTP, API, forwarded events). A CLI mode skips the browser and hands the job to a headless runner. Both entries reach the same model, tools, session and main loop — swapping a model or file-system provider requires no entry-point change at all.

DeepSeek Harness architecture: Browser and CLI entry points feeding one Cordis plugin-tree agent core

Composition is governed by Profiles and Bundles: a bundle is a pre-picked set of plugins, a Profile decides which bundles this launch uses, and the user can stack their own config on top. The Cordis Loader turns that config into a running plugin tree, honoring the dependencies each plugin declares.

Cordis holds it together

Cordis is the dependency-and-lifecycle framework underneath. Every plugin declares what services it needs and what it provides. A file tool that needs a file-system service simply waits; the moment that service registers, Cordis calls its apply. If a service is replaced or unloaded, Cordis tears down the plugins that depended on it and re-activates them once the new implementation is ready. Each plugin gets a Fiber tracking its pending state, its registered services, and its cleanup callbacks — no plugin runs until its dependencies are satisfied, and none leaves stale registration behind.

Plugins connect two ways. Long-lived capabilities go through services ("what am I currently depending on"); moment-in-time behavior goes through events ("who wants to participate when this happens"). The main loop sits in the execution path but is, to Cordis, just another plugin — which is the real meaning of "the main loop is a plugin": there is no single giant core that can't be replaced, only a set of plugins with declared relationships.

Cordis offers five event-dispatch modes for cases where many listeners can respond to one event:

  • emit — notify, don't await async results (state-change notices)
  • bail — ask sequentially, stop at the first valid answer (client-side decisions)
  • waterfall — pass along in order, allow interception (wrapping a model request or tool call)
  • parallel — run concurrently and wait for all (independent async work)
  • serial — ask asynchronously in order, stop at a valid answer (sequenced decisions)

The Capability Seam, shown through the file system

dsh splits any replaceable capability into three roles: the Service Definition declares what anyone can call; the Provider does the real work; the Consumer hands it up to the model. Together dsh calls this a Capability Seam. tool-fs calls resolve() to get a reference and then only ever passes that reference to readText()/writeText() — how the reference maps to a real file (a local realpath, a remote absolute path, an E2B path) is entirely the Provider's business. Swap the file system and tool-fs still works against the same interface; the model still sees read, edit, write.

What a message does to the loop

A user task lands in the Inbox; the loop writes turn/start and a turn begins. A step is one model request plus the tools it calls; a turn is zero or more steps. Before each step, agent/pre-step lets plugins inspect the incoming input — compression can shorten history, others can add to, rewrite, or reject it. At agent/turn-stopping a plugin may inject one more message so the model takes another step; only when nothing asks to continue does the loop write turn/end.

Messages arriving mid-run split by intent: a follow-up like "don't touch the config" goes to next-step (the current turn continues); a brand-new question goes to next-turn (queued for the next turn). dsh decides this not by semantics but by how the sender delivers it — followup() routes to next-turn, steer() and inject() route to next-step.

The logging principle is strict and worth restating: whatever enters a model request must be reconstructable from the Session log. User words, model answers, tool results, system prompts, tool definitions — all recorded. The log is append-only; compression writes new replacement content rather than rewriting old records. Empty assistant messages (a token cap hit) stay in the log as fact but are not folded into the next request's history.

Tool policy: one safety layer for everything

Value comes from tools, and so does risk. Before a tool call resolves, tools/pre-execute listeners allow, ask, or deny; a monotonic guard can only deny or abstain, so an already-formed denial can't be reopened by a laxer policy. If approval is needed but unavailable or denied, the registry returns a rejection. Only after passing does the call reach the tool; tools/execute can inject timeout, retry and metrics uniformly; post-processing plugins inspect the result; then a read-only tools/result fires and agent-loop writes tool/result to the session log for the model. Bash, web search and subagents keep their own business logic; permission, timeout and result recording aren't reimplemented per tool.

The live test: one bug, three agents

The writer had a real app with a real annoyance: a card-grid page that lagged on initialization because each of ~10 API calls sat on 3 seconds. The same prompt went to three setups — "figure out why this is slow and fix it."

Comparison of Codex, DeepSeek V4 Flash and DeepSeek V4 Pro fixing the same bug

Codex finished in ~9 minutes, merged several endpoints into one detail endpoint, cut the round-trips, and burned ~2% of the weekly quota. The new endpoint still took ~1.3s — the closest to expectation, the highest completion, but with room left on single-endpoint latency.

V4 Flash + Harness took ~20 minutes, didn't merge anything, and instead optimized each existing endpoint down to under 800ms, for ¥0.82 — a different plan, but a real and measurable improvement.

V4 Pro + Harness was the surprise: two rounds, ~20 minutes, ¥1.33. The first round proposed merging and a detail endpoint but nothing visibly changed; the second round ran 20 more minutes and produced no usable result. The best model in the lineup did the worst job — the sort of outcome that makes you check whether the release you got was the one that was supposed to ship.

Verdict

DeepSeek Harness is genuinely interesting for its composable runtime: someday an agent might pick the right plugins for the task on its own. It's that same composability that makes it harder to run — pick the right plugins and config, handle their interdependencies, and read busy logs when something breaks (the runtime trajectory helps a lot there). And it's still developer preview, with explicit warnings that compatible-breaking changes are coming. Treat it as an evolving engineering sample: for teams designing agents it's a reference worth reading, but it's not the base to drop into production yet.

All articles →