@yuru7/pi-ai-approval
A fail-closed approval gate for Pi that classifies every covered tool call into six risk levels with an isolated AI reviewer and enforces per-level allow/ask/deny from local configuration.
Package details
Install @yuru7/pi-ai-approval from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@yuru7/pi-ai-approval- Package
@yuru7/pi-ai-approval- Version
0.4.0- Published
- Sep 15, 2026
- Downloads
- 953/mo · 699/wk
- Author
- yuru7
- License
- MIT
- Types
- extension
- Size
- 268.5 KB
- Dependencies
- 0 dependencies · 4 peers
Pi manifest JSON
{
"extensions": [
"./extensions/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-ai-approval
English | 日本語
A fail-closed approval gate for Pi, the coding agent. An isolated AI reviewer classifies every covered tool call into one of six risk levels, and a local riskActions policy decides whether it runs: allow, ask (a Deny / Approve / Approve + Add Rule prompt), or deny.
The AI never decides the final outcome. It only assesses risk and explains what the operation does; the decision always comes from your local configuration, and anything the reviewer cannot classify is blocked.
How it works
Tool call
↓
AI review (isolated reviewer session)
↓
Risk level + instruction alignment + operation summary + rationale
↓
riskActions configuration
↓
allow ─────────→ execute
ask → Deny / Approve / Approve + Add Rule
Approve: execute this call
Approve + Add Rule: approve and add a session rule
Deny: block
deny ─────────→ block
allowruns the tool call without confirmation.askshows an approval prompt and rings the terminal bell in interactive TUI mode. The prompt is a title plus one Markdown document:Approval Required,**Risk: <level>**(bold, plus a level color in the TUI —warningfor medium/high,errorfor very high/critical — so it stays readable on terminals that do not render bold),Review Information:with theRisk Assessor:model and channel rank plusInstruction Alignment(and aSession Rule:line when an existing rule lowered the classification),Operation (tool: <tool name>):with the command in a syntax-labelled code block (bashorpowershell, matching the tool that will run),Operation Summary:, andReason:(all labels are plain lines; the document has no headings). In the TUI the document is rendered by the standard Markdown component under a full-width rule that carries the title (─── Approval Required ─────), so the prompt reads as its own area and not as another line of the session transcript; when the prompt is taller than the screen the body scrolls (mouse wheel orshift+↑/shift+↓) with a transient scrollbar and a remaining-lines hint, while the choices stay pinned. When a command is longer than the preview keeps (300 characters), theOperationcode block ends with... (truncated, ctrl+o to expand);ctrl+o— or, in a fullscreen TUI, a click on that marker text itself — swaps in the whole command with its own line breaks, and the help line under the choices then readsctrl+o collapse. Mouse input only reaches the prompt in fullscreen mode, soctrl+ois the way to expand in a regular TUI. Selectors outside the TUI keep the plain… [truncated]marker, because they cannot expand the block. The choice list is fixed to Deny / Approve / Approve + Add Rule with Deny preselected, so pressing Enter keeps the action blocked.Approve + Add Ruleapproves the current call and then asks for a session approval rule: Enter stores the typed text, and Esc cancels the input and brings the choices back, so a cancelled input never approves anything. Esc, Ctrl-C, and an unavailable UI also block (fail closed). Outside the TUI (RPC, print, JSON) the title is prepended to the same document and passed to the client's own selector.denyblocks the tool call and returns the AI's rationale to the agent, together with instructions not to retry the same action through a workaround.
An Approve (including Approve + Add Rule) applies to exactly that one tool call. The next call is reviewed on its own, and a session rule only lets the reviewer-confirmed classification fall by one level; the rule never approves anything by itself. Concurrent ask outcomes are serialized so only one prompt is ever visible.
Risk levels
Risk is the practical risk of running the action in the context of the user's current task: how much damage its failure, misuse, or unintended side effects could cause, how costly it would be to reverse, and how directly it implements what the user asked for. Alongside the risk level, the reviewer reports an instruction_alignment of direct, implied, weak, or unrelated.
Key principles:
- Ordinary development actions that directly implement the user's request, are narrowly scoped, and are easily reversible are
lowrisk — even when they modify project files. - Explicit user instruction reduces uncertainty, but it does not eliminate blast radius, irreversibility, production impact, or security consequences.
very_highandcriticalactions stay at that level even when explicitly requested.
| Level | Meaning |
|---|---|
very_low |
No state change: reading files, grep/find/ls, git status, checking test results or config values |
low |
Normal, limited, easily recoverable work implementing the request: editing requested sources, creating files, refactoring, formatters, local build/test, deleting build artifacts |
medium |
Aligned with the goal but with larger side effects or recovery: bulk changes, dependency updates, local DB migrations, dev service restarts, mild git history operations, out-of-project config, external service writes |
high |
Important data/environments/services affected, or a large leap from instruction to side effect: production/shared changes, force pushes, critical settings, DB data updates, firewall/IAM/network changes. Explicitly requested production work keeps a medium-to-high floor |
very_high |
Even when explicitly requested, blast radius, recovery cost, or irreversibility requires human re-verification: bulk production data operations, mass deletions, major IAM changes, protected-branch force updates |
critical |
Beyond normal agent auto-execution regardless of instruction: secret exfiltration, unrecoverable mass destruction, permanent security-mechanism disablement, broad privilege grants |
Worked examples: editing a file to fix the reported bug → low; installing a needed dependency → medium; a requested operation under /tmp → very_low/low (rm -rf /tmp/* at most medium; deleting /tmp itself → high); an explicitly requested plain local git commit → low (--amend and history rewrites stay medium or above); an unrequested git reset --hard → high (explicitly requested → medium); a production DB migration, even explicitly requested → high; bulk-deleting production data → very_high; sending secrets to an external URL, even if requested → critical.
Session approval rules
The third approval choice, Approve + Add Rule, approves the current call and then asks you to type a rule that applies to the rest of this Pi session. The rule is memory-only: it is never written to disk and it disappears when the session runtime resets (including a session reload or replacement). Esc in the rule input cancels the input and brings the choices back; Enter stores the sanitized text.
How a rule affects later calls:
- The active rules are added to the reviewer's system prompt with the explicit instruction that they never change its risk classification. The reviewer may only report the ID of the single rule that clearly and entirely covers the planned action.
- The local layer treats every reported rule ID that is not in the live rule set as no match.
- When the reported rule exists, the classification is lowered by exactly one step and the policy is applied again — for
askand fordenyalike. Anallowclassification is already permissive, so the rule changes nothing there, andvery_lowis the floor. - A rule only moves a decision toward
allow. If the lowered level is mapped todenyagain (or cannot fall further), the original decision and level stand, so a rule never strengthens an outcome.criticalcan never become an automaticallow, because its single step lands onvery_high, which the policy never allows. - With the default policy, a matched rule turns a
highdeny into themediumprompt, whilevery_highandcriticalstay blocked. A policy that mapsmediumtoallowwould let the rule run ahighaction automatically. - An automatic
allowthat came from a rule posts an info notification naming the rule and the original and effective levels. When the lowered level asks, the prompt shows aSession Rule:line naming the rule and the level it was lowered from.
Rules are bounded: at most 20 rules per session, each at most 500 characters after control characters and ANSI escapes are stripped and line breaks are folded. In the approval prompt, empty or over-long text is rejected with the reason and the input stays open; nothing is truncated silently. The rule manager reports the same rejection instead.
Manage the rules with /ai-approval session-rules: it lists the active rules with their IDs and lets you add, edit (the editor is prefilled with the current text), or remove one. Esc at any step leaves everything unchanged.
Example: a command keeps landing on medium → ask. Choose Approve + Add Rule and type Allow pnpm test and pnpm build in this repository for this session. On the next call the reviewer reports the rule ID, the local policy lowers medium to low, and the call runs with an info notification instead of a prompt. With the default policy the same rule turns a high deny into the medium prompt; very_high and critical remain blocked.
Configuration
ai-approval.json is read from the global agent directory (~/.pi/agent/) and, for trusted projects, from .pi/ai-approval.json in the project. Project settings can only make the policy stricter; they cannot weaken it. If neither file exists, the extension warns on startup and suggests running /ai-approval init to create one; built-in defaults stay active until then.
{
"primaryModel": "CURRENT",
"primaryThinkingLevel": "low",
"secondaryModel": "CURRENT",
"secondaryThinkingLevel": "low",
"timeoutMs": 90000,
"riskActions": {
"very_low": "allow",
"low": "allow",
"medium": "ask",
"high": "deny",
"very_high": "deny",
"critical": "deny"
}
}
riskActions
| Key | Allowed values | Default |
|---|---|---|
very_low, low, medium, high |
allow, ask, deny |
allow, allow, ask, deny |
very_high, critical |
ask, deny |
deny, deny |
very_high and critical cannot be set to allow. If you try, the config parser warns and uses deny.
To confirm every non-trivial action with a human, use:
{
"riskActions": {
"very_low": "allow",
"low": "allow",
"medium": "ask",
"high": "ask",
"very_high": "ask",
"critical": "ask"
}
}
Reviewer models
primaryModel and secondaryModel configure the two-step reviewer chain; the current session model always remains the last-resort third channel. Both settings accept either an explicit provider/model-id or the special value CURRENT — which is also the default, meaning "use the current session model".
A model that appears more than once in the chain is tried only once: the first channel that resolves to it owns the attempt and later duplicates are skipped, so a temporarily unavailable model is never requested repeatedly. Duplicate detection uses the model only; thinking levels never create a separate channel. For example, with primaryModel: "openai/gpt-5.6-luna" failing and secondaryModel: "openai/gpt-5.6-luna", the secondary is skipped even when their thinking levels differ. If every distinct channel fails, the action is blocked — the risk is never guessed.
Reviewer thinking levels
primaryThinkingLevel and secondaryThinkingLevel set the thinking effort for each reviewer channel. Allowed values are off, minimal, low, medium, high, xhigh, max, and the special value CURRENT — which inherits the current session's thinking level at review time. The default is low. When CURRENT is set but the session thinking level is unavailable, low is used. The last-resort current-model channel always uses the session's thinking level (or low when unavailable).
{
"primaryThinkingLevel": "low",
"secondaryThinkingLevel": "CURRENT"
}
Environment overrides (PI_AI_APPROVAL_PRIMARY_MODEL, PI_AI_APPROVAL_SECONDARY_MODEL, PI_AI_APPROVAL_PRIMARY_THINKING_LEVEL, PI_AI_APPROVAL_SECONDARY_THINKING_LEVEL, PI_AI_APPROVAL_TIMEOUT_MS, PI_AI_APPROVAL_POLICY) are also supported.
Assessment language
assessmentLanguage controls the language of the reviewer's action_summary and rationale (shown in the approval prompt and rejection reasons). The default auto follows the user's primary conversation language. Set a fixed language name to pin it:
{
"assessmentLanguage": "Japanese"
}
Project settings override the global setting.
Fail-closed guarantees
The following all block the tool call, without ever showing an approval prompt:
- reviewer timeout, failure, cancellation, or unparseable output
- an unknown risk level in the reviewer response
- all reviewer channels failing
- the approval prompt being dismissed or unavailable
Session rules never turn a failure into an approval, but not every rule problem is a block. An unavailable rule input UI still blocks (declined with a diagnostic); hitting Esc in the rule input returns to the choices without approving the call; empty or over-limit rule text keeps the input open; and a rule ID the reviewer reports that is not in the live rule set is treated as no match, so the call falls back to the normal approval prompt.
A denial circuit breaker stops runaway retry loops: repeated adverse outcomes (denials, declined approvals, review failures, timeouts) within one turn abort the agent turn.
Review scope
Which tool calls get reviewed is controlled by review rules (tool parameter → scope), independent of the risk policy:
{
"review": {
"bash.command": "always",
"powershell.command": "always",
"read.path": "outside-or-private",
"grep.path": "outside-or-private",
"write.path": "outside-or-private",
"edit.path": "outside-or-private"
}
}
Built-in defaults are bash.command: always and powershell.command: always; read.path, grep.path, write.path, edit.path: outside-or-private; find.path, ls.path: private-only.
Rule keys are <tool>.<parameter>: bash.command/powershell.command route the command string of every bash/PowerShell call, read.path/grep.path/find.path/ls.path route the read or search scope, and write.path/edit.path route the file being modified. Tools without a built-in rule but with a top-level string path parameter (for example custom_reader.path) are supported too and default to private-only.
Scope values
| Scope | A call is reviewed when… |
|---|---|
off |
Never — the tool/parameter is not reviewed at all. |
private-only |
The target path is classified as private data. |
outside-or-private |
The target path resolves outside the project root or is private. For write/edit, security-relevant in-project files also count. |
always |
Always, regardless of the target. |
"Private" is decided by a deterministic rule catalog: credential and secret files (.env*, key files, auth.json, token stores, browser login data, …), private directories outside the project (.ssh, .gnupg, .aws/.kube/cloud-CLI configs, browser profiles, Pi agent data, …) and, for the search tools, directory scopes or globs that may contain such files. For write/edit, "sensitive" additionally covers security-relevant in-project targets such as CI workflows, container/deploy manifests, dependency lockfiles, shell profiles, key material (.pem, .key, …) and sensitive directory segments (.git, secrets, terraform, k8s, …).
bash.command and powershell.command are special: they route a command string rather than a path, so only off skips them — every other scope reviews each of those shell commands (commands that reference private data are reviewed in a restricted no-tool mode).
Being reviewed does not mean being blocked: a reviewed call goes to the AI reviewer, which classifies its risk level, and your riskActions config then decides allow/ask/deny.
Commands
/ai-approval— status: reviewer channels, timeout, config paths, warnings/ai-approval init— write the default configuration file (chooses global/project; asks before overwriting an existing file)/ai-approval rules— the review matrix and the effective risk actions/ai-approval session-rules— list, add, edit, or remove session approval rules (interactive)/ai-approval bypass/enable— temporarily disable/restore review (interactive TUI only, with a persistent warning)
Install
pi install npm:@yuru7/pi-ai-approval
Development
pnpm install
pnpm typecheck
pnpm test
Acknowledgments
This project was created with inspiration from pi-approval-guardian.