All writing
Deep dive14 min read

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:

bash
npx @deepseek-ai/dsh web

If 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:

bash
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install
pnpm run build
pnpm dsh web

Pick 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:

APPLIED IN ORDERdsh-basemodel adapters · tools · persistence · sandbox · settings · credentials · telemetrydsh-web-app / dsh-headlessbrowser UI, or the one-shot runner with no serverprofile patchthe named composition you bootedhome-level patchyour machine-wide overrides in the Harness homeCLI overlaysflags for this run only
Boot composition: dsh-base lays the foundation, a frontend layer goes on top, then progressively more local patches. Later layers target rows by ID and either replace a config entirely or insert new entries.

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:

bash
dsh --profile web --dump-config

Run 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:

PackageServiceWhat it owns
core/sessionctx.sessionsAppend-only event log for everything that happened
core/system-promptctx.systemPromptPrompt assembly
core/toolsctx.toolsScoped tool registry
core/agentctx.agentsThe Agent interface
core/agent-loopctx.agentLoopThe default driver — replaceable
llm/llmctx.llmModel 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:

turn/startclaim inputassemble promptsagent/pre-stepstep/startmodel requesttool executionstep/endagent/turn-stoppingturn/end

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 & subprocessShared execution environment; repoint it and Bash, PTY and LSP follow
LLM adaptersWhich model provider answers a request
Shell backendsLocal spawn or remote execution
TerminalsPersistent PTY sessions
SandboxProcess confinement policy
SubagentsAnything 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:

typescript
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:

typescript
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:

typescript
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:

typescript
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:

typescript
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.

PI — FIXED CORE, OPEN PERIMETERDSH — NO PRIVILEGED COREread · writeedit · bashextensionskillpackagemodeltoolsfilesystemshellsandboxsessionssubagentagent loopsame shape, same weight — even the loop
Pi protects a small core and opens the perimeter. dsh refuses to designate a core — every slot in the tree is the same kind of thing, and every one of them is swappable.

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

PiDeepSeek Harness
Core thesisCut until nothing is left to cutStructure the complexity, then make every piece removable
Default toolsread, write, edit, bashA full default set, itself a plugin
What is fixedA thin core the model talks toNothing — including the agent loop
Extension unitTypeScript extensions, skills, packagesCordis plugins mounted as config rows
Who extends itYou, ahead of time, explicitlyYou — or the agent, at runtime
Failure modeYou rebuild what you cutYou drown in composition surface
MaturityStable, proven, derivatives shippingDeveloper 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.

Comparing agent harnesses on more than architecture? We rate AI developer tools on multi-dimensional, community-scored criteria.

Browse coding tools