Harness coverage matrix
ACP checks each tool call a coding agent makes before it runs. How much of that works depends on the harness. This table says, for each one, what the hook sees, how a held call reaches you, and whether a result can be changed before the model reads it.
This page is about what ACP does on each harness. For what each harness ships on its own (approval modes, rules, sandboxing, audit) see Which coding agent has the best native controls?
Three questions decide whether you are covered:
- What does the pre-call check see? If the hook does not fire for a tool, your rules never see it.
- How does a hold reach you? A deny is easy. A usable setup also needs a way for a human to say yes.
- Can the result be rewritten? Scanning that cannot act is a log entry. A leaked credential still reaches the model.
| Harness | Install | Pre-call check sees | How a hold reaches you | Result rewritten before the model reads it? |
|---|---|---|---|---|
| Claude Code | guide | All native tools + MCP | Safety-floor holds: a prompt in the session (workspace owner or admin). Holds from your own rules: a block, approved on the console or from the email, then retried | Yes |
| GitHub Copilot (CLI + VS Code agent mode) | guide | Shell, file edits, MCP; one hook file covers both | Block; approve on the console or from the email, then retry. In the cloud coding agent an ask is a deny by GitHub’s rule, and cloud runs are not governed today (the sandbox has no ACP credentials) | Not verified. The post-call hook records and scans the output. One Copilot CLI release did not honour a deny (copilot-cli#3874) |
| Qwen Code | guide | Shell, file edits, MCP, in every approval mode including yolo | Block; approve on the console or from the email, then retry. In a headless run Qwen Code turns an ask into a deny itself | Not verified. The post-call hook records and scans the output |
| Codex CLI | guide | Shell, apply_patch edits, MCP and other local tools on current builds (shell only on older builds). Hosted tools such as web search never reach a hook; the MCP connector’s acp_check is the path for those |
Block; approve on the console or from the email, then retry. Codex cannot show a hook ask as a prompt, so the plugin never sends one | No |
| Cursor | guide | Most native tools (some built-ins don’t emit; bugs filed) | Block; approve on the console or from the email, then retry | No; the post-call hook is observe-only |
| opencode | npm: acp-opencode | Permission-routed tools (bash, edit, webfetch, MCP) plus a deny-only backstop for the rest |
Block; approve on the console or from the email, then retry | No |
| OpenClaw | guide | Native plugin surface | Block; approve on the console or from the email, then retry | No |
| Hermes Agent | guide | Everything, in-process: native and custom skills | Block with a system message; approve on the console or from the email, then retry | No; Hermes ignores the post hook’s return value. Its transform_tool_result hook could do it, and the plugin does not register it yet |
| DeepSeek Harness (dsh) | guide | Everything: native tools, custom plugins, Code Mode sub-calls | Block; approve on the console or from the email, then retry. A headless dsh with no approval service turns an ask into a deny itself. A deny is reported to the agent but not enforced by the plugin today; the fix is tracked | Yes; tools/post-execute can replace result content |
| Muse Code (Meta) | guide | Everything: one hook pipeline over native tools (plugin hooks sit behind an experimental flag today) | Block; approve on the console or from the email, then retry | Yes; a PostToolUse block becomes feedback the model sees |
| pi (earendil-works) | guide | Everything: four built-ins + custom tools; no MCP layer to miss | Block; approve on the console or from the email, then retry. With no UI (pi -p, piped) an ask is denied on the spot |
Yes; tool_result replaces content |
| Prime Agent (Prime Intellect) | guide · npm: @agenticcontrolplane/prime-agent | Everything: the ipython kernel and custom tools flow through one tool_call event; MCP arrives as Python skills inside the kernel, so there is no second dispatch path |
Block; approve on the console or from the email, then retry. Headless runs deny an ask themselves; ctx.hasUI alone is not trusted (contract) |
Yes; tool_result rewrites content, details and isError |
| Grok Build (xAI) | guide | Everything: native tools + MCP (server__tool), in every mode including always-approve |
In default and plan modes the hold defers to Grok’s own prompt. In auto, bypass and headless modes the call is blocked; approve on the console or from the email, then retry | No; only PreToolUse blocks. Post-call findings land in the audit log |
| Antigravity (Google) | guide | Everything: one hook registration over native tools + MCP across the CLI, IDE and app | Block; approve on the console or from the email, then retry | No; PostToolUse carries no tool output (contract). The post-call record is audit only |
| fx (Vercel Labs) | what it does · npm: @agenticcontrolplane/fx | Every approval-gated call fx routes to session/request_permission. Not a hook over all tool calls |
Block; approve on the console or from the email, then retry. With --unattended the middleware fails closed |
No; it is a permission gate on the protocol seam, not an output rewrite |
About the holds column. Several of these harnesses have their own confirm prompt (Muse Code, Antigravity, pi, Prime Agent, Grok Build, dsh, OpenClaw, fx). The gateway only sends a hold as a prompt to a harness where someone has watched a human answer it, and today that is Claude Code. Everywhere else the call is blocked in the agent and waits (Grok Build is the exception in default and plan modes, where the hold defers to Grok’s own prompt) on the Approvals page and in the approval email. Approve it, then ask the agent to retry. An unanswered request expires and counts as denied.
Deny. A deny blocks the call on every harness in this table except dsh, where the plugin reports it to the agent and lets the call run. That row says so, and it will change when the plugin fix ships.
What “covered” means here
Every row speaks the same wire contract. Before a call, the hook posts the tool name and arguments to /govern/tool-use and gets back allow, ask or deny. After the call, it posts the result to /govern/tool-output for scanning and the audit log. Your rules are keyed the same way on every harness (Bash.git.push, Bash.rm, Edit), so one workspace and one set of rules covers every harness the installer set up.
What differs is what each harness lets the hook do with the answer. The table is where that is written down. Where a harness cannot express something (Codex has no ask prompt; Hermes cannot rewrite; dsh does not stop on deny yet), the plugin degrades and the row says so.
Questions before you install
Why not auto mode plus a sandbox? Keep both. A sandbox limits where the agent can write and what it can reach. Auto mode decides per call. Neither tells a force-push from a plain push, knows that DROP TABLE is headed for a database client, or gives you one set of rules that applies by tier across every harness on the machine. ACP adds that layer and one log of what each call did. The rules page has the six rules most people start with.
Does it cover my other harness? If it is in the table, yes, to the depth the row says. The keys and rules are the same everywhere. How a hold shows up differs (see the holds column), and so does whether a result can be rewritten.
What leaves my machine? In a connected workspace the hook sends each tool call to the gateway: the tool name, its input (the command or file path), and up to 200KB of its output. Credentials in a recognised format are redacted before a preview is stored. With --local, nothing is sent; decisions come from ~/.acp/policy.json and are recorded in ~/.acp/audit.jsonl on the same machine.
What if ACP is down? It depends on which hook is running. The Claude Code plugin keeps unattended tiers (subagent, background, api) blocked until the gateway answers; interactive sessions proceed with a loud UNGOVERNED notice, and the safety floor still runs on the machine. The shared hook that Cursor, Codex, Copilot, Qwen Code and the other hook-file harnesses use fails open in every tier unless you opt in with echo closed > ~/.acp/failmode (or ACP_FAIL_MODE=closed) on that machine. Harnesses with their own plugin package (dsh, pi, Prime Agent, fx and the rest) document their posture on their guide page.
What does a workspace do on day one? It starts in audit mode: every call is recorded, the safety floor applies, and your rules do not block yet. /acp-enforce or the Policies page turns them on.
If you’re building a harness
The spec that earns a full row: one pipeline every tool dispatch flows through, a typed pre-call decision with allow, ask and 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 interception points were designed for this (tools/pre-execute returning allow/ask/deny, tools/post-execute able to replace content) and its headless composition denies unanswerable asks by itself; the shipped plugin still has the deny gap noted above. Muse Code’s hooks speak Claude Code’s exact hook contract, verified against the harness’s own capability runner; the write-up covers where its docs run ahead of the shipped binary. The Hermes and dsh write-ups go deeper on why those designs work.
fx is a different shape. It has no plugin API, so there is no hook over every tool call. The seam is fx acp, an Agent Client Protocol server that routes every approval-gated call to the connected client with full raw arguments and fails closed on error. @agenticcontrolplane/fx stands there as protocol middleware, not a hook plugin.
Updated 2026-11-08: Copilot and Qwen Code rows added; dsh, Cursor, Codex and the holds column corrected against the gateway and plugin code. This page changes whenever a harness integration ships or a harness closes a gap.
Running coding agents? ACP records each tool call its hook sees and makes the dangerous shell commands (a force-push, destructive SQL, curl | sh) wait for you. One command, free up to 5 agents:
curl -fsSL https://agenticcontrolplane.com/install.sh | bash
irm https://agenticcontrolplane.com/install.ps1 | iex