# Prime Agent Permissions & Control Model, Explained

Prime Agent runs everything through one ipython kernel, ships no permission framework, and is built for unattended autonomy. How its extension events, hasUI signal (and its print-mode bug), daemon architecture, and autonomous limits actually work.

# The Prime Agent control model, explained

Prime Agent — [PrimeIntellect-ai/prime-agent](https://github.com/PrimeIntellect-ai/prime-agent), ~18k stars — is a hard fork of [pi](/controls/pi) that pushed pi's minimalism one step further: where pi ships four tools, Prime Agent ships **one**. A persistent ipython kernel is the universal tool — shell commands, file edits, Python, network access all execute as code in it — wrapped in the machinery for **staying alive**: daemon-backed sessions, heartbeats, schedules, recursive subagents (`await rlm(...)`), and a bounded `/autonomous` mode. It is a harness explicitly built for the unattended, long-running shape of agent.

What it did not add on the way: any permission system. This page covers what's native, where the seam is, and the one signal that lies.

*This page covers Prime Agent's own model. For adding ACP, see the [install guide](/integrations/prime-agent); for the cross-harness picture, [the controls comparison](/controls).*

## What Prime Agent ships natively

- **No permission prompts, no rules, no sandbox.** The kernel runs with the launching user's full access. The extension docs list "permission gates (confirm before `rm -rf`, `sudo`, etc.)" as the *first example use case* for an extension — control is homework, by design.
- **Bounded autonomy, not policy-controlled autonomy.** `/autonomous` takes explicit limits — `maxTurns`, `maxContinuations`, `maxTokens`, `timeoutMs` — and print mode reports which limit stopped the run. These are budget throttles: they bound *how much* the agent does, not *what* it's allowed to do.
- **A branchable session tree, not an audit record.** Like pi, sessions are durable JSONL you can fork and rewind — the run's own account, on the run's own machine.
- **A resident daemon.** A supervisor process hosts session workers over sockets in `$TMPDIR/prime-agent-<uid>/`; sessions survive the TUI closing. Operationally: extensions load per worker (`/reload` picks up changes), and a `kill -9`'d worker leaves a stale socket that hangs the next CLI start — clear the socket dir when testing.

## The seam: pi's two events, kept intact

Prime Agent kept pi's extension API wholesale — same package name, same typed events — which makes it a first-class control target on day one:

| Event | Fires | An extension can |
|---|---|---|
| `tool_call` | before the tool runs | return `{ block: true, reason }` to **deny**; mutate `event.input` in place to **rewrite arguments** (no re-validation after) |
| `tool_result` | after the tool runs | return `{ content, details, isError }` patches to **rewrite or redact** what the model reads |

Handlers chain in load order; `tool_call` errors **fail closed** (the call is blocked). Coverage is total for the same reason as pi: there's no MCP layer and no second dispatch path. The one tool being `ipython` cuts both ways — interception is coarse (you see code, not a parsed intent), but it is *complete*, and code-level policy is exactly what a server-side classifier is for.

Extensions load from `~/.prime/agent/extensions/*.ts` (global, no trust prompt — where a control extension belongs) or `.prime/agent/extensions/` (project-local, behind project trust), TypeScript loaded directly via jiti, no build step. Node 22.8+ enforced with a clear error. The provider seam (`pi.registerProvider("anthropic", { baseUrl })`) re-points model traffic through a proxy in three lines — the metering half of the story.

## The signal that lies: `hasUI` in print mode

pi's cleanest contribution to control was the **empty-chair signal**: `ctx.hasUI` tells an extension whether a human can answer, so an approval with nobody present can deny instead of hanging. Prime Agent inherited the API — but broke the semantics: **0.8.x reports `hasUI = true` in headless `-p` print mode.** The docs table says print mode has no-op UI; the types comment says false; the runtime says true (verified against 0.8.1 with a live extension).

The consequence is not cosmetic — and argv alone doesn't rescue it, because Prime Agent hosts *every* CLI session in a daemon worker process (argv `--mode daemon`, env `PRIME_AGENT_INTERNAL_DAEMON_WORKER=1`): the worker's argv never shows `-p`, and the same worker class hosts attached TUI sessions and empty-chair print runs alike. An extension using pi-correct logic here treats unattended runs as attended: outage handling falls open when it should fail closed, and asks try to prompt a chair nobody is sitting in.

What *does* discriminate is the UI itself: a confirm dialog delivered to an attached client gets answered; the headless no-op UI cannot say yes. The reliable posture is therefore **prompt-as-probe** — route asks (and outage proceed-ungoverned questions) through the confirm dialog and let a false/no-op answer mean deny — plus conservative tier labeling for worker and headless processes. [The ACP extension](/integrations/prime-agent) ships exactly that; the clean upstream fix would be per-session client-attachment state on `ExtensionContext`.

## What Prime Agent's model doesn't include

The same honest zero as pi, at higher stakes because the harness is built to run longer with less supervision:

- **No policy.** Nothing decides allow/deny until an extension does; out of the box, everything the kernel can reach, runs.
- **No independent record.** The session tree is not a decision ledger — what was evaluated, against which policy, on whose authority.
- **No identity.** Kernel executions aren't attributed to a user or delegation chain — and subagents multiply that.
- **Autonomy limits ≠ controls.** `maxTokens` stops a runaway bill, not a policy-controlled action.

## What the extension adds

[The ACP extension for Prime Agent](/integrations/prime-agent) is the same pattern as [the pi extension](/controls/pi) — a dependency-free TypeScript file on `tool_call` and `tool_result` — with one addition specific to this fork: it corrects for the `hasUI` print-mode bug documented above. Rather than trusting `ctx.hasUI`, it routes `ask` decisions through the confirm dialog itself and treats a no-op or false answer as deny, plus applies conservative tier labeling to daemon-worker and headless sessions by default — the prompt-as-probe posture this page's FAQ describes, shipped as the extension's default rather than left for you to reconstruct per session.

On `tool_call`, workspace policy decides allow/ask/deny before the `ipython` kernel executes anything; on `tool_result`, output scanning can redact or rewrite what the model reads back, and every decision — allow, ask, deny, redact — writes to an off-machine ledger whether or not a human was ever in the loop for that particular worker.

Configuration is the same environment-variable set as the pi extension, since it's the same underlying client: `ACP_BEARER_TOKEN` (workspace key), `ACP_GOVERN_BASE` (gateway URL), `ACP_AGENT_TIER` (session label, defaulted conservatively for daemon workers here), and `ACP_SHADOW` (record without enforcing, for testing against a live daemon before switching policy on).

That's tool-call policy and audit — cost is separate. Prime Agent kept pi's `pi.registerProvider(name, { baseUrl })` seam, and pointing it at the ACP proxy is what makes per-call cost, cache-hit rate, and a workspace budget cap possible on a daemon session that can otherwise run for hours unmetered. See [Prime Agent cost tracking](/blog/prime-agent-cost-tracking) for the full three-line setup.

## Where ACP fits

Prime Agent is the [pi thesis](/controls/pi) with the autonomy dial turned up: a minimal core where **control is an extension**, running agents whose sessions outlive the terminal that started them. [The ACP extension](/integrations/prime-agent) is that control — workspace policy decides allow/ask/deny on every `tool_call`, output scanning and the ledger land on `tool_result`, the argv fix restores the empty-chair posture that `hasUI` drops, and every decision is recorded off the machine, joined across your whole fleet. Same policy that applies to [Claude Code](/controls/claude-code), [pi](/controls/pi), and [dsh](/controls/dsh) — which is the point: the more harnesses fork and diverge, the more the control has to live outside all of them.

## Frequently asked questions


