DeepSeek Shipped a Harness That Doesn't Need DeepSeek
On August 13, 2026, DeepSeek open-sourced Harness — MIT licensed, developer preview, npx @deepseek-ai/dsh web. Within roughly two days it had crossed 95,000 GitHub stars.
Everyone immediately filed it as "the open-source Claude Code." That framing is wrong, and being wrong about it means missing the only part that actually matters.
Claude Code is a product. Harness is a runtime with no privileged center — including no privileged model. DeepSeek, a company whose entire business is selling tokens, shipped an agent framework in which DeepSeek's model is one swappable plugin among many, sitting at exactly the same level of privilege as Anthropic's, OpenAI's, Bedrock's, Vertex's, or whatever OpenAI-compatible endpoint you point it at.
No model vendor has done that before. Claude Code assumes Claude. Codex assumes GPT. Gemini CLI assumes Gemini. Every harness built by a model company so far has been, structurally, a funnel. This one isn't. That is the story.
The whole argument in one picture. Every harness a model company shipped before this one was, structurally, a funnel.
First: what a harness even is
DeepSeek's own framing is the cleanest I've read: the model is the soul of an agent; the harness is everything that lets that soul act in the world.
Concretely, the harness is the thing that connects the model to a filesystem, a shell, an editor, the web, and other agents — while recording what it did and constraining what it's allowed to do. Agent = Model + Harness. Nothing else.
This matters because for the past two years the industry has been benchmarking souls while quietly competing on bodies. The gap between "GPT-5 in a chat box" and "GPT-5 in Codex" is not a model gap. It's tool design, context management, permission policy, and loop discipline. Everyone who has actually shipped an agent knows this. Almost nobody was willing to say the harness was the product, because saying so devalues the model.
DeepSeek just said it out loud, and then gave the body away.
The architecture: everything is a plugin, and the plugin kernel is borrowed
Cordis, or: DeepSeek did not invent this
Harness is built on Cordis — a plugin meta-framework whose central capability is disposability: load, unload and reload a plugin with complete cleanup of its side effects. Plugins contribute services, typed events, and reversible effects into a shared context.
Here's the detail almost every English write-up skipped: Cordis is not new and not DeepSeek's. It's an independent open-source project by the developer Shigma, and it has been the plugin kernel of Koishi — a chatbot framework — since 2019, where it accumulated on the order of 4,000 community plugins. DeepSeek vendored it in and co-published a paper formalizing the design.
I find this the single most instructive engineering decision in the whole release. DeepSeek looked at "build an agent runtime" and correctly identified it as a plugin lifecycle and dependency problem — a problem the Chinese bot-framework community had already spent six years and four thousand plugins beating into shape. So they took the solved part. Meanwhile a dozen VC-funded agent startups are writing their own plugin loaders from scratch this quarter.
Taste in what not to build is a real engineering skill, and it is scarcer than the ability to build.
The layers
dsh-base— model adapters, tools, persistencedsh-web-app— the browser UIdsh-headless— CLI-only
Composition is layered config, applied top to bottom over an empty entry list: the dsh-base bundle's cordis.patch.yml (78 rows) goes down first, then the mode bundle, then the profile layer at profiles/<name>/cordis.patch.yml, then $DSH_HOME/cordis.patch.yml, then any --patch overlay for this run only. Last write wins. Rows are id-targeted and replace the whole config — there is no deep merge, so patching a row means restating what you want to keep. Then boot() mounts the Loader and the live tree comes up: 219 dsh-* packages.
The core capabilities hang off the context — ctx.sessions, ctx.agents, ctx.tools, ctx.systemPrompt, ctx.llm, ctx.shell, ctx.sandbox, ctx.approval. Read that list again: the agent loop itself is a plugin. You can replace the thing that decides what happens next without forking the project.
Bundles and patch layers compose the runtime, Cordis mounts them, and every capability shows up as a service on the context. Note the bottom row: DeepSeek's own model sits in the same slot as everyone else's.
The same pipeline in detail. dsh --dump-config and boot() run the same applyEntryPatches, which is why the printout cannot drift from what actually mounts.
Three details in that pipeline are better than they need to be.
Row order carries no load semantics. A row activates once the services it injects exist, not when the file happens to list it. You are declaring a set, not writing an init sequence — which is roughly the difference between a plugin system and a startup script with extra steps.
dsh --dump-config runs the same applyEntryPatches as boot. So the effective-config printout cannot drift from what actually mounts. Most config systems ship a "show me the resolved config" command that is a second implementation, and therefore a second set of bugs, and therefore a thing experienced users learn not to trust.
watchUserPatches recomposes the user layer live, and a bad edit keeps the last good tree. You can re-compose a running agent, and a typo doesn't take the session down with it.
A seam is three roles, not one
This is the part I would steal outright. In Harness a capability is not "a plugin." It is three separate roles, usually in three separate packages:
- Service Definition — an abstract class published on a context key.
ctx.shellisabstract class ShellExecutor, plus the semantics every implementation must honor. - Service Provider — a swappable implementation:
dsh-bash-local,dsh-bash-sandbox,dsh-pwsh-localon win32,dsh-fs-sandbox, an E2B remote sandbox. - Consumer — the model-facing tool that injects the definition:
dsh-tool-bash,dsh-tool-fs,dsh-tool-terminal.
Roles may share a package, but a single role is not a seam. Solid arrows inject the definition; hollow arrows implement it.
The payoff is what one swap buys. ctx.shell and ctx.fs both land on ctx.subprocess — point ctx.subprocess and ctx.fs at a remote sandbox and Bash, the PTY and the LSP move with them. No provider forks, no per-tool "remote mode" flag threaded through twelve call sites.
That only works because of one rule: extension plugins depend on the Definition, never on a concrete Provider. This is precisely where architectures like this normally collapse — somebody imports bash-local directly because it was faster that Tuesday, and eighteen months later "swappable" is a word that appears only in the README.
Harness enforces it mechanically rather than socially: docs/module-graph.md is generated from source and freshness-gated in CI. An architecture rule that no machine checks is decoration.
One directory per capability, one package per role — because the roles evolve apart.
The contract also lives on the Definition instead of in each provider's good intentions: run rejects only for infrastructure failures, while nonzero exits, timeout kills and abort kills all resolve. That single sentence is why a tool written against ctx.shell behaves identically in a local process tree and in a remote sandbox.
One trap before you try it: a single-implementation seam holds exactly one provider, so you disable the old row before inserting the new one. The other order throws.
Step vs turn
A step is one model request plus its tool calls. A turn is zero or more steps, opening before the first input and closing only when the obligations of that input are satisfied.
That distinction sounds pedantic until you've tried to bill, cancel, retry, or audit an agent. "One user message" and "one model call" are not the same unit, and almost every homegrown agent conflates them — which is why cancellation is broken in most of them, and why cost attribution is a mess.
Every amber point is a waterfall. Everything either side of the loop lands in the append-only log, and deriveMessages() builds the next step's history back out of it.
The hooks in that diagram are the actual extension surface, and their semantics are unusually precise for a v0.1. agent/pre-step can reject the turn or enter it with modified messages. agent/request and llm/stream are where an adapter swap, a retry, or a compaction pass slots in. tools/pre-execute returns allow / deny / ask; tools/execute wraps around the call for timeout, retry and metrics; tools/post-execute can accept, replace or block the result. tool/result is a frozen snapshot — observe only. agent/turn-stopping is serial, with no next().
That word waterfall is the whole interception model: call next() to delegate down the chain, return without it to short-circuit. It means "add a retry policy" and "redact secrets from tool output" are listener registrations, not forks.
A small thing I like more than I expected: a rejected — or empty — first claim still closes a durable turn that spent no step. Zero-step turns exist in the log. Nothing silently disappears because it happened to do no work.
The session log is the actual product
Harness keeps an append-only event log — turn/start, step/start, user/message, assistant/*, tool/*, step/end, turn/end — governed by one rule:
Model-visible means logged. Anything that reaches the model must be reconstructable from the log.
Model history is then derived from the log — deriveMessages() builds the next step's input — rather than being some mutable array that a dozen code paths poke at. And model-visible ⟺ logged is not a line in the docs; it is a runtime assertion.
I'd argue this, not the plugin system, is the most important design decision in the repo. It turns context engineering from vibes into a data structure. Every context injection has a recorded source. Trajectories become replayable. resume, fork, search and replay all become operations on one event stream instead of four bespoke features.
If you've ever debugged an agent that went insane on step 14 and had no idea what was actually in its context window at step 13 — this is the fix, and it's the part worth stealing even if you never run dsh.
Sandbox and approval are two different things
ctx.sandbox confines spawned processes. ctx.approval enforces policy. Policy attaches by listening on fs/*, tools/*, telemetry/* capability events — without importing the loop.
Most agent frameworks collapse these into one "are you sure?" prompt. They are not the same axis. What the process can physically touch and what the human has agreed to are orthogonal, and conflating them is how you end up with a tool that is simultaneously annoying and unsafe.
Credit where due, and a caveat: the filesystem sandbox does not currently govern network or process visibility. That's a real hole, and DeepSeek says so in their own docs.
The whole plugin protocol is four exports
For all the layering above, the thing you actually write is small:
export const nameexport const inject— the services you needexport const Config— schemastery-validatedexport function apply(ctx, config)
Plus one declare module '@deepseek-ai/cordis' merge that registers your context key and your events — a single declaration serving both the type face and the runtime.
Four exports and one declaration merge. No base class to extend, no registry to sign up with.
package.json lists cordis as a peer dependency, which is what stops 219 packages from each dragging in their own copy of the kernel.
The lifecycle discipline is the other half. Every contribution goes through ctx.effect() or ctx.on() and returns a disposer, so unload unwinds it. That reversibility is what the entire "swap anything at runtime" story rests on — and it is the part most plugin systems skip, which is why most plugin systems can load but not unload.
The rest of the file set is boringly consistent across all 219 packages: src/types.ts as the one home for pure types, an optional src/invariant.ts companion plugin, src/client.ts for the browser half, tests split into unit / loader-composition / invariant, and a bilingual README.md + README.zh.md pair that CI gate-checks. Somebody was thinking about year two of this project, not just launch week.
The usage patterns that are genuinely new
1. Run Claude Code and Codex as subagents
Harness explicitly supports calling other coding agents as child agents. So the shape you can build is:
DeepSeek Harness orchestrates. Cheap fast model drives the loop. Claude Code gets handed the gnarly refactor. Codex gets handed the thing Codex is weirdly good at. Results come back into one session log.
Routing by subtask, with three vendors' agents in the same run — and one event stream underneath all of them, which is what makes fork, replay and per-source context auditing possible at all.
This is the first mainstream harness that treats rival harnesses as tools. Strategically it is close to insolent, and practically it is the correct architecture: model quality per dollar varies wildly by subtask, and nothing about routing should be decided at signup time.
2. Fork the trajectory instead of restarting the chat
Because everything is one append-only stream, you can fork a run at step 9 and try three different continuations. This is git branch for agent behavior — and it quietly makes prompt and tool changes testable: replay the same trajectory against a modified tool schema and diff what happens.
The industry has been shipping agents with no regression suite. This gives you the substrate for one.
3. Audit context by source
The Trajectory view lets you inspect every record by where it came from: system prompt, reasoning, tool result, subagent scheduling, injected context. When your agent degrades over a long session, the cause is almost always something put into the window, not the model getting dumber. Being able to point at the offending injection is the difference between engineering and superstition.
4. Headless in CI, Web UI from your phone
dsh-headless makes the same composition run as a CI worker; the web app makes it something you can drive remotely. Same session log underneath. The "agent as a long-running background process I check on" pattern gets much cheaper here than in a terminal-first tool.
5. Write your own loop
The strongest reason to care. If your domain needs deterministic orchestration — this step, then that step, always, with the model filling gaps rather than choosing the path — you don't fight the framework, you replace the loop plugin.
That is the real philosophical split with Claude Code: Claude Code gives the model autonomy; Harness gives the developer determinism. Both are legitimate. They are not the same product, and pretending they compete head-on is how you pick the wrong one.
6. Escalate from a config edit to a plugin without forking anything
The adoption path is explicit, which is rarer than it should be.
Four entry points, one verification step — and none of them edits the agent loop.
Start with dsh --dump-config to see the tree you are about to change. Just a config value? Patch the row. Swapping an implementation? The shape depends on the seam: single-implementation seams need disable-then-insert, while registry seams — ctx.llm, ctx.tools, ctx.subagents — hold many, so you simply insert. Writing it yourself? Four exports attached to a documented point. dsh plugin add <pkg> forwards pnpm into the profile directory; if the package declares dsh.bundle it becomes a profile layer, otherwise it is a plain dependency you insert yourself. And cordis_define / cordis_run mount something for the current session that is gone on restart.
The line at the bottom of that diagram is the one that matters: no path here edits agent-loop. A framework whose documented extension story is "fork the core and rebase forever" is not extensible. It is merely open source.
What it actually means
The bundle everyone accepted is being broken up
For two years, adopting a model meant adopting a harness, and adopting a harness meant adopting a company. Your prompts, your tool definitions, your permission policy, your session history — all of it lived inside somebody's product.
Notice where the switching cost really was. It was never the model; models swap behind an API in an afternoon. The lock-in was always the body, never the soul. Harness is the first serious attempt by a model vendor to make the body portable, and once portability is the default expectation, every closed harness has to justify itself on quality rather than gravity.
DeepSeek is commoditizing its complement — one layer up
This is the same move as open-weights, played at a different altitude. If harnesses are free and interchangeable, competition moves back to price-per-token and capability-per-token — the axis where DeepSeek has spent its whole existence winning.
And the timing is the tell. In the same window as this "free and open" release, DeepSeek raised V4-Pro API pricing sharply — peak-hour output went from roughly $0.87 to $3.96 per million tokens. Free harness, more expensive tokens, same week.
That is not hypocrisy, it's a thesis: margin lives in inference; the harness is distribution. Anyone reading the stars as pure altruism is reading the wrong artifact.
There's a real cost to the choice, too. A locally-run MIT harness sends DeepSeek no trajectory data — and agent trajectories are among the most valuable training and eval assets in existence right now. DeepSeek traded visibility for default position. If you believe agentic RL is where the next capability jump comes from, that trade is either brave or expensive, and we won't know which for a year.
If you're building on top: don't build a harness
The clearest practical takeaway. Harness infrastructure is now a commodity with a credible free implementation, a plugin kernel with six years of production history, and a vendor incentive to keep it free forever.
Your durable layer is the plugins: domain tools, evaluated skills, permission policy that matches how your organization actually works, and the trajectory data your own users generate. That's the Cinema Studio lesson again in a different costume — own the part that survives the model swap, not the part that gets commoditized by the next release.
And now the cold water
95,000 stars in two days measures agreement with an idea, not adoption of a codebase. It's a vote about the model-agnostic thesis, cast by people who mostly haven't run it.
The honest status: developer preview, explicitly unstable APIs, breaking changes promised, hot-swap not actually implemented yet, a plugin ecosystem that is currently mostly first-party, no published performance benchmarks, documentation gaps around circular plugin dependencies and multi-agent context sharing, and the sandbox limitation noted above.
The correct posture is a contained pilot, not a production control plane. Put it on internal tooling, run it against a repo you don't mind breaking, and keep it away from customer data and production credentials until the API stops moving.
The line worth keeping
The model is the soul. The harness is the body. For two years we argued about souls while quietly paying rent on bodies.
DeepSeek just made a body that runs any soul, gave it away under MIT, and raised the price of its own soul in the same breath. Whether or not dsh is the tool you end up using, the assumption it broke — that your agent runtime and your model provider must be the same company — is not going back in the box.
Sources and further reading: DeepSeek Harness official page · Architecture reference · The New Stack · VentureBeat · Cordis explained · Production-readiness review