Skip to content
Agentic Control Plane

DeepSeek Harness (dsh) permissions plugin: allow, ask or deny each tool call

David Crowe David Crowe · · 8 min read
deepseek-harness dsh coding-agents agentic-control-plane plugin-launch
Share X HN LinkedIn

(ACP here is the Agentic Control Plane, the policy layer this site is about. If you searched dsh acp looking for the Agent Client Protocol, the editor protocol: how dsh runs over it is below, and two unrelated projects share the acronym.)

Updated 2026-11-08. Two corrections. A deny is not enforced on dsh today: the plugin reports it to the agent and records it, but dsh has gone on to run the call (seen in one production workspace, including on a safety-floor deny). The fix is tracked; until it ships, treat a deny on dsh as reported, not guaranteed, and keep dsh’s read-only sandbox default as your backstop. And a hold does not appear as a prompt inside dsh. It waits on the console Approvals page and in the approval email; approve it, then ask the agent to retry. The coverage matrix has the current status.

DeepSeek Harness, the coding harness DeepSeek released into developer preview this week, now has ACP support via @agenticcontrolplane/dsh (npm). One command:

curl -sf https://agenticcontrolplane.com/install.sh | bash

The installer detects dsh, adds the plugin to every profile you have, signs you in once in the browser (free for individuals), and writes the key to ~/.acp/credentials, which the plugin reads on its own. By hand: dsh plugin --profile <your-profile> add @agenticcontrolplane/dsh, then export ACP_BEARER_TOKEN with a key from cloud.agenticcontrolplane.com.

From the next boot, every tool call dsh makes (bash, read, write, edit, web_fetch, subagent spawns, and the serialized sub-calls inside its Code Mode) is checked against your workspace rules before it runs, and every decision lands in your activity log with the reason attached. A new workspace starts in audit mode: calls are recorded and the safety floor applies, but your own rules don’t hold anything until you turn enforce on.

dsh is three days into a developer preview that promises breaking changes. Treat this plugin the same way: early, tested, tracking a fast-moving target. But the interception points it plugs into are among the best we’ve integrated, and that is the reason for this post.

Where dsh lands on the coverage table

(The living table is at /coverage. This copy reflects the gateway and plugins as of 2026-11-08.)

By August 2026 we had shipped ACP plugins for six harnesses, plus OpenCode and OpenClaw on the same wire contract. The question for each one: when your policy says no, what stops? When it says ask, how does the question reach you?

Harness What the pre-call check sees How a hold reaches you Result rewritten before the model reads it?
Claude Code All native tools + MCP Safety-floor holds: a prompt in the session. Holds from your rules: console or email, then retry Yes
Codex CLI Shell, apply_patch edits, MCP on current builds (shell only on older ones) Console or email, then retry. Codex can’t show a hook ask as a prompt No
Cursor Most native tools Console or email, then retry No
Hermes Everything, in-process Blocked with a system message; console or email, then retry No (the post hook is observe-only)
dsh Everything: native tools, custom plugins, Code Mode sub-calls Console or email, then retry. A deny is reported but not enforced today Yes: tools/post-execute can replace result content

When this post went out, dsh was the first harness since Claude Code where the post-call hook could change what the model sees. That comes from their architecture, not from anything clever in our plugin.

Why dsh ended up here

dsh is built on Cordis, a plugin framework where the harness’s own tools, policy and UI are plugins on typed extension points. Tool execution has a tools/pre-execute waterfall any plugin can join. A listener returns one of three typed decisions:

  • { kind: 'allow' }: the call runs
  • { kind: 'deny', reason }: the call is skipped and the model sees the reason
  • { kind: 'ask', reason }: dsh’s own approval service prompts the human. With no approval service mounted (headless), the ask becomes a deny

That last line deserves a pause. In a headless composition, dsh turns ask into deny by design. An unattended agent cannot approve its own call. That is the unattended posture we’ve argued for since the Codex integration, and dsh ships it in core.

One caveat for ACP users: the gateway only sends an ask as an in-session prompt to a harness where someone has watched a human answer it, and today that is Claude Code. On dsh, a held call waits on the Approvals page and in the email. Approve it, then ask the agent to retry.

After the call, a tools/post-execute waterfall can accept, block, or replace the result content the model sees. Claude Code has that hook and Hermes doesn’t. It is where output scanning stops being a log entry and starts being able to act: strip a leaked credential, or drop injected instructions from a fetched page, before the model reads it.

The plugin is a few hundred lines of plain ESM. No build step, no dependencies. Every transitive dependency on a control-plane plugin is a supply-chain target sitting next to a credential, and this one has zero.

What the integration caught on day one

The first live test found a real gap, in our gateway rather than in dsh.

Every harness spells its tools differently. Claude Code says Bash; dsh says bash; Gemini CLI says run_shell_command. Our gateway’s name-specific layers (the hardline floor that always blocks a recursive delete of root, the per-binary and per-domain command classification, the dotted policy tiers) matched the Claude Code spellings exactly. A shell command arriving as bash got a policy answer at the generic tier but skipped every layer that knew what a shell is.

The fix is one alias table where classification begins, so every harness dialect is canonicalized before the floor, the classifiers and the policy tiers see it (#676, fixed and closed the same day). One change covered every harness we had and ones we hadn’t met yet. The recursive-root-delete-as-bash repro now comes back deny with the hardline reason attached.

The lesson applies beyond our gateway: if your control layer keys on tool names, every new harness is a silent coverage gap until proven otherwise. Test with the harness’s real spellings.

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, the same shape as every other harness. dsh’s own append-only session log records the same calls independently, so you have two witnesses to one history when you’re reconstructing what an agent did.

If the gateway is unreachable, interactive sessions fail open: the call runs, with a loud [ACP] ⚠ UNGOVERNED warning and a line in ~/.acp/lapse.log. For unattended tiers the plugin returns a deny instead. Because dsh does not enforce a deny today, that deny is recorded but may not stop the call.

Running dsh over Agent Client Protocol (the editor protocol, not this plugin)

dsh ships its Agent Client Protocol mode as a profile. Per the dsh CLI README, dsh --profile acp serves automation clients over Agent Client Protocol on stdio until they disconnect. The acp profile sets itself up from a shipped template the first time you run it, and its app bundle is @deepseek-ai/dsh-acp-app. The README doesn’t cover editor setup. Your editor registers dsh the way it registers any custom Agent Client Protocol agent: a command plus arguments.

Our plugin is installed per profile (dsh plugin --profile <name> add @agenticcontrolplane/dsh). We have not tested it inside the acp profile. Until the coverage matrix lists it, don’t count on a dsh session driven from an editor being checked or recorded by ACP.

Three gotchas

dsh’s acp profile is not this plugin. It’s the Agent Client Protocol, an editor-integration protocol with the same acronym, covered in the section above. Older write-ups call it @deepseek-ai/dsh-acp; current dsh runs it as the acp profile. @agenticcontrolplane/dsh is this plugin. /acp-vs-acp has the full map.

Node 24.9 or newer is required. Early previews ran on Node 22; dsh’s package.json now sets engines.node to >=24.9.0. Under an older Node, dsh fails at boot with errors (Promise.withResolvers, zstd) that don’t mention Node. fnm install 24 first.

Profile-installed plugins don’t resolve under a bare npx launch (rc.6). dsh plugin add links the package into the profile, but an npx-run dsh resolves bare package names from the npx cache and won’t find it. A --patch overlay that loads the plugin by file path works everywhere. Expect this to change.

What this means for harness authors

When dsh hit the front page this week, much of the discussion was about the append-only trajectory log rather than the model. Developers want to see exactly what their agent did.

Seeing is half of it. A trajectory tells you what happened; it can’t stop what shouldn’t. dsh’s authors built for both halves: the same typed extension points that feed its log let a plugin decide, before each call, whether it runs at all.

If you’re building a harness and want it controllable, that’s the spec. One pipeline every tool dispatch flows through, a typed pre-call decision with allow/ask/deny that the harness enforces, a post-call hook that can rewrite what the model sees, and deny-by-default for asks nobody can answer. dsh’s design has all four. The shipped plugin still has the deny gap noted at the top, and that part is on us.

Try it

fnm install 24 && fnm use 24   # dsh needs Node 24.9+
curl -sf https://agenticcontrolplane.com/install.sh | bash
dsh --profile <your-profile>

Plugin source: github.com/agentic-control-plane/dsh-acp-plugin. Install guide: /integrations/dsh. What dsh itself ships for permissions and sandboxing: /controls/dsh. Already running ACP for Claude Code, Codex, Cursor or Hermes? The same ~/.acp/credentials works: one workspace, every harness.

(Installed the plugin under its launch name dsh-plugin-acp? Same code. It’s been renamed on npm to @agenticcontrolplane/dsh; swap the name in your profile when convenient.)

Running dsh? One command adds a native plugin. Each tool call is checked and recorded. A block is reported to the agent but not enforced by the plugin yet.

macOS · Linux · WSL
curl -fsSL https://agenticcontrolplane.com/install.sh | bash
Windows · PowerShell
irm https://agenticcontrolplane.com/install.ps1 | iex

What the installer writes  ·  dsh guide  ·  free up to 5 agents

Share X HN LinkedIn
Get the next data drop
What agents actually cost, new tool-surface captures, and the occasional incident post-mortem — sent when we publish something worth your inbox, not on a schedule. Unsubscribe anytime.
Share: Twitter LinkedIn
Related posts

← back to blog