# opencode Governance, Audit Log & Cost X-Ray — Install Guide

Govern opencode (sst/opencode) in one command. A native plugin checks, scopes, and audits every tool call — allow/deny/ask policy, full audit trail, per-user attribution — plus zero-credential local metering. Free up to 5 agents.

# Govern opencode with Agentic Control Plane

<p style="font-size:17px;line-height:1.6;color:var(--acp-text);max-width:660px;margin:8px 0 6px;">See, control, and price <strong>every tool call opencode makes</strong> — the bash commands, the file edits, the web fetches, and the model calls behind them — from one dashboard. One command, no code changes.</p>

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

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



The installer detects opencode (`~/.config/opencode` or the `opencode` binary), registers the [`acp-opencode`](https://www.npmjs.com/package/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`:

```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](https://opencode.ai/docs/plugins/): 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:

```bash
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):

```json
// ~/.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](/cost-tracking).

## 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:

```bash
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:

```bash
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.)

## Related integrations

- [Hermes Agent](/integrations/hermes) — native Python plugin, the same two-plane design
- [Claude Code](/integrations/claude-code) — hook-based governance, the most complete integration
- [OpenClaw](/integrations/openclaw) — native plugin, like opencode
- [OpenAI Codex CLI](/integrations/codex) — Claude-style hooks, currently Bash-only
- [Turn on Cost X-Ray](/cost-tracking) — meter spend across any harness through the proxy

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "HowTo",
  "name": "Install Agentic Control Plane in opencode",
  "description": "Install ACP's governance plugin in opencode (sst/opencode) for governance, audit, and cost tracking.",
  "totalTime": "PT1M",
  "step": [
    {"@type": "HowToStep", "name": "Run the installer", "text": "curl -sf https://agenticcontrolplane.com/install.sh | bash"},
    {"@type": "HowToStep", "name": "Authenticate", "text": "Browser opens to provision your workspace. The token is saved to ~/.acp/credentials."},
    {"@type": "HowToStep", "name": "Restart opencode", "text": "opencode auto-installs the acp-opencode plugin from npm and governs every tool call."}
  ]
}
</script>
