Skip to content
Agentic Control Plane

DeepSeek Harness (dsh)

TL;DR:

dsh plugin --profile <your-profile> add dsh-plugin-acp
export ACP_BEARER_TOKEN=gsk_...   # or keep it in ~/.acp/credentials
dsh --profile <your-profile>

From the next boot, every tool call flows through your ACP workspace before it runs. Already ran the installer? It detects dsh and installs the plugin into your existing profiles automatically.

dsh is in developer preview with breaking changes promised — the plugin tracks it, and this page records the sharp edges we’ve hit.

Why a native plugin instead of a shell hook

dsh is built on Cordis: the harness’s own tools and policy are plugins on typed extension points, and dsh-plugin-acp is an ordinary plugin on the same points — no subprocess per tool call, no JSON-over-stdin dialect. ~190 lines of plain ESM, zero dependencies, no build step.

(dsh also ships a hooks-claude-code bridge that runs an unmodified Claude Code hooks.json, so our shell-hook govern.mjs works today with zero new code — deny and ask are honored, input rewriting is not. Use the native plugin; the bridge is a compatibility note, not the recommended path.)

How it works

dsh extension point ACP endpoint What happens
tools/pre-execute POST /govern/tool-use Server returns allow / ask / deny. Deny skips the call with the reason in the trajectory; ask feeds dsh’s own approval prompt.
tools/post-execute POST /govern/tool-output Output scanning. A server block turns the result into corrective feedback; a redact replaces the content the model reads.

Everything dispatches through that one pipeline — bash, pwsh, read/write/edit, glob/grep, web_fetch/web_search, subagent spawns, and the serialized sub-calls inside Code Mode. Coverage details on the harness coverage matrix.

Asks with nobody watching deny themselves. In a headless composition with no approval service mounted, dsh resolves ask to deny by design — an unattended agent cannot self-approve. That’s the fail-closed posture for unattended runs, shipped by the harness itself.

Failure posture is the standard one: interactive sessions fail open with a loud [ACP] ⚠ UNGOVERNED warning and a ~/.acp/lapse.log entry when the gateway is unreachable; unattended tiers fail closed. Policy denies are unaffected.

Configuration

Override the plugin row in your profile’s cordis.patch.yml:

- id: acp
  name: dsh-plugin-acp
  config:
    governBase: https://govern.agenticcontrolplane.com  # or self-hosted
    agentTier: interactive   # default: interactive when an approval service is mounted, background otherwise
    timeoutMs: 4000

ACP_GOVERN_BASE, ACP_BEARER_TOKEN, ACP_AGENT_TIER, and ACP_SHADOW=off work as environment variables.

Three sharp edges (developer preview)

packages/acp inside dsh is not this. It’s Zed’s Agent Client Protocol — an editor-integration protocol that shares the acronym. dsh-plugin-acp is the Agentic Control Plane.

Node 22 required. dsh uses Promise.withResolvers and zstd streams from node:zlib; under Node 20 it fails at boot with errors that don’t mention Node. fnm install 22 first.

Bare npx launches can’t resolve profile-installed plugins (rc.6). dsh plugin add links the package into the profile, but an npx-run dsh resolves bare names from the npx cache. A --patch overlay loading the plugin by file path works everywhere; a normal dsh install resolves fine.

What you’ll see in the dashboard

A dsh-plugin client row on cloud.agenticcontrolplane.com: tool name, input preview, decision, reason, latency, session and workspace attribution — same shape as every harness. Since 0.1.1 the plugin forwards dsh’s per-call id, so the tool-use and tool-output rows pair exactly in the Calls view. dsh’s own append-only trajectory independently records the same calls: two witnesses to one history.

Troubleshooting

[ACP] ⚠ UNGOVERNED: no credential at boot. No ACP_BEARER_TOKEN and no ~/.acp/credentials. Get a key at cloud.agenticcontrolplane.com — without one the plugin stays out of the way rather than bricking the session.

Boot fails with Promise.withResolvers is not a function. Your Node is 20; dsh needs 22. fnm use 22 (or equivalent) and relaunch.

failed to import loader entry acp. You’re launching via bare npx — see the sharp edges above; use a --patch overlay by file path or a normal install.

Every call asks. Same as any harness: an over-broad ask rule in your workspace policies. Approve with “make this a rule” to scope it down.