DeepSeek Harness:
Everything Is a Plugin
Install dsh in two minutes, learn to read its plugin tree, write your first plugin — and see why its architecture bets in the exact opposite direction from Pi.
On August 13, 2026, DeepSeek open-sourced something that is not a model and not a benchmark: the scaffolding that turns a model into an agent. It is called dsh, it is MIT licensed, and its entire thesis fits on the repo tagline — Everything is a Plugin.
That reads like marketing. It is not. In dsh, the model adapter is a plugin. The tool registry is a plugin. The session log is a plugin. The agent loop — the while-loop that decides when to call the model again and when to stop — is a plugin. The official coding agent is not the product; it is one default composition of these plugins, and you can take it apart.
This piece does two things. First, a working tutorial: get it running, understand what actually booted, write a plugin. Second, the part I find more interesting — a comparison with Pi, the other harness people are talking about, which arrives at the opposite answer to nearly every question dsh asks.
Part one — Tutorial
Run it in two minutes
The fastest path needs nothing but Node. This pulls the package and boots the web UI on http://127.0.0.1:3080:
npx @deepseek-ai/dsh webIf you want to read the source while you use it — which, for a harness whose whole point is that you will modify it, you probably do — build from the repo instead:
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh webPick a runtime mode
dsh ships several presets, and they are worth knowing before you start tuning anything, because each one is just a different plugin composition:
- Standard — the full toolset: file editing, shell, search. The default coding agent.
- Code — the model writes TypeScript to orchestrate multi-step work instead of emitting one tool call at a time.
- Minimal — bash and an editor, nothing else. Built for benchmarking, and the honest way to measure how much the harness is really contributing.
- Creator — for authoring your own presets, with runtime inspection of what you composed.
That Minimal mode is a quiet act of confidence. Most harnesses do not ship a switch that lets you subtract the harness.
Part two — The mental model
A plugin tree, not a program
Underneath dsh sits Cordis, a plugin framework the project vendors in. The one sentence worth memorising: plugins contribute services, typed events, and reversible effects to a shared context. Reversible is the load-bearing word — when a plugin unloads, everything it registered unwinds with it. That is what makes “swap the agent loop” a real operation rather than a slogan.
A running dsh is not a program with a main function. It is a tree composed at boot from ordered layers, each patching the one before it:
Two nouns organise this. A bundle distributes Cordis config rows plus the code they point at. A profile is a named composition stored in the Harness home — it lists a bundle stack and holds your patches. Both are declared in package.json under a dsh field: dsh.profile lists a profile’s bundle stack, dsh.bundle points at the bundle’s patch file.
The single most useful command while you are learning the system prints the composed result of all of that:
dsh --profile web --dump-configRun it before and after any change you make. It is the difference between guessing and knowing.
The services you will actually touch
Core packages register services on the shared context. These are the handles your plugin code reaches for:
| Package | Service | What it owns |
|---|---|---|
| core/session | ctx.sessions | Append-only event log for everything that happened |
| core/system-prompt | ctx.systemPrompt | Prompt assembly |
| core/tools | ctx.tools | Scoped tool registry |
| core/agent | ctx.agents | The Agent interface |
| core/agent-loop | ctx.agentLoop | The default driver — replaceable |
| llm/llm | ctx.llm | Model adapter seam |
Extension happens through three categories of event. Session events are durable and logged. Agent events (agent/*) give you a live registry and let you observe each step. Capability events let you supply policy and adapters without importing the loop at all — which is how you change behaviour without taking a dependency on the thing you are changing.
One turn, step by step
A turn spans zero or more steps; a step is one model request plus the tool calls it produces. Knowing where the hooks sit tells you where to intervene:
Terracotta entries are event names you can subscribe to. Grey entries are the work that happens between them.
Capability seams
The architecture’s central idea is the capability seam: a swappable interface with three roles — a Service Definition declaring the interface, a Service Provider implementing it, and a Consumer using it. Consumers never know which provider they got.
| Filesystem & subprocess | Shared execution environment; repoint it and Bash, PTY and LSP follow |
| LLM adapters | Which model provider answers a request |
| Shell backends | Local spawn or remote execution |
| Terminals | Persistent PTY sessions |
| Sandbox | Process confinement policy |
| Subagents | Anything from a child agent to a delegated turn |
Part three — Tutorial
Write your first plugin
A plugin is a TypeScript module exporting an apply function. The framework calls it with a context object, and you register capabilities on that context. The smallest possible version:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello-plugin'
export function apply(ctx: Context) {
console.log('[hello-plugin] plugin loaded!')
}If your plugin consumes a service, declare it in an inject array. The framework waits until those services exist before loading you — which is how a tree with no fixed boot order still resolves deterministically:
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(/* ... */)
}There are three equivalent shapes. Object form, when you want the metadata inline:
import type { Context } from '@deepseek-ai/cordis'
export default {
name: 'my-plugin',
inject: ['tools'],
apply(ctx: Context) {
// ...
},
}And class form, when your plugin is a service other plugins will inject:
import { Service, type Context } from '@deepseek-ai/cordis'
export default class MyService extends Service {
static inject = ['tools']
constructor(ctx: Context) {
super(ctx, 'myService')
}
}Clean up after yourself
This is the part that makes hot-swapping real. Anything with a lifetime goes inside ctx.effect(), and you return the teardown. When the plugin unloads, the returned function runs:
export function apply(ctx: Context) {
ctx.effect(() => {
const timer = setInterval(() => {
console.log('heartbeat')
}, 5000)
return () => clearInterval(timer)
})
}Mount it by adding a config row — there is no core to patch, so you extend dsh by placing your plugin beside the others rather than inside anything. If you publish it, tag the repo with the dsh-plugin topic so it shows up for everyone else.
Part four — Analysis
Two harnesses, two opposite bets
Now the interesting part. Put dsh next to Pi — Mario Zechner’s minimal terminal harness — and you get two architectures that disagree about almost everything, which makes each one legible in a way it is not on its own.
Subtraction versus structured addition
Pi’s philosophy is subtraction. It was built out of frustration with tools that kept accreting features, and the response was to cut the core down to four tools: read, write, edit, bash. The argument is that frontier models have been trained hard enough on agentic work that they already understand what a coding agent is — the model knows what bash is, it knows how files work. Bolting on a bespoke “search the codebase” tool spends system-prompt tokens without buying capability. So Pi keeps a thin core and pushes everything else — plan modes, subagent orchestration, MCP — out to TypeScript extensions you add back deliberately.
dsh does not do subtraction. It does addition, then shatters the result. Rather than distilling a thin core, it takes every layer of an agent system — model, tools, filesystem, shell, sandbox, session storage, subagents, and the agent loop itself — and makes each one replaceable. The official coding agent is one answer assembled from those pieces, and explicitly not the only one.
The granularity is a level deeper than Pi’s. Pi is fixed core, pluggable periphery. dsh has no irreplaceable core at all.
Who holds control?
Dig one level under the architecture and the two projects answer a different question: who should own control of the system?
Pi’s answer is the developer. Minimalism there is not aesthetic — it exists so you can see every step and change any of it. The trust is placed in the person writing the code, not in the agent at runtime. If a feature would make the core decide something on your behalf, Pi would rather not ship it.
dsh hands control progressively to the runtime. When even the scheduling logic of the agent loop can be replaced, the system is declaring that it has no opinion about how it should run. Every rule is redefinable while it is running.
These are two genuinely different attitudes toward complexity. Pi treats complexity as a burden to be cut away. dsh treats it as something to be structured and packed into plugins — because complexity you have deleted is complexity nothing new can grow out of.
The real gap: self-evolution
Here is where the distance actually opens up. dsh can already have an agent inspect the edge of its own capabilities at runtime, write a plugin on the spot, mount it, and call that freshly acquired capability in the same session. Give it a task, and it can add a plugin to solve that task and then shed it again. The reversible-effects design is precisely what makes shedding safe.
The caveats are real and worth stating plainly. This is experimental. Dynamic plugins live in memory, so a restart forgets them, and there is no automatic path from “the agent invented this” to “this is now a permanent plugin.” But the direction is open.
Pi’s extension mechanism is, by comparison, static: a human writes a TypeScript extension and installs it explicitly. The agent does not notice a capability gap mid-task, build a tool, and start using it. Which lands right back on the philosophical split — Pi leaves “who extends the system” to people; dsh is trying to give that job back to the agent.
Side by side
| Pi | DeepSeek Harness | |
|---|---|---|
| Core thesis | Cut until nothing is left to cut | Structure the complexity, then make every piece removable |
| Default tools | read, write, edit, bash | A full default set, itself a plugin |
| What is fixed | A thin core the model talks to | Nothing — including the agent loop |
| Extension unit | TypeScript extensions, skills, packages | Cordis plugins mounted as config rows |
| Who extends it | You, ahead of time, explicitly | You — or the agent, at runtime |
| Failure mode | You rebuild what you cut | You drown in composition surface |
| Maturity | Stable, proven, derivatives shipping | Developer preview, interfaces still moving |
Where the ceiling is
If I had to lean one way on long-run potential, I lean toward dsh — with the caveat that this is a bet on a direction, not a verdict on today’s software.
Pi has already proven its thesis. Minimal-and-extensible works; it has spawned derivatives that took off fast, and third-party evaluations have reported it sending dramatically less context per turn than heavier harnesses, which shows up directly as cost. But its ceiling is legible by design. The core philosophy is to manage as little as possible and leave extension to users and the community. That is a deliberate limit, not an oversight.
dsh is earlier, its interfaces are still moving, its plugin ecosystem is barely seeded, and its functional-programming idiom is a genuine on-ramp cost. What it has done is make “an agent can grow new capabilities itself” a first-class architectural property rather than an effect stitched together from outside tools. If that line pays off, the space it opens is an order of magnitude larger than a lean extensible harness — because the bet is not better building blocks. It is blocks that evolve on their own.
What to do with this
If you want to try the idea rather than read about it, the shortest useful path is: boot dsh web, run --dump-config to see what actually composed, then write the heartbeat plugin from Part three and watch it unwind cleanly when you unload it. That loop — compose, inspect, mount, unwind — is the whole system in miniature. Everything else is scale.
Sources
Comparing agent harnesses on more than architecture? We rate AI developer tools on multi-dimensional, community-scored criteria.
Browse coding tools