Back to All Cheatsheet Libraries cheatsheets

DeepSeek Harness

DeepSeek's open-source agent harness where every capability — models, tools, sessions, even the agent loop — is a swappable plugin. CLI, profiles, patch layers and the gotchas that bite.

Read this first: developer preview

The project's own README states it plainly, in bold: "THERE WILL BE COMPATIBILITY-BREAKING CHANGES." DeepSeek Harness is iterating rapidly and is not positioned as stable.

That is not a reason to avoid it — it is a reason to pin a version, keep your profile under version control, and re-read the changelog before upgrading. Treat anything on this page as a starting point and confirm against the repo.

Everything is a plugin

dsh is an open-source agent harness from DeepSeek AI, built on the Cordis plugin framework. The distinguishing claim is architectural: there is no privileged core. The model adapter, the tool registry, the session log, even the agent loop itself are plugins mounted from configuration, and every one of them can be swapped or patched.

The second design commitment is traceability — everything the model sees is recorded in an append-only session log.

# Fastest path — needs Node.js only npx @deepseek-ai/dsh web # Serves http://127.0.0.1:3080 and opens your browser # Pass --no-open to skip the browser handoff npx @deepseek-ai/dsh web --no-open # From a checkout git clone https://github.com/deepseek-ai/deepseek-harness.git cd deepseek-harness pnpm install pnpm run build # prepares repository artifacts pnpm dsh web # runs the built artifacts without rebuilding
Step What to do
1. Configure a modelSettings → Models, paste a DeepSeek API key, save. Takes effect on the next request — no restart.
2. Choose a workspaceClick Choose workspace and add the project directory. The session composer stays disabled until you do — a fresh Web UI has no workspace selected.
3. Run a taskStart a session and ask something. The agent reads and edits workspace files, runs commands, delegates work, and keeps a plan.
Who it's for
  • Harness developers — people building agent infrastructure, not just using an agent. The whole value proposition is that you can replace any layer.
  • If you want a coding agent that works out of the box with no assembly, a finished product is a better fit. This is a framework with a reference application on top.
  • Written in TypeScript, MIT licensed, and genuinely open source — no commercial tier gating the architecture.
  • Plugins are discoverable via the dsh-plugin GitHub topic.
Group Command Does
Bootdsh --profile <name>Boot the profile at $DSH_HOME/profiles/<name>.
Bootdsh webHardcoded alias for --profile web. Serves 127.0.0.1:3080.
Bootdsh --profile headless "job"One fresh persisted session, prints the final answer, exits. 0 on completion, 1 otherwise.
Web flags--host / --portOverride the composed values. --host 0.0.0.0 is deliberately unsupported and exits with a usage error.
Web flags--no-openSkip the browser handoff. Auto-suppressed when SSH_CONNECTION or SSH_TTY is set.
Web flags--trusted-hostRepeatable. Adds authorities accepted by the /api browser-trust fence.
Config--patch <path>Overlay a patch file. Repeatable; applied in argv order, last wins.
Config--dump-default-configPrint only the bundle layers, without booting.
Config--dump-configAdds the profile and home patch layers plus --patch overlays. Comments name the file that supplied each row.
Pluginsdsh plugin --profile <n> add <pkg>Forwards to pnpm in the profile directory. Every pnpm verb works — add, remove, why, update.
Pluginsdsh plugin --profile <n> add github:owner/repoGit-hosted plugins. See the pnpm ≥10 allowBuilds gotcha.
Helpdsh --helpThe launcher's help. dsh --profile web --help prints the web app's help and boots nothing.

Launcher flags come first — this trips people

The launcher parses only its own flags. The first token it does not recognise starts the app's arguments, and everything from there is handed to the booted profile verbatim. Put launcher flags before app flags or they will be silently forwarded.

dsh --profile web --port 8080 # --port belongs to the WEB APP dsh --profile headless "run the tests" dsh --profile web --help # the web app's help dsh --help # the launcher's own help # The launcher consumes one `--`. To pass a literal `--` to the app: dsh --profile web -- -- # A flag beats the value written beside it in config — but only while # the row keeps its runtime expression: # port: !!js ctx.webStartup.port ?? 3080 # A user patch that replaces the whole config with literals removes # that read, and the flag stops working.
Environment variable Effect
DSH_HOMEHarness home — profiles, credentials, home-level patch layer.
DSH_TOOLS_MODEnative, code, or both. Any other value fails at boot.
DSH_PERMISSION_MODEChanges the process-level permission fallback.
DEEPSEEK_API_KEYUsed by the built-in web_search tool.
DEEPSEEK_SEARCH_BASE_URLAlternative search endpoint.
DSH_TELEMETRY_MODEFULL streams every session event as OTLP/HTTP; FEEDBACK_ONLY uploads a log suffix when feedback is recorded. Read the privacy note before enabling.
DSH_TELEMETRY_DISABLEDAny non-empty value is an authoritative hard opt-out.
DSH_TELEMETRY_OTLP_URLPoint exports at your own collector.
NODE_USE_ENV_PROXYSet to 1 so a supporting Node honours HTTP_PROXY / HTTPS_PROXY.

Profiles, bundles and patch layers

A running dsh is a plugin tree composed at boot from ordered layers. Understanding the layer order is most of understanding the product.

Composed over an EMPTY root, in this order — later layers win per row: 1. each bundle listed in the profile manifest's dsh.profile.bundles 2. the profile's own cordis.patch.yml 3. the home-level $DSH_HOME/cordis.patch.yml 4. each --patch <path> overlay, in argv order Note (3) outranks (2): machine-local preferences shared by every profile deliberately beat the per-profile layer. A patch REPLACES the targeted row's whole `config` value. It does NOT deep-merge keys. It may also insert new rows.
Concept What it is
ProfileA named composition in the Harness home. Lists its bundles, holds out-of-tree plugins, keeps the user's patch. web and headless ship as templates and auto-initialise; any other name must be created via dsh plugin.
BundleA distribution format for config rows and the code they mount — so whatever it inserts stays patchable by layers above.
dsh-baseFirst layer of every profile: model adapters, tools, persistence, sandbox, approval policy, settings, credentials, telemetry.
dsh-web-appAdds the browser application.
dsh-headlessOne-shot runner with no server at all — no ApiProxy, Host, HTTP server or browser client, and it opens no listening port.
StepOne model request plus the tools it calls.
TurnZero or more steps. Opens before its first input is claimed, closes once nothing is owed.
Session logAppend-only SessionEvent record of everything the model saw. The traceability guarantee.
Core packages and their ctx keys
  • core/session — append-only event log and in-memory store · ctx.sessions
  • core/system-prompt — prompt-section and tool-schema assembly · ctx.systemPrompt
  • core/tools — scoped tool registry and guarded execution · ctx.tools
  • core/agent — the Agent interface, live registry, agent/* events · ctx.agents
  • core/agent-loop — the default driver implementing that interface · ctx.agentLoop
  • llm/llm — message and stream vocabulary plus the adapter seam · ctx.llm
  • core/scope — per-agent scoped registration. A library, no ctx key.
Events — the extension points
  • Session events — durable facts appended to the log and broadcast via session/event. Use when the fact must survive a reload.
  • Agent events (agent/*) — carry a live agent: inbox, step, status, request, validation, continuation. Use to observe or intercept work in flight.
  • Capability events (fs/*, tools/*, telemetry/*) — attach policy and adapters to a seam without importing the loop.
  • Registrations are reversible effects: they unwind when their plugin unloads.
  • Picking the right event domain is the first decision in most changes.
Providers and credentials
  • Settings → Models. The DeepSeek card takes one API key; changes apply on the next request without a restart.
  • Add provider pulls endpoint, protocol and model list from the installed catalog — Anthropic, OpenAI and others.
  • Add a custom provider for a company gateway or self-hosted server: lowercase Provider ID, base URL, API protocol, credential, at least one model. Fetch available models queries the endpoint you have typed.
  • Keys are write-only. The page only ever receives a redacted descriptor; the secret lands in $DSH_HOME/.credentials.yaml and settings keep only a reference.
  • Resolution order: inherited environment → $DSH_HOME/.credentials.yaml → the invoking directory's .env → $DSH_HOME/.env. The managed document is never written into process.env.
  • Bedrock, Vertex, Azure and Codex need native auth — AWS credentials and a region, an ADC project, an api-version, and OAuth respectively. Filling only the API-key field does not configure them.

Hand-entered models are text-only until you say otherwise

Nothing can ask an endpoint which modalities it accepts, so a model you type by hand is treated as text-only and an attached image is refused before it is sent. The form has no field for this — add it in $DSH_HOME/settings.yaml.

llm-pi-ai: providers: my-gateway: apiKeyEnv: GATEWAY_API_KEY api: openai-completions baseURL: https://gateway.example/v1 models: - id: legacy-chat # stays text-only - id: vision-preview input: [text, image] # per-model opt-in # `input` accepts text and image, and applies to that model alone, # so one route can serve both kinds. Omitting it — or an empty list, # which means the same — keeps whatever the catalog records, falling # back to the route's defaultInput for an unknown model.
Permissions and sandboxing
  • New sessions default to the workspace-write preset.
  • Bash and filesystem mutations are restricted to the session workspace and platform temp roots. Reads and network access are not confined.
  • Process visibility depends on the backend: bwrap uses a private PID namespace that hides host processes; Landlock and Seatbelt leave host process visibility unchanged.
  • Stored General-settings permissions affect later Web sessions, not one already open.
  • No MCP server is enabled by default — each server command is trusted executable code outside the agent sandbox. @deepseek-ai/dsh-mcp-client ships as a dependency for patch layers to use.
Subagent providers
  • Codex and Claude Code are separate optional bundles, added and removed independently.
  • dsh plugin --profile <n> add @deepseek-ai/dsh-subagent-codex
  • dsh plugin --profile <n> add @deepseek-ai/dsh-subagent-claude-code
  • On the next start each installed bundle registers only its dormant host provider — a copied preset must separately enable the matching tool row for new agents.

Gotchas

Telemetry has no redaction rules
  • Session telemetry stays local by default, which is the right default.
  • But the shipped base has no telemetry redaction rule. If you explicitly enable exports, they can contain message text, tool arguments and results, and workspace paths.
  • Do not turn on DSH_TELEMETRY_MODE=FULL against a shared collector without thinking about what is in your sessions.
  • DSH_TELEMETRY_DISABLED (any non-empty value) is a hard opt-out that outranks everything else.
Things that will catch you
  • Bundle changes need a restart. A running profile keeps the bundle set it started with. Ordinary cordis.patch.yml edits hot-reload; adding, removing or updating a bundle does not.
  • A patch replaces a row's whole config, it does not deep-merge. Replacing a config with literals also removes any !!js runtime read — which is how a CLI flag silently stops overriding it.
  • The home patch outranks the profile patch. Counter-intuitive if you expect the more specific layer to win.
  • Git-hosted plugins fail on first install with pnpm ≥10. Their prepare script is blocked until you copy the printed allowBuilds key into the profile's pnpm-workspace.yaml and re-run. Built tarballs and local checkouts need no allowance.
  • --host 0.0.0.0 is intentionally unsupported and exits with a usage error.
  • Relative path specs anchor to the invoking directory, so add . from a plugin checkout installs that checkout, not the profile.
  • Shutdown allows five seconds to dispose. SIGTERM exits 0, SIGINT reports 130, a second signal forces immediate exit.
  • The launcher does not check artifact freshness — stale frontend bundles will happily run older browser code until you rebuild.
  • Missing frontend bundles fail at startup telling you to run pnpm run build; missing host artifacts fail earlier as bare module-resolution errors with no such hint.

Tips

Dump before you debug

dsh --profile web --dump-config prints the composed tree with comments naming the file that supplied each row and every overlay that changed it. It answers "why is this setting like that" instantly.

Diff the two dumps

--dump-default-config shows bundles only; --dump-config adds your layers. Diffing them shows exactly what you changed versus what shipped.

Version-control your profile

A profile is just a package.json and a cordis.patch.yml. Given the preview status and promised breaking changes, keeping it in git is cheap insurance.

Headless for CI

dsh --profile headless "task" opens no port, writes nothing to stderr on success, and exits 0 or 1 — a clean scriptable surface.

It reads your agent instructions

Every mode loads applicable AGENTS.md or CLAUDE.md from the workspace, with a 65,536-byte render budget.

Find plugins by topic

The dsh-plugin GitHub topic is the discovery mechanism, and the convention for publishing your own.

Resources