Skip to content
Agentic Control Plane

DeepSeek Harness (dsh)

To install a plugin in dsh, run dsh plugin --profile <your-profile> add <package>. For this one the package is @agenticcontrolplane/dsh. From the next boot, every DeepSeek dsh tool call is checked against your policy and recorded before it runs. A deny or a hold is reported to the agent, but the plugin does not stop the call yet (see the note below).

Install

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

Already ran the installer? It detects dsh and installs the plugin into your existing profiles for you.

Deny is not enforced on dsh today. The plugin reports a deny to the agent and records it, but dsh goes on to run the call. So on DeepSeek Harness the safety floor records and reports a risky call instead of stopping it. The fix is tracked in the plugin repo. Until it ships, don’t rely on ACP to stop a call on dsh.

dsh is in developer preview and its maintainers promise breaking changes. The plugin tracks it, and this page lists the sharp edges we’ve hit.

(Looking for @deepseek-ai/dsh-acp, the Agent Client Protocol server for editors? It’s an unrelated project with the same acronym. /acp-vs-acp sorts it out.)

Why a native plugin instead of a shell hook

dsh is built on Cordis. Its own tools and policy are plugins on typed extension points, and @agenticcontrolplane/dsh is an ordinary plugin on the same points. There is no subprocess per tool call and no JSON-over-stdin dialect. It is a few hundred lines of plain ESM with 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 with no new code. The bridge honors deny and ask but not input rewriting. Use the native plugin. The bridge is a compatibility option, not the recommended path.

How it works

dsh extension point ACP endpoint What happens
tools/pre-execute POST /govern/tool-use The server returns allow, ask or deny. A deny is reported to the agent with the reason in the trajectory. An ask goes to dsh’s own approval prompt if an approval service is mounted. Otherwise dsh denies it, and the hold waits on the console Approvals page or in the approval email until you approve it and ask the agent to retry.
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 goes 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 are on the harness coverage matrix.

An ask with nobody watching becomes a deny. In a headless run with no approval service mounted, dsh resolves ask to deny, because an unattended agent can’t approve itself. That part is dsh’s own behavior, not the plugin’s.

If the gateway is unreachable. Interactive sessions fail open: calls run, with a loud [ACP] ⚠ UNGOVERNED warning and an entry in ~/.acp/lapse.log. Unattended tiers fail closed, which means the plugin returns a deny. Because deny isn’t enforced on dsh yet, that deny is reported and recorded but may not stop the call. Policy denies behave the same way.

Configuration

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

- id: acp
  name: "@agenticcontrolplane/dsh"
  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 also 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 with the same acronym. dsh-plugin-acp is the Agentic Control Plane.

Node 24.9 or newer is required. dsh’s package.json sets engines.node to >=24.9.0. dsh uses Promise.withResolvers and zstd streams from node:zlib, and on older Node it fails at boot with errors that don’t mention Node. Run fnm install 24 first. (Some older ACP docs and installer messages still say Node 22. That is out of date.)

Bare npx launches can’t find profile-installed plugins (rc.6). dsh plugin add links the package into the profile, but an npx-run dsh looks up bare names in the npx cache. A --patch overlay that loads the plugin by file path works everywhere. A normal dsh install resolves it fine.

Add the cost X-ray

The plugin checks and logs tool calls. It never sees the model call, so it can’t price it. The installer writes an acp provider into $DSH_HOME/settings.yaml (the shape from dsh’s providers guide, with the key read from ACP_BEARER_TOKEN through apiKeyEnv, so nothing is pasted) and installs dsh-acp next to claude-acp and codex-acp:

dsh-acp --profile <profile>

dsh-acp reads your workspace key from ~/.acp/credentials, exports it as ACP_BEARER_TOKEN, and starts plain dsh with a one-line note if the gateway is unreachable. dsh has no model flag, so set provider acp as the default model of the profile you launch. Use model gemini-3.5-flash, or any gpt-*, claude-* or gemini-* id the proxy routes. If your settings.yaml already has an llm-pi-ai.providers section, the installer leaves it alone and tells you to add the block by hand. Full walkthrough: Turn on Cost X-Ray.

What you’ll see in the dashboard

A dsh-plugin client row on cloud.agenticcontrolplane.com shows the tool name, input preview, decision, reason, latency, session and workspace, the 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 up in the Calls view. dsh’s own append-only trajectory records the same calls independently, so you have two records of one history.

Troubleshooting

[ACP] ⚠ UNGOVERNED: no credential at boot. There is no ACP_BEARER_TOKEN and no ~/.acp/credentials. Get a key at cloud.agenticcontrolplane.com. Without one, the plugin stays out of the way instead of bricking the session.

Boot fails with Promise.withResolvers is not a function (or a zstd error). Your Node is older than 24.9, and dsh needs 24.9 or newer. Run fnm install 24 && fnm use 24 (or the equivalent) and relaunch.

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

Every call asks. As on any harness, an over-broad ask rule in your workspace policies is the usual cause. When you approve, choose “make this a rule” to narrow it.

Frequently asked questions

What is the dsh install plugin command?

dsh plugin --profile <your-profile> add <package> adds a plugin to that profile, and it mounts on the next dsh --profile <your-profile> boot. For this plugin the package is @agenticcontrolplane/dsh. Confirm it mounted with dsh --profile <name> --dump-config, especially after a dsh upgrade.

Which dsh tool calls does the plugin see?

All of them: bash, pwsh, read, write, edit, glob, grep, web_fetch, web_search, subagent spawns, and the serialized sub-calls inside Code Mode all dispatch through the tools/pre-execute point the plugin sits on. A deny is reported to the agent with the reason in the trajectory. An ask goes to dsh’s own approval prompt when an approval service is mounted. In a headless run dsh denies it, and the hold waits on the console Approvals page or in the approval email until you approve it and ask the agent to retry.

Is deepseek dsh-acp the same plugin?

No. @deepseek-ai/dsh-acp is the Agent Client Protocol server for editors. This page covers @agenticcontrolplane/dsh, the Agentic Control Plane plugin that checks tool calls. They share an acronym and nothing else.