Independent field guide · Evidence-marked

DSH Field Guide

A working reference for DeepSeek Harness: what it is, how its composition model fits together, how to write a plugin for it, and what its sandbox does and does not protect. Every claim here is marked with how it was checked: run against the shipped binary, read from source, or taken from an external source. Where a widely repeated claim turned out to be wrong, the correction is shown with its citation.

17
Run
Executed against the shipped binary
17
Source
Read from the repository, not run
11
External
Outside the project
Strongest evidenceWeakest
45 claims, sized by how many carry each mark. Six commonly repeated errors are corrected in section 13.
Repository commit
b150a55
Commit date
2026-08-21
Checkout version
0.1.1-rc.2
Binary exercised
dsh 0.1.0-rc.7
Compiled
2026-08-24
00 - Method

How to read this guide

DeepSeek Harness is a pre-release project (version 0.1.1-rc.2, breaking changes expected) that moves fast enough for eight release candidates to ship in a week. Two of them landed on the same day. Any document about it is a photograph, not a map, so this one states its shutter speed: it describes commit b150a55 of 2026-08-21, and every factual claim carries a badge saying how it was established.

Run

Verified by executing the published dsh binary or inspecting the installed npm artifact. The strongest evidence here.

Source

Read from the repository at b150a55 but not executed. Reliable for structure; weaker for runtime behaviour.

External

From outside the project - public registries, published commentary, other vendors' documentation.

Why this matters

Source-reading is weakest precisely where behaviour is concerned. A file can describe an intent that the running system does not honour. Where a claim in this guide is behavioural and carries only a Source badge, treat it as well-founded but unconfirmed, and check it before depending on it.

Contents

01 - Context

What a harness is, briefly

A model produces tokens. Everything else that makes an agent useful is the harness: the tools it may call, the loop that decides when to stop, what goes into the context window and what gets evicted, which actions need approval, where the conversation is persisted, and what the process may touch on disk. The model is the engine; the harness is the rest of the car.

Three corrections to the usual framing are worth carrying into the rest of this guide. External

This guide does not attempt a general introduction to harness engineering; that ground is well covered elsewhere, notably by walkinglabs/learn-harness-engineering. What follows is specific to DeepSeek Harness.

02 - Orientation

What DeepSeek Harness is

DeepSeek Harness (dsh on the command line) is an MIT-licensed, TypeScript agent harness published by DeepSeek. It is built on a vendored fork of Cordis, a dependency-injection and plugin framework, and its organising idea is that the harness should be assembled from plugin layers rather than configured within a fixed application. Source

You launch it as a profile: an ordered stack of plugin bundles under your own overrides. The documented entry point is npx @deepseek-ai/dsh web, which serves a local web UI on port 3080. There is also a headless profile for one-shot tasks, an ACP server for automation, and a JSON-RPC SDK. It is web-first in a field that is mostly terminal-first. Run

Scale, stated accurately

The repository contains 227 packages, all named @deepseek-ai/dsh-*. That number is widely quoted and it is nearly useless, because no deployment loads 227 packages. The number that describes what actually runs comes from asking the binary: Run

ProfileComposed rowsDisabledActive plugins
web12925104
headless81279
dsh-base alone78177

So a real web session boots 104 active plugins composed from 129 rows across two bundles. Of the 128 distinct packages in that tree, only two come from Cordis itself; the rest are harness packages. That is the honest measure of how much of this system is assembled rather than hard-wired.

The caveat that matters most

There are no published harness-level benchmarks. The repository's BENCHMARK.md is 231 bytes and three lines long; it explains how to run the Python SDK against an example and contains no numbers at all. A question in the public launch thread asking for harness-level rather than model-level benchmarks went unanswered. Run External

A harness marketed on the strength of its architecture has, as of this commit, published no evidence that the architecture improves task outcomes. That does not make the architecture bad. Much of it is well built. But it should temper any claim that it is better rather than different.

Open source, but not open governance

CONTRIBUTING.md is unusually direct, and reading it changes how you should relate to the project: Source

The result is a project that is source-available and freely licensed without being community-governed. That is a coherent position and the project states it plainly, but it is a different thing from the "open source" most readers will assume, and worth knowing before you build a business on it.

03 - The central claim

"Everything is a plugin", qualified

This is the project's signature line and the thing most repeated about it. It is an accurate description of the architecture's intent and a false literal statement, and the source says so. Source

There is a kernel. It consists of Cordis's Context, the root Fiber, and the built-in Reflect, Registry, Events and Logger services, plus host bootstrap. Bootstrap then mounts the Loader as a plugin, and from that point the generalisation holds - the model adapter, the tool registry, the session log and the agent loop are all plugins. But some things are deliberately not: Session, for instance, is a plain class rather than a service.

The precise version

Nearly every capability above a small kernel is contributed through one common plugin lifecycle, and no capability above that kernel is privileged over a third-party replacement. That is a strong claim and it is substantially true. "Everything is a plugin" is shorthand for it.

Is the idea novel?

Deep plugin architectures are old: OSGi Declarative Services dates to 1999, and Eclipse extension points, Emacs, VS Code, Spring and Guice all long predate this. Dependency-injected service registries are not new, and neither is around-middleware. The Cordis paper concedes much of this itself, noting that iPOJO's Gravity "directly prefigures Cordis's ctx.provide/ctx.get pattern" - the authors' own word. External

What is claimed as novel is narrower and more defensible. The first is composition of inverses, where you write an inverse only for each atomic effect and the inverse of any composite is derived. The second is asynchronous teardown, which OSGi's synchronous deactivation callback cannot express. The contested question is whether that justifies calling it a programming paradigm.

The sharpest public critique comes from a direct peer, the author of a competing harness, who granted that per-registration cleanup handlers are nice but doubted the rest: the cross-plugin dependency injection "comes with a lot of footguns... most plugins do not have dependencies on each other, so this more complex system doesn't win you much." A substantive Chinese-language review reached a similar verdict: the architecture has novelty, the orchestration paradigm has no breakthrough. It also observed that "everything is swappable" does not automatically yield better task success rates. External

Evidence from the repository itself

The Cordis paper argues that correctness becomes a structural property of the paradigm rather than a matter of developer discipline. The repository's own vendoring log complicates that. Shipping required 18 documented local patches to the framework, and the log is exhaustive by policy. Item 6 "locally closes three reentrant disposal gaps" in cordis/src/fiber.ts. Item 12 describes fixing "a deadlock that exited 13 with no diagnostic." Run

That log is admirable engineering and admirable honesty. It is also evidence that these guarantees took a great deal of developer discipline to obtain.

Where the genuine novelty probably is

Not in pluggability, which is old, but in the agent modifying its own runtime. The cordis preset exposes model-facing tools that let the agent define and mount a plugin into the process it is currently running in. Every other harness lets you extend it; this one lets the agent extend itself mid-session. Reversible-effect machinery is over-engineering for a static plugin set, and becomes load-bearing the moment something is mutating the runtime while it runs. This paragraph is inference, not a claim from the source.

04 - The core distinction

Bundle, Profile, Preset

These three words are the most common source of confusion about dsh, and nearly every secondary explanation of the project conflates at least two of them. Each is documented upstream; what is not documented anywhere is the contrast, which is the reader's actual problem. Run

ConceptWhat it isWho owns itWhat ships
Bundle An installable npm package that contributes one patch layer, declared by dsh.bundle.patch in its package.json The plugin author - this is what you publish dsh-base, dsh-web-app, dsh-headless, plus the Codex and Claude Code subagent bundles
Profile A directory at $DSH_HOME/profiles/<name> holding an ordered bundle list plus your own patch layer. Booted with --profile The user, maintained via dsh plugin web (base + web-app), headless (base + headless)
Agent preset A per-session composition mounted under one agent's scope - the thing the UI calls a "mode" The deployment, or you, by copying one standard, code, minimal, cordis
The rule

A bundle is what an external author distributes. A profile is what a user assembles and boots. An agent preset is what one session sees. Nothing is both. A package without dsh.bundle still installs, but contributes no layer and does nothing.

Here is a real profile manifest, generated by the tool rather than transcribed from documentation: Run

// $DSH_HOME/profiles/web/package.json
{
  "name": "dsh-profile-web",
  "private": true,
  "dependencies": {},
  "dsh": {
    "profile": {
      "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"]
    }
  }
}

Profiles are directories, and the shipped ones are created for you: asking dsh to dump its configuration was enough to bring both web and headless into existence on disk. An arbitrary new name is not auto-created. Until you run dsh plugin add against it, it resolves to nothing and composes zero rows.

05 - The distinction nobody explains

Host plane and agent plane

Once you have more than one session in a process, the question of where a plugin registers becomes the thing that breaks. The shipped code preset explains this better than the architecture documentation does, in a header comment on the file itself: Run

"This file is an agent-plane composition. It is mounted under one agent's scope context, so every tool and prompt section it registers belongs to that session alone. The host composition keeps everything a preset must not own: the registries themselves, the sandbox and approval stack, persistence, and the model route."

So the split is:

Host plane

Process-wide and shared. The registries, the sandbox, the approval stack, persistence, the model route. Providers live here. A bundle patch mounts here.

Agent plane

Per-session and private. Tools, prompt sections, persona. An agent preset mounts here, and native sessions run beside each other in the same process, each seeing its own catalog.

A real footgun, caught at mount

From the same file: "A service row here MUST sit inside a group carrying an isolate realm. Without one it publishes into the root realm, where it is process-global rather than per-session and the second session mounting this preset collides with the first; dsh-agent-presets rejects that at mount."

This is the single best "you will get this wrong" item in the product. It fails loudly rather than silently, which is the right design, but it will not be obvious why until you have read the sentence above.

The practical consequence for anyone building something real: a provider and its model-facing tool are two different roles that belong on two different planes. Mount the provider from a bundle patch on the host plane; mount the tool that consumes it from a preset on the agent plane. This is exactly how the shipped Codex subagent bundle is arranged.

06 - What ships

The four presets that ship

Exactly four agent presets ship, discovered from directory names rather than declared in an enumeration. The table below was read from the installed npm package - the artifact users run. A recursive search of that tree for orchestrator returns nothing. Run

IDName in the UIOrderWhat it is
standard标准模式1Full coding agent: file editing, shell, file and web search, skills, plan, goals, subagents, workflows. The default.
codePTC 模式2All of standard, with tools presented through the Code Mode SDK so the model composes multi-step operations in one TypeScript program.
minimal极简模式3A two-tool agent: persistent bash and str_replace_editor only.
cordis创造模式4All of standard, plus runtime inspection, plugin experimentation, and preset-authoring guidance.
Two things worth noticing

The UI names ship in Chinese only, including in the English package. An English-speaking reader looking for "Code mode" in the interface will find PTC 模式 instead. The mode usually described as "creator" has the ID cordis and displays as 创造模式.

Upstream documentation is bilingual throughout, and a substantial part of this community is Chinese-speaking. An English-only guide reaches a fraction of it.

What Code Mode actually does

This is the most misdescribed feature in the project, so here is the shipped file's own header comment, verbatim: Run

"Everything in standard is here unchanged. What is added is the tool-presentation row: instead of one tool call per action, the model writes a TypeScript program against a generated SDK and run_code executes it, so a sequence that would be five round trips becomes one... what this preset owns is the PRESENTATION of that registry for this agent alone."

Code Mode is a tool-presentation change that collapses round trips. The program is an ephemeral call executed in a fresh worker with no cross-run state. It is not a durable artifact, and not a conversion of your session into a standalone program.

07 - Assembly

Composition and layer order

A profile composes over an empty root in a fixed order. Each layer is a list of patch operations against rows identified by id: Source

  1. each bundle's patch, in dsh.profile.bundles order
  2. then the profile's own cordis.patch.yml
  3. then $DSH_HOME/cordis.patch.yml
  4. then any --patch overlays, in the order given
The behaviour that will surprise you

Later layers win per row, and a patch replaces a row's entire config. There is no deep merge. If you patch a row to change one field, you must restate every other field you want to keep. This is the composition footgun most likely to cost you an afternoon.

The best diagnostic in the product

--dump-config and --dump-default-config print the fully composed tree without booting it, annotated with which bundle contributed each row and which bundle patched it. No secondary explanation of this project mentions it, and it makes the layering model concrete in a way no diagram does: Run

$ dsh --profile web --dump-default-config

# == @deepseek-ai/dsh-base
- id: timer
  name: '@deepseek-ai/cordis-plugin-timer'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: hmr
  name: '@deepseek-ai/cordis-plugin-hmr'
  config:
    root: [.]
  disabled: true

It needs no API key and makes no model call, so it is the cheapest way to answer "what is actually loaded?" and the first thing to reach for when a plugin appears to do nothing.

08 - The seam most people want

Bringing your own model

Composition is abstract until you use it for something you actually want. For most readers that something is the model. A harness published by a model vendor invites one obvious suspicion: that the model connection is welded in. It is not. The verdict is more interesting than either "DeepSeek-only" or "run anything", and the gap between those two is where the practical detail lives.

The verdict in one line

The seam is genuine and the reach is wide, but breadth is configuration, not the shipped default. "Run any model" is marketing. "Run essentially every model that matters, after configuration" is accurate.

The seam is real

LlmAdapter is an abstract class in the LLM service, not a DeepSeek client with hooks bolted on. Its only required method is stream(). Adapters are registered with ctx.llm.registerAdapter(providers, adapter), and because registration is effect-owned, an adapter disappears when its plugin unloads, exactly like any other contribution described in section 03. Source

The service documents one obligation on adapter authors that is easy to miss: every provider HTTP request must include attributionHeaders(). The two shipped adapters satisfy it through completely different internals, one by direct fetch and one through a library header hook. Source

PackageWhat it isRoutes it registers
llm/llmThe service and the neutral LlmAdapter baseNone; it is the seam itself
llm/llm-deepseekFirst-party adapter, direct fetchdeepseek-official
llm/llm-pi-aiMulti-provider twin, delegates to pinned @earendil-works/pi-ai@0.82.1Zero, until settings supply profiles
llm/llm-retryOptional retry executorNot a provider
test-support/llm-replayDeterministic replay for testsNot a production network provider

How far the catalog actually reaches

The llm-pi-ai adapter delegates to a pinned third-party catalog. Rather than repeat a figure from someone else's summary, the count below was produced by calling getBuiltinProviders() against version 0.82.1, the exact version the repository pins. It returns 37 provider IDs: Run

amazon-bedrock, ant-ling, anthropic, azure-openai-responses, cerebras, cloudflare-ai-gateway, cloudflare-workers-ai, deepseek, fireworks, github-copilot, google, google-vertex, groq, huggingface, kimi-coding, minimax, minimax-cn, mistral, moonshotai, moonshotai-cn, nvidia, openai, openai-codex, opencode, opencode-go, openrouter, qwen-token-plan, qwen-token-plan-cn, together, vercel-ai-gateway, xai, xiaomi, xiaomi-token-plan-ams, xiaomi-token-plan-cn, xiaomi-token-plan-sgp, zai, zai-coding-cn

The Anthropic Messages, OpenAI Completions, and OpenAI Responses implementations behind these are real imported modules, not stubs. Source

Three limits worth knowing before you plan around this

1. The multi-provider adapter ships dormant

The base composition mounts llm-pi-ai with zero routes and no extra models in the picker, while separately setting the default to provider deepseek-official, model deepseek-v4-flash. Routes register live only once a llm-pi-ai: settings section supplies provider profiles, and they drop again when that section empties. The comment in the base patch states the division plainly: which adapters exist is composition, which providers run is the user's settings document. So the out-of-box live route is DeepSeek-only, and every one of those 37 providers is a configuration step away rather than a default. Source

2. A hand-declared endpoint reaches three protocols, not arbitrary wire formats

If you declare a route yourself instead of picking a catalog entry, you get exactly openai-completions, openai-responses, and anthropic-messages. This is deliberate and the source explains why: Bedrock signs with SigV4 over AWS credentials and a region, Vertex needs a project, a location and application-default credentials, Azure needs an api-version, and Codex authenticates through OAuth. None of that fits a configuration shape of key, endpoint and headers, so offering it would hand back a provider that cannot authenticate. Catalog routes still reach every protocol through their own provider; only an explicit override is refused. Source

3. No retry, caching, or rate limiting in the core service

Provider registration stores a retry policy, but llm/stream is a single-attempt call wrapper. Retry is a separate optional plugin, dsh-llm-retry, loaded by the shared example spine. Treat resilience as something you compose in, not something you inherit. Source

What this means in practice

If you already pay for a frontier model, the useful reading is that anthropic, openai, and github-copilot are catalog entries, so reaching them is a settings exercise rather than an engineering one. If you run open weights locally, Ollama and most self-hosted servers expose an OpenAI-compatible endpoint, which is precisely the openai-completions protocol a hand-declared route supports. The community has already built at least one adapter of its own, an OAuth-based GitHub Copilot provider, which is the clearest available evidence that the seam is usable from outside the repository. External

A protocol outside those three still needs a new adapter plugin. That is not a workaround; it is the intended extension path, and it is the same three-files-and-one-command exercise as section 09. An adapter is simply a plugin whose contribution happens to be a model route.

The limit of this section

Everything above is read from source or executed against the pinned catalog. No model was invoked through any of these routes. This section describes what the code supports and what the shipped composition selects. It says nothing about latency, output quality, or how well any given model drives this agent loop.

09 - Practice

Writing a plugin

An external plugin is three files and one command. No build step, no TypeScript toolchain, no bundler. The framework packages are peer dependencies supplied by the host at runtime, so plain JavaScript works. The sequence below was executed end to end. Run

1. package.json: the dsh.bundle key is what makes it installable

{
  "name": "dsh-hello-probe",
  "version": "0.1.0",
  "type": "module",
  "main": "index.js",
  "files": ["index.js", "cordis.patch.yml"],
  "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}

2. index.js: named exports only

export const name = 'hello-probe'

export function apply(ctx) {
  ctx.logger('hello-probe').info('external plugin loaded')
}

3. cordis.patch.yml: name must match the package name exactly

- insert:
    - id: hello-probe
      name: dsh-hello-probe

4. Install it

$ dsh plugin --profile probe add ./dsh-hello-probe
dsh: initialized profile probe at $DSH_HOME/profiles/probe
+ dsh-hello-probe
Done in 223ms
Undocumented, and useful to know

dsh plugin ... add does more than install a dependency: it automatically appends the package to dsh.profile.bundles. You do not hand-edit the manifest. Note the resulting order: the new bundle lands after dsh-base, so it patches base rather than being patched by it. Install order decides layer order.

Three traps

Never also default-export

A function plugin uses named exports: name, optional inject, optional Config, and apply. A service package instead default-exports its class. Mixing the two makes the loader silently discard namespace metadata such as inject, so your dependencies stop being awaited and you get a confusing undefined-service crash.

Provider availability is not model access

Registering a web, skill, subagent or workflow provider does not give the agent a tool. The matching consumer must also be in that agent's composition. A web provider needs dsh-tool-web, a skill provider needs dsh-tool-skill, and so on. This is the most likely reason a correctly written plugin appears to do nothing.

A bundle alone gives you no UI

A newly created profile composes base only. It boots cleanly and then sits there with no interface, because an app bundle (dsh-web-app or dsh-headless) is what provides a surface. Add one, or install into an existing profile.

Check your dependency pin

At the time of writing, the latest npm dist-tag for @deepseek-ai/dsh-tools points at 0.0.1-rc.1, the first release ever published, while the current line ships under the next tag. A plain npm install @deepseek-ai/dsh-tools therefore resolves to a stale package whose own peer dependencies are an entire generation behind. Pin explicitly, or install @next. External

Where you can attach

The documented extension seams, each consumed by declaring inject and calling a registration method that returns a disposer: Source

Two rules govern all of them. Every registration is an effect - it returns a disposer, and disposal must remove the contribution. Waterfall listeners must call next() to delegate; returning without it short-circuits the chain.

10 - Ecosystem

What has already been built

The most striking thing about this project is not its architecture. It is how fast people built on it. As of 2026-08-24, 11,258 GitHub repositories carry the dsh-plugin topic, on a harness still at 0.1.1-rc.2. That is a remarkable community response, and it is also a number that needs careful handling. External

The topic is a discovery signal, not a verified index

A two-stratum audit of 200 of those repositories (100 by stars, 100 drawn deterministically from the recently-updated window) found 81 native plugins and 22 verified multi-tool integrations. The rest either showed no plugin signal or could not be determined from a root manifest.

Some of that is ordinary topic drift rather than anything untoward: reactive-resume (41,636 stars, a resume builder), PicGo (27,028, an image uploader) and nocobase (23,811, a no-code platform) all carry the topic. And roughly a quarter were undetermined simply because the plugin sits in a monorepo subdirectory rather than at the root, so the true count is certainly higher than 81. Browse the topic to discover plugins; do not cite its count as a census.

Detail matters less than the shape, which is unambiguous: this is a real, active ecosystem with genuine traction. A sample of what has shipped: External

ProjectStarsWhat it does
nexu-io/open-design90,972Design workspace with native DSH support alongside Claude Code, Codex and Cursor
Nagi-ovo/voyager19,822Browser enhancement and prompt manager across DSH and other AI web UIs
DSH-better-sidebar2,792VS Code-style sidebar: files, terminal, Git, browser, chat, subagent panels
dsh-TUI2,434Terminal UI with live status, streaming reasoning, rollback, context metrics
dsh-market2,155In-product marketplace for browsing and installing plugins
Tencent/BrowserSkill1,294Logged-in browser control, with a nested native DSH plugin
dsh-context990Context composition, compaction, pruning and a usage dashboard
dsh-im752Bridges DSH to Feishu, WeChat, DingTalk, Slack, Telegram, Discord and more

Two entries deserve attention beyond their star counts. weijiafu14/pi2dsh is an ABI compatibility layer that runs another harness's plugins inside DSH - cross-harness plugin portability already exists in the wild. And lujianjun19/dsh-llm-github-copilot is a GitHub Copilot OAuth adapter for the LLM seam, which is a concrete demonstration that the model layer really is replaceable by a third party.

Where the gaps are

If you are deciding what to build, the useful output of that audit is the distribution rather than the total. Categorised across the sample:

Saturated - pick a different problem

Cost and token widgets (at least six independent implementations), themes and UI beautification (at least six), plugin marketplaces (three), memory layers (at least five). These are the obvious first ideas, which is exactly why they are crowded.

Contested but not closed

Vision tooling (two strong entries), context and compaction (one at 990 stars plus others), trajectory and run analysis (three). Room exists, but you would be competing on quality against something that already works.

Genuinely thin

Research workflows - the sample turned up one mathematical-research adaptation and one academic-writing auditor. Note the caveat in section 11 before building anything that fetches from the open web.

Empty

Enterprise and line-of-business. No ERP, CRM, Dynamics 365, Power Platform, SAP or Salesforce integration appeared anywhere in the sample. The nearest neighbours are a Chinese e-invoicing toolkit and an interview trainer. This is the largest unoccupied category found.

Method, for anyone wanting to repeat it

Stratum one was the top 100 by stars - what a browsing user actually sees. Stratum two took all ten available pages of sort=updated, deduplicated to 947 candidates, and selected 100 by sorting on a salted SHA-256 of the repository name, so the draw is reproducible rather than simply "the most recent". Each repository was tested for dsh.bundle or dsh.profile keys, a @deepseek-ai/* dependency, or a root Cordis manifest.

The two strata disagreed sharply: 12% native among the most-starred, 69% among the recently-updated. So no ecosystem-wide total is offered here. The 200-repository result is solid; anything extrapolated from it would not be.

11 - Risk

Security posture, honestly

The project documents its own limits unusually well, but does so scattered across a dozen package READMEs. Consolidated, the picture is this, and it is the section to read before letting anything autonomous loose inside a corporate network. Source

The one-sentence summary

The strongest boundary is OS-enforced file-write confinement for wrapped subprocesses. It is not network isolation, not credential isolation, not host-read isolation, and not plugin isolation.

BoundaryWhat it constrainsWhat it does not
Confined subprocesses Wraps exact subprocess arguments in an OS mechanism - bwrap then Landlock on Linux, Seatbelt on macOS, a restricted-token runner on Windows. On Linux, failure to obtain a usable mechanism fails closed. Same-host-kernel confinement, not a container or VM. danger-full-access bypasses it entirely.
Network Nothing. Network is not part of the policy vocabulary at all, and bwrap does not unshare the network. Any confined process can still reach anything the host can reach.
Reads Nothing. The policy vocabulary constrains writes. Reads are unrestricted, including of credential files in your home directory.
Credentials Child environments scrub DSH_* and variables whose names look like KEY, PASSWORD, SECRET or TOKEN. Code Mode gets an empty environment. HOME, proxy settings, normal CLI configuration, readable credential files and differently named secrets all survive. This is not a credential firewall.
macOS specifically An allow-by-default Seatbelt profile with file-write* denied, then workspace and temp roots allow-listed. Depends on the deprecated sandbox-exec, and relies on launch-time failure rather than probing availability first.
Windows specifically A restricted token plus per-workspace and per-session temp ACL grants. Reported by the project itself as partial: Everyone-granted objects and NTFS hard links weaken path-based write isolation.
Code Mode worker Fresh worker per run, empty environment, heap/output/compute/wall limits, message validation, hard termination. Described in its own README as "containment, not a security boundary." It is a worker thread in the host process, and any OS process it spawns can outlive it.
Plugins Dynamically defined packages get a separate VM realm with withheld globals and a guarded context façade. Ordinary plugins execute directly in the host process. The source states host-realm helpers permit escape and that this is not a malicious-code boundary - treat it as equivalent to shell access.
If you enable web fetching, read this first

The HTTP fetch provider's own README states that it is "an SSRF primitive" and "must not be enabled in a deployment that can reach sensitive internal network targets." Combined with network being absent from the sandbox policy, that is a specific and serious constraint for anyone running this inside a corporate network. The web server package separately ships with no TLS, authentication or origin policy.

Two further caveats worth carrying. Installing a plugin from a git source requires granting permission to execute package code at install time, outside any sandbox. And the minimal preset wires its editor to the unsandboxed filesystem service, so its edits are governed by tool logic and account permissions rather than the session's workspace mode - which contradicts the blanket statement in the CLI reference that filesystem mutations are restricted.

None of this is unusual for the category. Every honest harness documents where its boundary leaks. One competitor notes its sandbox "does not run the CLI itself in a sandbox", another that its DNS-rebinding defence "does not eliminate" the risk. The failure mode to avoid is assuming "sandboxed" means "safe." External

12 - Comparison

How it compares

Positioning against the harnesses most readers already use. Licence and capability facts are from each vendor's own documentation. External

dshClaude CodeCodex CLICopilot CLI
Source openFullCLI closedFullNo source
LicenceMITProprietaryApache-2.0Bespoke
LanguageTypeScriptTS -> binaryRustNode
ExtensibilityPlugins at every layerHooks, skills, plugins, MCPHooks, skills, pluginsHooks, plugins, skills
Config formatYAML patch layersJSONTOMLJSON
SandboxLandlock/bwrap, Seatbelt, Win ACLSeatbelt, bwrapSeatbelt, Landlock, native WinOS + cloud
Sub-agentsIncluding Codex and Claude Code as providersYes, plus agent teamsYesYes
PersistenceAppend-only event log; fork/resumeJSONL; resume/forkJSONL -> SQLiteSQLite
Primary surfaceWeb, plus CLI, headless, ACP, SDKTerminal, IDE, desktopTerminal, IDETerminal, ACP
Owns the modelYesYesYesBroker

Three things that table understates

It ships wrappers for two competitors, with pinned versions. The subagent provider bundles depend on @anthropic-ai/claude-agent-sdk and @openai/codex, the latter invoked as app-server --stdio. "Keep your existing tooling and point it here" is not a metaphor; it is two npm dependencies. Source

Harness lock-in is being competed away by the vendors themselves. Copilot CLI reads AGENTS.md, CLAUDE.md and GEMINI.md; Codex sets a Claude plugin root variable; dsh ships bridges that execute both Claude Code and Codex hook files. The convergence on AGENTS.md is real and it flows through the closed players too.

"Public repo" is not "open source" is not "open governance." Claude Code's public repository contains no CLI source. Copilot CLI's licence permits running and unmodified redistribution but forbids derivative works. dsh is MIT-licensed and complete, and accepts no external pull requests, with issues disabled. Three different meanings of "open", and the distinction is worth making explicitly whenever someone compares them.

13 - Corrections

Commonly repeated errors

These circulate widely in secondary explanations of the project. Each correction below is cited to the shipped artifact or the source. No individual is named: the point is the correction, not who made it, and several of these are compressed summaries rather than mistakes.

"The modes are standard, code, minimal and creator" - or "standard, creator and orchestrator"

The preset IDs are standard, code, minimal and cordis. There is no orchestrator. A recursive search of the shipped preset tree returns nothing. "Creator" describes cordis but is not its ID, and the display names are Chinese.

Verified against config/agent-presets/ in the installed npm package. Run

"Code mode converts your interaction into a standalone TypeScript program"

It changes tool presentation. The model writes a program against a generated SDK and run_code executes it, collapsing what would be five round trips into one. The program is ephemeral, runs in a fresh worker with no cross-run state, and is not saved as an artifact.

The shipped preset's own header comment states this. Run

"Everything is a plugin - there is no core"

There is a kernel: Cordis's context and root fiber, plus built-in reflect, registry, events and logger services and host bootstrap. The Loader is then mounted as a plugin. Some objects are deliberately plain classes rather than services. The accurate claim is that nearly everything above a small kernel is a plugin, and nothing above it is privileged.

The repository's own instructions call the phrase shorthand. Source

"The trajectory view maps every action back to the plugin that caused it"

Trajectory is a turn-aware event ledger. It renders provenance for sourced context, including a plugin kind, but assistant and tool records do not generally carry their owning plugin, so it cannot attribute every loop action to an originating plugin.

Read from source, not executed - treat as well-founded but unconfirmed. Source

"Sandboxed, so it is safe to point at anything"

Network is not in the sandbox policy vocabulary, reads are unrestricted, the environment scrub is not a credential firewall, and both Code Mode and dynamically defined plugins are documented as containment rather than security boundaries. See section 11.

Consolidated from package READMEs and sandbox sources. Source

"227 packages" as a measure of what runs

227 is the repository's package count. A real web session composes 129 rows and boots 104 active plugins; headless boots 79. Ask the binary with --dump-config rather than counting directories.

Measured from composed profile output. Run

14 - Errata

Documentation drift at this commit

Four inconsistencies between the documentation and the tree, current as of b150a55. A project shipping this much this fast having four stale references is unremarkable; they are listed because each one will waste someone's afternoon, not as criticism. Run

WhereWhat it saysWhat is true
packages/README.md Documents 48 package groups Two groups exist but are undocumented: mcp and runtime-diagnostics
AGENTS.md Lists a self-modification/ directory No such directory. The code lives in packages/extensions/
Plugin tutorial and CLI reference, both languages, plus the binary's own --help Cite deepseek-harness/turtle-ui as the working external plugin example That repository does not exist. The organisation has zero public repositories. It is the only third-party example named anywhere
docs/cookbook/adding-a-package.md Lists private: true as an enforced package.json invariant The constraints gate raises "release member must not set private: true" for every packages/<group>/<pkg> outside experimental/ - the advice holds only for experimental packages

Separately, and with more practical consequence: the latest npm dist-tag for @deepseek-ai/dsh-tools points at the first release ever published, while current versions ship under next. Anyone following the plugin tutorial's install instruction gets a stale package. External

Because the project accepts no external pull requests and has issues disabled, the only route for any of this is GitHub Discussions.

15 - Provenance

How this was verified

The part of this document that should determine how much you trust the rest.

What was executed Run

What was read but not executed Source

Known limitations

The binary exercised was 0.1.0-rc.7, not the checkout's 0.1.1-rc.2. A registry mirror on the machine used for this work served a stale view and the current release could not be installed from it. Row counts in particular may differ on the newer version. The structural findings (the preset inventory, the composition mechanism, the plugin install path) are unlikely to have moved, but they have not been re-confirmed on the current release.

No model was invoked at any point. Everything here is keyless, which means nothing in this guide describes how the agent behaves in a real task, only how the system is assembled. Tool-calling behaviour, loop dynamics, compaction quality and output quality are all outside what was tested.

A note on method

During this work a corporate npm mirror silently served a two-week-old view of the registry, which produced three confident and wrong conclusions about release timing before being caught by cross-checking against independent sources. Those claims were retracted rather than quietly adjusted. If you are verifying anything about what is published, do not trust a single mirror.