pi ships no permission system, on purpose. We added one as an extension.
Run agents? ACP lets you see, control, and price every tool call they make — free on-device install for Claude Code, Cursor, Codex, and OpenClaw, nothing leaves your machine:
curl -sf https://agenticcontrolplane.com/install.sh | bash -s -- --local
Getting started → · or use the free cloud console → · free for individuals
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 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:
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_callfires before the tool runs. Return{ block: true, reason }to deny; mutateevent.inputin place to rewrite arguments. This is where the pre-call policy decision lands —POST /govern/tool-usereturns allow, ask, or deny.tool_resultfires after. Return replacementcontentto redact or rewrite what the model reads. This is output scanning —POST /govern/tool-outputfor 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 and Claude Code, and stronger than 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
askdecision prompts you inline through pi’s confirm dialog. Approve and the call runs; decline and it’s blocked. - Unattended: an
askbecomes 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, 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.)
| 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 |
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. It adds nothing to pi except the half pi deliberately left for you.