Govern opencode with Agentic Control Plane
See, control, and price every tool call opencode makes — the bash commands, the file edits, the web fetches, and the model calls behind them — from one dashboard. One command, no code changes.
Every tool opencode runs — bash, edit, webfetch, MCP tools — is checked against server-side policy before it executes: allow runs it, deny blocks it with the reason, ask escalates to opencode’s own approval prompt. And a second, independent plane needs no account at all: local metering records the token and cost of every model call to a database on your machine.
TL;DR
curl -sf https://agenticcontrolplane.com/install.sh | bash
Three things you get the moment it's running — click any to see it full-size:
rm -rf, force-push, and the rest. Enforced at the call, outside the model.See your first governed call →Free for individuals · full install below
The installer detects opencode (~/.config/opencode or the opencode binary), registers the acp-opencode plugin plus a permission gate in ~/.config/opencode/opencode.json, and opens your browser to provision a workspace. Restart opencode — it auto-installs the plugin from npm on next start — and every tool call is governed.
--localnote. opencode is cloud-only today. Theacp-opencodeplugin needs a workspace, so if you run the installer with--local, opencode is skipped (the installer says so). On-device decisions aren’t wired into the opencode plugin yet — run the installer without--localto govern opencode. (Local mode covers Claude Code, Cursor, and Codex’s shell calls.)
Prefer to wire it by hand? Add two blocks to ~/.config/opencode/opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["acp-opencode"],
"permission": {
"bash": "ask",
"edit": "ask",
"webfetch": "ask"
}
}
Then set your ACP key (export ACP_BEARER_TOKEN="gsk_yourslug_...", or write it to ~/.acp/credentials). The permission block matters — see below.
How it works
Unlike Claude Code, Cursor, and Codex — which shell out to a hook script over stdin — opencode ships a first-class plugin system: a module whose exported hooks run in-process. acp-opencode registers four:
| Hook | What it does |
|---|---|
permission.ask |
The primary governance point. POSTs to /govern/tool-use. ACP allow sets the permission to allow (opencode’s prompt is skipped — pre-approved); deny blocks; ask leaves the status untouched so opencode’s native once / always / reject gate fires. |
tool.execute.before |
Deny-only backstop for tools not routed through the permission system — an ACP deny throws, which is how opencode blocks a call. Also caches the args so permission.ask can attach them. |
tool.execute.after |
Fire-and-forget POST to /govern/tool-output for server-side audit (PII / injection / secret scanning). Best-effort; never blocks. |
chat.headers |
Stamps X-GS-Session / X-GS-Client on every model request, so proxy-metered spend (below) joins the hook-side audit rows for the same session. |
All four send X-GS-Client: opencode-plugin/<version> so the dashboard and policy router can tell opencode traffic apart from Claude Code / Hermes / Cursor.
Why the permission block matters. permission.ask only fires for a tool whose permission resolves to "ask". Without the block, you still get the deny-only backstop and the audit trail — but ACP allow can’t pre-approve a prompt away and ACP ask can’t reach the native gate. The installer sets bash / edit / webfetch to ask for you (filling gaps only — it never overrides a choice you made deliberately).
What gets installed and where
| Path | Contents |
|---|---|
~/.config/opencode/opencode.json |
plugin + permission entries added (existing config preserved) |
~/.cache/opencode/.../acp-opencode/ |
The plugin package, auto-installed from npm by opencode at startup |
~/.acp/credentials |
Your bearer token (the same credential every ACP integration uses) |
~/.acp/opencode-local.db |
Local metering database (created on first run, no account needed) |
One install.sh run covers opencode alongside Claude Code, Cursor, and the rest. Uninstall: remove the plugin entry from opencode.json.
Local metering — no account required
The governance plane above needs a workspace key. The metering plane doesn’t. With zero credentials, acp-opencode taps opencode’s event bus and records every model call — tokens (including cache read/write), model, and the cost opencode computed in-process — plus every tool result, to SQLite at ~/.acp/opencode-local.db. Read it back:
npx acp-opencode report # spend by model, cache hit rate, tool errors, per-task cost
npx acp-opencode report --json # machine-readable — an agent can read its own economics
npx acp-opencode report --days 30 # widen the window (default 7)
It maintains no price table: cost comes from opencode’s own pricing data, and when opencode reports no cost (unknown model, or subscription-included auth) the call is stored with the cost blank and the tokens exact — money is never guessed. Turn metering off with ACP_LOCAL_METERING=off; it also fails open, disabling itself with one warning on a storage error rather than interrupting your agent.
Add the cost X-ray
Local metering runs on your machine; the cost X-ray puts priced spend, prompt-cache hit rate, and the loop-vs-leaf breakdown on the team dashboard, metered server-side. Route opencode’s model calls through the ACP proxy by adding it as a provider — this is opt-in (it changes which model backend you call, so the installer leaves it to you):
// ~/.config/opencode/opencode.json
{
"provider": {
"acp": {
"npm": "@ai-sdk/openai-compatible",
"name": "Agentic Control Plane",
"options": {
"baseURL": "https://api.agenticcontrolplane.com/v1",
"apiKey": "{env:ACP_BEARER_TOKEN}"
},
"models": { "gemini-3.5-flash": {} }
}
},
"model": "acp/gemini-3.5-flash"
}
The proxy is multi-provider (routes gpt-*, claude-*, gemini-* by model id) and forwards to the real provider unchanged — same responses, now metered. The plugin’s X-GS-Session header ties that spend to the tool-audit rows for the same session. Full walkthrough: Turn on Cost X-Ray.
Fail-open by default — and opting in to fail-closed
If the ACP gateway is unreachable — network error, timeout (>2 s), a non-2xx response, or a malformed body — the plugin allows the call and prints one loud warning per session rather than blocking. ACP never bricks an agent. If every call is being blocked, that’s a deny policy, not an outage — check the dashboard.
Security-sensitive setups can flip that to fail-closed — block whenever the gateway is unreachable — with the same contract every ACP harness uses, so one setting covers Claude Code, Cursor, Codex, and opencode together:
export ACP_FAIL_MODE=closed # or: echo closed > ~/.acp/failmode
A policy deny always blocks regardless; fail mode only governs the unreachable case. A machine with no credentials is always a no-op — fail-closed never blocks before you’ve connected. Disable the whole plugin without touching your config:
export ACP_OPENCODE=off
Setting up policy
The same three policy axes that apply to every harness apply to opencode traffic:
- Tool policies (Policies → Tool Policies) — workspace-wide tool allowlist and required scopes.
- Agent policies (Policies → Agent Policies) — per-tier rules.
- User policies (Policies → User Policies) — overrides per identity.
Most-restrictive wins on conflict. Start in audit mode for a few days to learn what your agents actually do, then switch to enforce mode when patterns are clear.
Limitations
Honest list of what the opencode integration cannot do today:
- Context composition isn’t broken out. The per-call token buckets, cache splits, and cost are all recorded, but the system/user/assistant/tool share of each prompt isn’t — opencode 1.18.4 exposes the outgoing message array only through an unstable experimental hook. The report’s composition section lights up automatically once a stable hook lands.
- Per-request model duration isn’t exposed per step; message-level fallback rows carry wall-clock, step rows record 0. Proxy-side metering (above) covers durations authoritatively for routed traffic.
- Tier attribution is coarse. opencode’s hooks don’t distinguish its
buildvsplanagents to the plugin, so calls report at theinteractivetier. - Audit is observe-only. The plugin blocks a call (deny) or allows it; the output path flags findings, it does not redact.
Troubleshooting
Plugin isn’t firing. Restart opencode completely — plugins load at startup. Confirm "plugin": ["acp-opencode"] is in ~/.config/opencode/opencode.json and that opencode installed it (look under ~/.cache/opencode).
Prompts still appear for every tool. That’s the ask path with no ACP decision yet (no credentials, or a policy that says ask). Set ~/.acp/credentials, and check your Tool Policies.
acp-opencode report shows no price. opencode reported no cost for that model (unknown model, or subscription-included auth) — token counts are still exact. Route through the proxy for authoritative priced spend.
Nothing in the dashboard. The Activity page is workspace-scoped — confirm the workspace tied to your token in the top-right dropdown. (Local metering is separate and always works, account or not.)
Related integrations
- Hermes Agent — native Python plugin, the same two-plane design
- Claude Code — hook-based governance, the most complete integration
- OpenClaw — native plugin, like opencode
- OpenAI Codex CLI — Claude-style hooks, currently Bash-only
- Turn on Cost X-Ray — meter spend across any harness through the proxy

