# pi (earendil-works) Tool-Call Control & Audit — Install Guide

Install ACP for pi via its native TypeScript extension. Every tool call — bash, read, write, edit, custom tools — policy-checked before it runs, with an audit record and inline output scanning. pi ships no permission system; this adds one.

# Govern pi 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 pi makes</strong> — bash, read, write, edit, and any custom tool — from one dashboard. pi ships <a href="/controls/pi">no permission system by design</a>; this native extension adds one, in one command.</p>

## TL;DR



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.

<p style="font-size:12.5px;color:var(--acp-text-faint);margin:8px 0 0;">MIT-licensed, zero dependencies, one readable file — <a href="https://github.com/agentic-control-plane/pi-acp-plugin">read it before you run it</a>.</p>

## 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](/controls/pi). 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](/controls/pi) and the [cross-harness matrix](/coverage).

## 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](/blog/interactive-vs-autonomous-the-empty-chair-test), 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`.

<details>
<summary>Manual install</summary>

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

</details>

## Add the cost X-ray

pi has no MCP, and its model calls can route through the ACP proxy for metering. Start pi with the launcher the installer put next to `claude-acp` and `codex-acp`:

```bash
pi-acp
```

The installer already wrote the `acp` provider into `~/.pi/agent/models.json` (nothing to paste) and left your default model alone, so plain `pi` is untouched. `pi-acp` passes `--model acp/gemini-3.5-flash` for that launch only (`ACP_PI_MODEL=<id>` picks another), and if the gateway is unreachable it starts plain `pi` with a one-line note. The provider it writes, for the record:

```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" }]
    }
  }
}
```

By hand, that is `pi --model acp/gemini-3.5-flash` — which is all the launcher does, plus the fail-open check. 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](/cost-tracking).

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

- [What ACP can see and control in pi](/controls/pi) — the living controls reference
- [Which coding agent has the best native controls?](/controls) — where pi sits in the cross-harness comparison
- [The extension source](https://github.com/agentic-control-plane/pi-acp-plugin) — one MIT TypeScript file
