Skip to content
Agentic Control Plane

Govern pi with Agentic Control Plane

See, control, and price every tool call pi makes — bash, read, write, edit, and any custom tool — from one dashboard. pi ships no permission system by design; this native extension adds one, in one command.

TL;DR

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

The installer detects pi, drops the extension at ~/.pi/agent/extensions/acp.ts, and opens your browser once to provision a workspace and save the key to ~/.acp/credentials. Restart pi — every tool call is governed from the next session.

MIT-licensed, zero dependencies, one readable file — read it before you run it.

Why an extension (and why it’s the whole story)

pi is built minimal-core: four tools, no built-in approval prompts, no allowlist, no sandbox — and its docs say so plainly. What it does have is a typed extension pipeline that every tool dispatch flows through. Because pi has no MCP layer and no second tool path, one extension on that pipeline governs everything the agent does. There’s nothing to supplement — unlike Codex (hooks cover shell, MCP covers the rest) or Cursor (some built-ins don’t emit). One file, complete coverage.

How it works

pi dispatches every tool through two typed events; the extension registers on both:

pi event ACP endpoint What happens
tool_call POST /govern/tool-use Server returns allow / ask / deny. Deny blocks the call with the reason in the transcript; ask prompts you (attended) or denies (unattended).
tool_result POST /govern/tool-output Output scanning. A server block turns the result into corrective feedback; a redact replaces the content the model reads (PII, secrets, injected instructions).

Everything pi runs — bash, read, write, edit, subagent and custom tools — flows through those two events. Coverage details on the pi controls reference and the cross-harness matrix.

Approvals and the empty chair

pi tells the extension whether a human is present via ctx.hasUI:

  • Attended (interactive TUI): an ask decision prompts you inline through pi’s own confirm dialog — approve and the call runs, decline and it’s blocked.
  • Unattended (pi -p, JSON mode): an ask becomes a deny, with the request surfaced in the console for later review. No timeout guessing, no silent auto-yes — an agent nobody is watching cannot self-approve. This is the empty-chair posture, and pi states the condition exactly.

Failure posture

The standard never-brick contract:

  • Attended sessions fail open, loudly. Gateway unreachable → the call proceeds, a [ACP] ⚠ UNGOVERNED warning shows, and a line lands in ~/.acp/lapse.log. One transport retry precedes the fail posture (the slow answers are cold starts).
  • Unattended runs fail closed. Nobody is watching, so the block is the safety net.
  • Policy denies are unaffected — this posture only covers the inability to ask the policy.

What gets installed and where

Path Purpose
~/.pi/agent/extensions/acp.ts The extension (global scope — loads without a project-trust prompt)
~/.acp/credentials Bearer token from browser OAuth (chmod 600)

The global path matters: extensions there govern before any repository is opened, whereas project-local .pi/extensions/ entries load only after you trust the project. Credential rotation is a one-file overwrite — no reinstall. Uninstall: delete acp.ts.

Manual install ```sh mkdir -p ~/.pi/agent/extensions curl -sf https://raw.githubusercontent.com/agentic-control-plane/pi-acp-plugin/main/index.ts \ -o ~/.pi/agent/extensions/acp.ts # then set your key: export ACP_BEARER_TOKEN="gsk_yourslug_..." # or write it to ~/.acp/credentials ``` Get a key at [cloud.agenticcontrolplane.com](https://cloud.agenticcontrolplane.com). Restart pi.

Add the cost X-ray

pi has no MCP, and its model calls can route through the ACP proxy for metering. Add a provider to ~/.pi/agent/models.json:

{
  "providers": {
    "acp": {
      "baseUrl": "https://api.agenticcontrolplane.com/v1",
      "api": "openai-completions",
      "apiKey": "!cat ~/.acp/credentials",
      "compat": { "supportsDeveloperRole": false, "supportsReasoningEffort": false },
      "models": [{ "id": "gemini-3.5-flash" }]
    }
  }
}

Then pi --model acp/gemini-3.5-flash. The proxy is multi-provider (routes gpt-*, claude-*, gemini-* by model id) and forwards unchanged — same responses, now priced, with prompt-cache hit rates and the loop-vs-leaf split on the dashboard. Full walkthrough: Turn on Cost X-Ray.

Two things to know

  • pi needs Node 22+. Node 20 boots it cryptically; use fnm/nvm to select 22.
  • No key? The extension says so loudly at session start and stays out of the way — it never bricks a session. A machine with no credentials is a clean no-op.

Learn more