AI

DeepSeek Harness vs Claude Code: What Is Actually Different

Two coding agents went onto the same Ubuntu box a minute apart. Claude Code landed as eleven files. DeepSeek Harness landed as 29,437.

Original content from computingforgeeks.com - post 170894

That gap is the honest summary of DeepSeek Harness vs Claude Code, and it goes deeper than install size. dsh is an MIT-licensed plugin composition you can open, patch, and re-route. Claude Code is a compiled binary with a fixed set of tool implementations. This comparison sticks to what you can verify on your own machine: what each install writes to disk, which parts are swappable, how each sandbox is enforced, what the Web UI shows that a terminal cannot, and the one integration that works but that nothing tells you how to switch on. If dsh is not on your machine, install dsh and run your first agent first, then come back. Everything below was run in August 2026 on a single Ubuntu 26.04 host carrying both agents, dsh 0.1.0-rc.7 against Claude Code 2.1.237.

Which one to run

Run Claude Code if the agent is doing paid work today. It is a general-availability product, it is stable release to release, and in our own use it holds up better on long multi-file refactors. That last point is an impression from working with both, not a benchmark, so weigh it accordingly.

Run dsh if you need to change something the vendor did not expose: point the agent at your own model endpoint, replace the sandbox or filesystem implementation, self-host the whole thing behind your own auth, or read the code that decides what your agent is allowed to touch. Those are not preferences, they are procurement requirements in a lot of shops, and no amount of quality in a closed binary satisfies them.

One caveat belongs in the verdict rather than a footnote. The first thing dsh shows on launch is a modal titled Internal Testing Notice, telling you that version 0.1 remains in testing and that core plugins and APIs will keep changing quickly. The composer header carries a Preview badge. This is a self-declared preview measured against a shipped product, so treat the comparison as architecture versus architecture, not maturity versus maturity. If you are shopping the wider field, the OpenCode, Claude Code and Cursor comparison covers the alternatives that are further along.

What the two installs put on disk

Both went in through the same npm on the same host, so the numbers are directly comparable.

Measureddsh 0.1.0-rc.7Claude Code 2.1.237
Files on disk29,43711
Size on disk295 MB320 MB
Scoped packages195 (185 named dsh-*)2
LicenseMITProprietary
Readable sourceAll of itNone of it

Nearly identical footprints, three orders of magnitude apart in file count. The reason is that Claude Code no longer ships as JavaScript:

find /usr/lib/node_modules/@anthropic-ai -type f -printf "%s %p\n" | sort -rn | head -3

Two entries of exactly the same byte count sit at the top, and everything else is rounding error:

334715184 /usr/lib/node_modules/@anthropic-ai/claude-code/node_modules/@anthropic-ai/claude-code-linux-x64/claude
334715184 /usr/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe
156320 /usr/lib/node_modules/@anthropic-ai/claude-code/sdk-tools.d.ts

Those two paths are one file. Check the inode and the link count before assuming the install wrote 638 MB:

stat -c "%i %h %n" /usr/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe /usr/lib/node_modules/@anthropic-ai/claude-code/node_modules/@anthropic-ai/claude-code-linux-x64/claude

Same inode, two hard links, one 319 MB native binary:

756377 2 /usr/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe
756377 2 /usr/lib/node_modules/@anthropic-ai/claude-code/node_modules/@anthropic-ai/claude-code-linux-x64/claude

Eleven files is one platform’s worth. Claude Code declares eight platform packages as optional dependencies (linux, darwin and win32, each x64 and arm64, with musl variants for Linux only) and npm installs exactly the one matching your host, so an Alpine box gets the same eleven files around a musl binary of roughly the same size, not both.

The disk cost is a wash and the difference is what you get for it. Claude Code gives you an opaque blob you cannot read, patch, or audit. dsh gives you 295 MB of JavaScript that you can grep, and every claim in the rest of this article was verified by doing exactly that.

Swappable seams versus compiled-in tools

dsh is built on Cordis, through a vendored fork that ships inside the package, so behavior that other agents hardcode is a package here. Count the scope:

ls /usr/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai | wc -l

195 packages, 185 of them prefixed dsh-. They are not flat: each named seam ships as an interface package plus one or more implementations, which is what makes substitution possible rather than theoretical.

SeamInterface plus implementations
sandboxdsh-sandbox, -local, -policy, -windows-acl
llmdsh-llm, -deepseek, -pi-ai, -retry
fsdsh-fs, -local, -observation-policy, -sandbox
shelldsh-shell, -env, dsh-bash-local, dsh-bash-sandbox, dsh-pwsh-local, dsh-pwsh-sandbox
subagentdsh-subagent, -spawn-in-process, -fork-in-process, -in-process-driver
compactiondsh-compaction, -basic, -tool-result-pruner
session-persistencedsh-session-persistence, -jsonl
credentialsdsh-credentials, -local

The Web UI exposes the composed result rather than hiding it. Settings then Plugins then Plugin list shows every plugin in the running deployment with its enabled state, 165 rows on a default install.

DeepSeek Harness plugin list showing 165 plugins with enabled state

Be precise about what Claude Code lacks here, because it is narrower than the marketing on either side suggests. Claude Code has real extension surfaces: MCP servers, plugins, skills, and subagents defined in .claude/agents/ that carry their own tool allowlists. Those are worth understanding on their own terms, covered in MCP vs RAG vs agent skills. What it does not have is replaceable implementations. You can add a tool and you can hide a tool. You cannot swap the filesystem provider or the sandbox runner, because neither is a component in the first place.

Model routing you can change

Every agent claims to be model agnostic. In dsh the vendor SDKs are sitting in the install, so the claim is checkable rather than a roadmap item.

Bundled SDKVersion
@anthropic-ai/sdk0.91.1
@google/genai1.52.0
@mistralai/mistralai2.2.6
@aws-sdk/client-bedrock-runtime3.1048.0
@modelcontextprotocol/sdk1.30.0

Anthropic’s own SDK ships inside DeepSeek’s agent, which means you can run dsh against Claude models if that is the tradeoff you want: open harness, closed model. Behind the SDKs sits dsh-llm-pi-ai, which pulls in a bundled provider layer carrying 37 built-in catalogs, most with their own key variable: anthropic, google, google-vertex, amazon-bedrock, azure-openai-responses, mistral, openrouter, groq, cerebras, fireworks, together, xai, nvidia, huggingface, github-copilot and the rest. A few authenticate by OAuth instead and reject a configured key outright. The Models page takes a key per provider and adding your own endpoint is a first-class action rather than an environment variable hack.

DeepSeek Harness model provider settings

Add a custom provider and the same wiring points at a local server, which is how you get an agent that never sends code off the box. That path has its own set of compatibility switches and is worth a guide of its own; the model side is covered in running DeepSeek V4 Flash locally. Claude Code routes to Anthropic models only, though it can reach them through Amazon Bedrock, Google Cloud, Microsoft Foundry, or an LLM gateway. Several doors, one vendor behind all of them.

The Trajectory tab shows what a transcript cannot

Every session in the Web UI carries two tabs. Chat is the conversation you would get in a terminal. Trajectory is the machine record behind it, rendered.

DeepSeek Harness Trajectory tab showing the typed event stream and timeline

Every event is rendered with its type badge (SYSTEM, USER, CONTEXT, ASSISTANT, TOOL), the raw tool-call JSON, and the return value inline, so a call reads as glob {"pattern": "**/ports.py"} followed by its result. Above the stream sits a timeline banded into Input, Model and Tools with Duration, Turns and Calls toggles, plus a search box.

Claude Code keeps the same information. It writes a full JSONL transcript per session under ~/.claude/projects/ with every message, tool call, and tool result. The difference is that reading it is your problem, and here it is a tab. For anyone who has piped a session transcript through jq at 1am to work out why an agent deleted something, that distinction is not cosmetic.

The status bar carries the accounting for the whole session. A five-step edit-and-verify task on a two-line Python file reported this:

1 turns · 5 steps | LLM 8s · Tool call 0.1s | TTFT avg 1s · 193 tok/s | Cache hit 80% | Input 40.2K tok · Output 559 tok

40.2K input tokens against 559 output tokens, for a one-line fix. That ratio is the actual economics of coding agents and it is visible per session without instrumentation. Claude Code answers the same question with /usage and /context rather than a status bar, and for the wider problem of getting that number down there is reducing Claude Code token usage.

DeepSeek Harness Web UI after completing a code edit task

The Chat view above is the same run in conversational form. The red marker sits on the Bash row, where a compound command tried git diff first, found the directory was not a repository, and fell through to a plain diff in the same call. The edit itself succeeded. Reading which of those two things happened takes one glance at the Trajectory tab and a scroll through JSONL anywhere else.

Agent presets pick the toolset per session

What the model can do is selected per session from a preset picker in the composer. Four ship by default.

DeepSeek Harness agent preset picker with Standard, PTC, Minimal and Creator modes
PresetidWhat it gives the model
Standard modestandardFile editing, shell, file and web search, skills, planning, goals, subagents, workflows
PTC modecodeStandard, with tools exposed through the Code Mode SDK so multi-step operations combine into one TypeScript program
Minimal modeminimalTwo tools only: persistent bash and str_replace_editor
Creator modecordisStandard plus runtime inspection, plugin experiments, and preset-authoring guidance

Claude Code can also scope a toolset, through subagents with a tools: allowlist, the --agents flag, and --allowedTools. The difference is what the scoping acts on. Those flags filter a fixed catalog. A dsh preset is a composition patch that can mount a different implementation of the same capability, which is why the next section is possible at all.

Minimal mode makes that concrete, and hides a trap. Its composition file states outright that the bare local filesystem shadows the host’s sandboxed provider for that preset only, while the shell backend keeps consuming the host sandbox policy. bash stays confined and file writes do not. The asymmetry is deliberate and documented in the shipped package, which means the preset dropdown is a security control, not a convenience.

Both sandboxes are OS-enforced, and dsh tells the model the rules

Skip the usual claim that one of these asks nicely and the other does not. Both enforce at the operating system. Claude Code 2.1.237 runs its Bash tool inside a sandbox using bubblewrap on Linux and Seatbelt on macOS, with filesystem and network policy, a /sandbox panel, and an explicit override parameter. dsh does the same through dsh-sandbox-local, which probes the host once and picks a runner: bubblewrap if a working bwrap is present, Landlock if not, Seatbelt on macOS, a restricted token on Windows. With no usable backend it refuses to run the command rather than running it unconfined. Claude Code defaults the other way: if its sandbox cannot start, it warns and runs the command unsandboxed unless you set sandbox.failIfUnavailable.

Two differences are real. The first is that dsh sandboxes by default with named modes you select per session, and the second is that the model is told the policy as a typed CONTEXT event at the start of every session:

Current runtime context. This snapshot supersedes earlier runtime-context snapshots.
Current DSH file policy: workspace-write. Any available operation enforced by the DSH
file sandbox may modify files under the session workspace: "/home/jmutai/dsh-lab".
Some platform temporary areas may also be writable. Approval policy: ask. Operations
that require approval may ask through the configured answerers; without an available
answerer, the request fails closed.

Under the default workspace-write mode, asking the agent to write outside the workspace returns a kernel error rather than a refusal:

bash: line 1: /etc/dsh-probe.txt: Read-only file system
[sandbox: file access denied under workspace-write mode]
[sandbox: escalation available ...]
[exit code: 1]

The escalation line is trimmed above for width. In full it tells the model to retry the same command once with a sandbox_permissions argument naming the narrowest wider mode that suffices, plus a justification, and that the approval prompt goes to the user. Switch to read-only and writes inside the workspace fail identically. Neither file existed on the host afterwards. If escalation is attempted with no approval channel wired up, that fails closed too:

Error: sandbox escalation to "workspace-write" requires approval, but no approval channel is available

Why the same denial says “Read-only file system” on one host and “Permission denied” on another

The wording depends on which runner got picked, and that trips people comparing notes. On a host with bubblewrap installed, the denial above reads Read-only file system, because the workspace is mounted read-only. Remove bwrap and run the identical probe:

bash: line 1: /etc/dsh-probe.txt: Permission denied

Same policy, same block, different errno, because Landlock denies the access rather than presenting a read-only mount. Ubuntu 26.04 Server does not ship bubblewrap, so a stock install silently takes the Landlock path. Install it with sudo apt install bubblewrap if you want the stricter mount semantics and error text that matches most write-ups.

Making dsh drive Claude Code as a subagent

The shipped presets declare a claude-code subagent provider, disabled. Three of the four (standard, code, cordis) carry the row, alongside a matching codex one:

grep -A7 "id: tool-subagent-claude-code" /usr/lib/node_modules/@deepseek-ai/dsh/config/agent-presets/standard/agent.cordis.yml

The row names a provider and a tool, and ships switched off:

    - id: tool-subagent-claude-code
      name: '@deepseek-ai/dsh-tool-subagent'
      disabled: true
      config:
        provider: claude-code
        toolName: subagent_claude_code
        backgroundMode: one-shot
        maxDepth: provider-managed

Deleting disabled: true and restarting does nothing, and this is where most people will conclude the feature is vapor. The backend is not in the box:

ls /usr/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai | grep -E "claude|codex"

That prints nothing, because the provider is published separately as @deepseek-ai/dsh-subagent-claude-code, MIT, wrapping @anthropic-ai/claude-agent-sdk 0.3.220. It is opt-in by design, and it works. Getting there takes four steps, and three of the four fail without telling you much.

Install the provider into the profile. dsh plugin shells out to pnpm and stops with dsh: pnpm not found on PATH ... if it is missing, so install that first. Pin the version to match your dsh build: the package’s latest tag still resolves to 0.0.1-rc.1, an older BSD-3-Clause build, so an unpinned install gets you the wrong provider.

sudo npm install -g pnpm
dsh plugin --profile web add @deepseek-ai/[email protected]

It installs, then warns you that the job is not finished:

dsh: warning: @deepseek-ai/dsh-subagent-claude-code declares no dsh.bundle ...

The rest of that warning reads “installed as a plain dependency, not a profile layer”, which is the actual instruction: mount it on the host plane yourself. Open the profile patch layer:

vim ~/.dsh/profiles/web/cordis.patch.yml

dsh creates that file at profile init with an empty flow sequence, [], under a comment header. Replace the [], do not append below it, or the file becomes two conflicting root nodes and dsh refuses to boot with YAMLException: end of the stream or a document separator is expected. The finished file, in full:

# Your patch layer for this dsh profile, applied after every bundle layer:
# a top-level YAML array of loader patch entries (id-targeted config
# overrides, disables, and insert lists; `!!js` expressions allowed).
- insert:
    - id: subagent-claude-code
      name: "@deepseek-ai/dsh-subagent-claude-code"
      config:
        env:
          ANTHROPIC_API_KEY: !!js process.env.ANTHROPIC_API_KEY

The insert key matters as much as the content. Without it, the patch layer reads the row as an override of an entry that does not exist yet, discards it, and tells you so once on stderr while everything else carries on normally:

dsh: [/home/jmutai/.dsh/profiles/web/cordis.patch.yml] patch: entry "subagent-claude-code" not found

Two prerequisites hide in that block. The !!js expression reads the environment of the dsh server process, not the shell you type in later, so ANTHROPIC_API_KEY has to be exported before you start dsh web. The provider also resolves the child through claude on PATH, so the Claude Code CLI has to be installed on the same host. If you authenticate Claude Code through a subscription login rather than an API key, drop the env block entirely and let the child use the native login state. A CLAUDE_CODE_OAUTH_TOKEN gets scrubbed too, since the name matches TOKEN, so that route needs the same explicit pass-through as a key.

The explicit env block is otherwise not optional. dsh scrubs credential-shaped variables (anything matching KEY, PASSWORD, SECRET or TOKEN) out of the environment it hands to child processes, so an ANTHROPIC_API_KEY exported in your shell never reaches Claude Code. Pass it in the plugin config or the child starts without credentials.

Copy a preset under a new id and enable the tool row. A home preset that reuses a shipped id stays shadowed by the shipped one, so standard is not a valid name for your copy. The Web UI has a Duplicate button under Settings then Agent presets that handles this properly; by hand it takes three edits, because cp -r also carries over the source preset’s display name and sort order and would give you two entries labelled the same thing:

mkdir -p ~/.dsh/.agent-presets
cp -r /usr/lib/node_modules/@deepseek-ai/dsh/config/agent-presets/standard ~/.dsh/.agent-presets/standard-cc
sed -i '/id: tool-subagent-claude-code/,+2 { /disabled: true/d }' ~/.dsh/.agent-presets/standard-cc/agent.cordis.yml
sed -i 's/^name: .*/name: Standard plus Claude Code/; s/^order: .*/order: 5/' ~/.dsh/.agent-presets/standard-cc/preset.yml

The copy keeps the shipped preset’s description, which is Chinese, so the picker will show your English name above a Chinese subtitle. That is expected, and the Duplicate button does the same. Restart the server so the profile picks up the new host-plane entry. Presets themselves are re-read on every call, so only the plugin mount needs the restart: stop dsh web and start it again, or restart whatever unit runs it. Then pick the copied preset in the composer and delegate. The tool shows up and the call goes through:

DeepSeek Harness delegating a task to Claude Code through the subagent_claude_code tool

DeepSeek’s model called subagent_claude_code, Claude Code ran in the same workspace and returned one sentence describing the file, and the parent then verified the answer against the real file before reporting it. The whole round trip cost 12.5 seconds of tool time on top of 11.3 seconds of model time.

1 turns · 4 steps | LLM 11.3s · Tool call 12.5s | TTFT avg 1.5s · 131 tok/s | Cache hit 75% | Input 33.9K tok · Output 686 tok

The child gets the parent’s working directory and a standalone text task, nothing else. It runs on its own Claude Code authentication and native settings, its tokens never enter the parent’s context, and only the final answer comes back. One vendor’s agent supervising another vendor’s agent, on your box, configured by you. Claude Code can shell out to dsh through Bash or an MCP server, but it has no provider seam to plug another agent into.

What has to change before dsh replaces Claude Code

The preview label has to come off. Version 0.1.0-rc.7 with a testing notice on first launch and a package that changes shape between release candidates is not something to standardize a team on, however good the architecture is.

The silent failures have to go. Three of the four steps in the delegation setup above fail without an error a normal person would find: a preset row that is enabled but inert, a patch entry rejected in a single line of stderr, a credential stripped in transit, a preset id shadowed with no complaint. The capability was already built and shipped, and it is undiscoverable. That pattern shows up elsewhere in the package too, so treat anything you read in a config file as a claim to test rather than a feature to rely on.

The model has to close the gap on long multi-file work. Everything architectural in this comparison favors dsh, and none of it matters if the agent takes three attempts at a refactor the other one gets right first time. That is the one thing you cannot fix with a plugin, and it is why the verdict at the top splits on requirements rather than on which is better. For the day-to-day commands on the other side of that split, the Claude Code cheat sheet stays the reference, and OpenCode is worth a look if open source is the requirement and you want something older than a fortnight. The dsh source is on GitHub under MIT if you want to check any of the above yourself.

Keep reading

Claude Code Cheat Sheet – Commands, Shortcuts, Tips AI Claude Code Cheat Sheet – Commands, Shortcuts, Tips Ollama Models Cheat Sheet 2026 (gpt-oss, Qwen3-Coder, DeepSeek) AI Ollama Models Cheat Sheet 2026 (gpt-oss, Qwen3-Coder, DeepSeek) OpenCode CLI Cheat Sheet – Commands and Workflows AI OpenCode CLI Cheat Sheet – Commands and Workflows Best Machine Learning and Statistical Learning Books for 2026 AI Best Machine Learning and Statistical Learning Books for 2026 Claude Fable 5.1 Released: Benchmarks, Pricing, and API Changes AI Claude Fable 5.1 Released: Benchmarks, Pricing, and API Changes Install Open WebUI with Ollama on Linux AI Install Open WebUI with Ollama on Linux

Leave a Comment

Press ESC to close