# pi ships no permission system, on purpose. We added one as an extension.

pi (earendil-works) is a minimal-core harness: four tools, no approval prompts, no sandbox — by design. Its typed tool_call / tool_result events make control a clean extension concern. Here's the ACP extension that governs every pi tool call, and why pi's design is the whole thesis in miniature.

[pi](https://github.com/earendil-works/pi) — the minimal terminal coding agent from Mario Zechner (libGDX) and Armin Ronacher (Flask, Jinja) — does something most harnesses don't: it ships **no permission system at all, and says so.** Four tools (`read`, `write`, `edit`, `bash`), no approval prompts, no allowlist, no sandbox. Its security docs don't hedge — they tell you to containerize if you want a boundary, and point at the [Gondolin](https://github.com/earendil-works/pi) micro-VM extension, Docker, or OpenShell.

That's not a gap to dunk on. It's a design, and a coherent one: keep the core tiny, make everything else an extension. Which means the *right* way to add control to pi isn't to bolt something on the side — it's to be an extension, on the same seam pi's own tools use. So that's what we built. pi now has ACP support via [@agenticcontrolplane/pi](https://github.com/agentic-control-plane/pi-acp-plugin):

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

The installer detects pi, drops one TypeScript file at `~/.pi/agent/extensions/acp.ts`, and signs you in once in the browser — free for individuals. From the next session, every tool call pi makes gets policy-checked against your workspace before it runs, and every decision lands in your activity log with the reason attached.

## The seam pi gives you

pi dispatches every tool through two typed events, and an extension is an ordinary TypeScript module that subscribes to them. The ACP extension registers on both:

- **`tool_call`** fires before the tool runs. Return `{ block: true, reason }` to deny; mutate `event.input` in place to rewrite arguments. This is where the pre-call policy decision lands — `POST /govern/tool-use` returns allow, ask, or deny.
- **`tool_result`** fires after. Return replacement `content` to redact or rewrite what the model reads. This is output scanning — `POST /govern/tool-output` for PII, secrets, and injected instructions.

That's the full trifecta: deny, input-rewrite, and inline output-rewrite. The same capability set we get from [dsh](/controls/dsh) and Claude Code, and stronger than [opencode](/controls/opencode), whose post-hook can only observe. And here's the part that makes pi genuinely clean to govern: **it has no MCP layer and no second tool path.** `bash`, `read`, `write`, `edit`, and any custom or extension-registered tool all flow through those two events. One extension, complete coverage — nothing to supplement, no built-in that quietly doesn't emit. Where our Codex integration needs a hook *and* an MCP connector, pi needs one file.

## pi tells you when the chair is empty

The detail that made this a pleasure to write: pi hands the extension `ctx.hasUI` — `true` in the interactive TUI, `false` in print (`pi -p`) and JSON modes. That's the attended-vs-unattended signal most harnesses leave you to infer from a timeout, handed over directly. We use it exactly as pi's own shipped `permission-gate.ts` example does:

- **Attended:** an `ask` decision prompts you inline through pi's confirm dialog. Approve and the call runs; decline and it's blocked.
- **Unattended:** 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 — that's the [empty-chair test](/blog/interactive-vs-autonomous-the-empty-chair-test), and pi lets an extension pass it in one line.

## What it looks like

Point pi at an unattended run and ask it to do something your policy forbids, and the deny comes back through pi's own transcript:

```
$ pi -p "run: rm -rf ./build-artifacts-nonexistent"
[ACP] Session receipt: 1 tool call governed · 1 denied —
      review this session: https://cloud.agenticcontrolplane.com/sessions/01a0…
The command was denied by policy: approvers notified; action blocked until approved.
```

A benign call passes and is counted, and the session ends with one honest line — how many calls ACP governed, and a deep link to the timeline. Zero governed calls produce zero receipt, because nobody needs to be told nothing happened.

## Where pi lands on the coverage table

*(The living version, updated with every integration, is at [/coverage](/coverage).)*

<div class="acp-post-wide-table" markdown="1">

| Harness | What the pre-call check intercepts | Can policy ask a human? | Can the result be rewritten inline? |
|---|---|---|---|
| Claude Code | All native tools + MCP | Yes (native `ask`) | Yes |
| dsh | Everything, incl. Code Mode sub-calls | Yes (native approval) | Yes |
| **pi** | **Everything — four built-ins + custom tools; no MCP to miss** | **Yes — inline confirm when attended, deny when not** | **Yes — `tool_result` replaces content** |
| Hermes | Everything, in-process | Inline prompt | Yes (`transform_tool_result`) |
| opencode | Permission-routed + backstop | Yes | No (post-hook observes) |
| Codex CLI | Full, via its 2026 hooks | Guardian reviewer / dashboard | Via hooks |

</div>

pi joins the top tier — not because it ships strong controls, but because it ships a clean enough seam that the controls attach without compromise.

## The philosophy is the point

pi is the sharpest version of an argument this site keeps making: in a minimal-core harness, **control is an extension**, and the extension seam is where it has to live. pi's authors made that literal — they left the permission system out and handed you the events to build one. We think that's the right instinct, and the ACP extension is what it looks like filled in: workspace policy that decides, an audit record the agent didn't author, approvals that know whether you're there, and the same rules running on pi as on every other harness you touch.

It's one MIT TypeScript file that imports only a pi *type* — zero runtime dependencies, no build step, [readable in a couple of minutes](https://github.com/agentic-control-plane/pi-acp-plugin). It adds nothing to pi except the half pi deliberately left for you.

- [Install guide](/integrations/pi) · [What ACP sees and controls in pi](/controls/pi) · [The cross-harness comparison](/controls)
