Skip to content
Agentic Control Plane

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:

ACP tool-surface control table: every tool the agent can call, Allow/Deny per tool
ControlEvery tool opencode can call, one row each — Allow / Deny per tool. Deny-by-default on rm -rf, force-push, and the rest. Enforced at the call, outside the model.
ACP cost-per-run distribution for one agent across many runs, log scale
PriceRoute model calls through the proxy and every one is metered — each run split loop-vs-leaf and plotted across runs, so you see which step is the bill and which runs blow the budget.
ACP session trace: per-call timeline of one agent run with cost and decision
AuditEvery tool + model call in a run, in order — what ran, what it returned, the policy decision, and what it cost. Exportable.

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.

--local note. opencode is cloud-only today. The acp-opencode plugin 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 --local to 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:

  1. Tool policies (Policies → Tool Policies) — workspace-wide tool allowlist and required scopes.
  2. Agent policies (Policies → Agent Policies) — per-tier rules.
  3. 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 build vs plan agents to the plugin, so calls report at the interactive tier.
  • 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.)