Architecture
August 16, 2026 · View on GitHub
The plugin is an external-CLI bridge, the same shape as the Codex and Grok Build Claude Code plugins: Claude Code slash commands run one Node script (dsh-bridge.mjs), and that script drives DeepSeek Harness. Nothing in this repo modifies DSH; every capability composes from DSH's public CLI and SDK wire protocol. Third-party copyrights and design provenance (including the anchored-standard mechanism port) are recorded in NOTICE.
Two drive paths
Claude Code slash command / dsh-delegate agent
│
▼
scripts/dsh-bridge.mjs (subcommand dispatcher; stdout = user-facing result)
│
├── one-shot path ──► spawn: dsh --profile headless
│ --patch <generated unattended overlay (per mode)>
│ [--patch <generated agent-mode overlay (none for standard; minimal / anchored-standard opt-in)>]
│ [--patch <generated model overlay>] -- "<prompt>"
│ env DSH_PERMISSION_MODE=read-only|workspace-write
│ (review, critique, fresh run, import digest source)
│
└── multi-turn path ─► scripts/dsh-broker.mjs (per-workspace daemon, unix socket)
│ owns
▼
dsh --profile cc --patch <generated unattended overlay>
[--patch <generated agent-mode overlay>]
(dsh-base + SDK JSON-RPC server on stdio)
sessions get-or-created by sessionId inside this
one live process (run --session/--resume, import)
One-shot is the default because it is crash-isolated and needs no setup: each invocation is a complete DSH session that exits with the final assistant message on stdout. Multi-turn exists only because headless sessions cannot continue across processes and SDK sessions live inside one runtime (see Headless profile and SDK wire protocol); the broker keeps one SDK runtime alive per workspace and routes prompts to it by session id.
Decisions forced by DSH facts
Each of these is a design decision downstream of a verified DSH behavior (all pinned in dsh-compat.md):
- A generated unattended overlay on every dsh spawn (one-shot AND broker runtime). The dsh-base approval policy is
ask, which fails closed with no approval answerer composed — and dsh-base's permission-presets service refuses to boot when the composed sandbox+approval pair names no preset, and pins the default preset's knobs into fresh sessions. The bridge therefore generates a per-mode overlay (approval.policy: neverplus a singleunattendedpreset exactly matching the launch mode) instead of shipping a static file; the sandbox mode (viaDSH_PERMISSION_MODE) remains the real safety boundary. - Model selection is a generated
--patchoverlay. Headless has no--modelflag; model/effort live in theagent-default-modelandllm-deepseekconfig rows, and--patchis the last composition layer, so a temp overlay wins deterministically. - Agent mode is a generated
--patchoverlay too, defaulting to standard.standardis the untouched composition (full catalog from request #1).minimalandanchored-standardare opt-in.minimalfixes the persona (includeHarnessIdentity/includeRuntimeContextoff), disables every model-facing dsh-base row except bash andstr_replace_editor, and insertslib/tool-bootstrap.mjsso assemble sections collapse to onecomplete: trueRL sentence. Extra tools stay uncomposed, so the plugin's later promotion cannot widen that catalog.anchored-standardkeeps the full registry mounted, uses the same persona/runtime-context flags, and inserts the same plugin, which filters the model-visible catalog to the Minimal pair until that session records a durabletool/callorassistant/message, then returns the assembled catalog. DSH's own preset roster (dsh-agent-presets) is unreachable from both plugin paths — the headless bundle and the SDK JSON-RPC server deliberately mount no preset — so the mode lives at the composition layer the plugin does control.@deepseek-ai/dsh-personacannot be inserted here (it is scope-only and collides with the deployment persona). Official Web Minimal's persistent PTY bash anddsh-fs-localare also out of scope (sandbox boundary). A broker's mode is fixed at spawn (same lifecycle as its permission mode). - The broker hand-rolls the SDK wire client. The protocol is three requests and four notifications over newline JSON-RPC; embedding ~150 lines keeps the plugin dependency-free (both reference plugins made the same call). The run-to-idle algorithm is a direct port of the TypeScript SDK's
HarnessSession.run: wait for the prompt'sagent/inbox/splicedreceipt, collectsession.events, stop atsession.status: idle, extract the lastassistant/messagetext. - Stop = kill. The SDK wire has no cancel or session-close method; aborting a mid-turn broker run means killing the runtime, which discards its in-memory sessions. The bridge makes this explicit rather than pretending to cancel.
- Structured critique output is prompt-contract, not API. DSH has no structured-output flag, so the JSON schema is embedded in the prompt and the parser tolerates bare JSON, fenced blocks, and brace-span extraction, falling back to raw text.
- Review targeting lives in the plugin. The bridge resolves working-tree vs branch scope and collects bounded diff context itself, then hands DSH a self-contained prompt — DSH needs no git awareness beyond reading files.
- Default install is the pinned npm CLI; the SDK JSON-RPC server is added separately with its published peers.
@deepseek-ai/dshdoes not depend on@deepseek-ai/dsh-sdk-jsonrpc-server, and the launcher's$DSH_HOME/profiles/node_modulesself-heal does not provide the server's peerDependencies (a server-only add fails at boot withCannot find package '@deepseek-ai/dsh-sdk-protocol'). Setup thereforeplugin adds the pinned server plusHARNESS_SDK_JSONRPC_PEER_SPECS. Dist-tags are unsafe (latestof the server is not the CLI'slatest).--harnessstilllink:-installspackages/sdk/serverfrom a user-built checkout. The persistedsdkProfileVersionis the install identity (npm:<pin>orharness:<realpath>), so switching source re-adds the server instead of keeping the previous profile plugins.
Process model
dsh-bridge.mjsruns per command invocation and exits. Foreground runs block until DSH finishes;--backgroundwrites the job record + queued request to disk and re-spawns itself detached asrun-worker.dsh-broker.mjsis spawned detached on first--session/--resume/importand survives Claude sessions. One broker per workspace state dir; one run in flight at a time (busy answers RPC code-32001).- The SessionStart hook exports
DSH_CC_SESSION_IDandDSH_CC_TRANSCRIPT_PATHthroughCLAUDE_ENV_FILE; SessionEnd cancels the session's still-active jobs. Jobs are scoped per Claude session for listing but stored per workspace (see state-and-jobs.md).
Layering inside scripts/
dsh-bridge.mjs— dispatch, argument surface, job orchestration. Knows nothing about DSH argv details.lib/dsh.mjs— the only file that composes DSH invocations (binary resolution, headless argv, overlays, output parsing, profile probes).lib/tool-bootstrap.mjs/lib/request-snapshot.mjs— Cordis plugin inserted by the mode overlay (complete persona viasystemPrompt.section, outermost assemble filter, assemble-time phase freeze, optionalDSH_CC_SNAPSHOT_FILErecorder after pre-step /request/header). Copied next to generated yaml so--patchinsertnameis self-contained.lib/broker-client.mjs— the only file that talks to the broker socket.lib/git.mjs,lib/claude-session-transfer.mjs— context collection (git diffs, transcript digests).lib/state.mjs,lib/tracked-jobs.mjs,lib/job-control.mjs— durable job state and lifecycle.lib/render.mjs— all user-facing text.
Keeping DSH knowledge inside dsh.mjs, dsh-broker.mjs, and the inserted bootstrap plugin means a DSH upgrade audit touches those files plus dsh-compat.md.