Quick Start
1
Simple Usage
2
With Configuration
[o] once— approve this exact call and prompt again next time (default; backward compatible).[s] session— auto-approve matching calls (same permission target) for the rest of this run. Never persisted — restarting the process starts over.[a] always— persist an allow-rule toapprovals.json. The suggested pattern in parentheses (e.g.bash:git status *,edit:src/app.py) is what gets saved; it is auto-derived from the tool + arguments.[n] no— deny (also selected on Ctrl-C / EOF).
The ConsoleBackend keystroke prompt offers no single “allow ALL uses of this tool” key — author a blanket
bash:* rule with praisonai permissions allow "bash:*" when you need one. The Rich/Textual TUI used by praisonai code / praisonai chat has a separate Always Allow All option for this — see Rich / Textual TUI.When approval is required
Approval runs when any of these apply:- The agent has
approval=True(or a CLI--approvalbackend) - The tool is in the default dangerous-tools list (e.g.
bash,write,delete) - The tool has
trust_level == "external"in the tool registry
Scope choice
Four options in one row — this diagram picks the right one.A nameless
always grant degrades to session. An always grant without an agent_name would match any later agent making the same target call, so the registry refuses to persist it and keeps it in-memory for this run only — which is why an “always” click sometimes doesn’t outlive the run.session grants are keyed by (agent_name, permission_target) and live in an in-memory set. clear_approved() wipes them, and they are never written to approvals.json.How the suggested pattern is derived
Thealways (…) hint isn’t magic — it maps the tool + arguments to a reusable target.
Session-only workflow
Press[s] to auto-approve a repeated call for the rest of the run — nothing is written to disk.
[a] instead when the grant should persist across runs.
Change Preview
Before you approve a file-mutating tool, PraisonAI shows a preview so you approve the actual change — not just the tool name. The same preview renders across the ConsoleBackend keystroke prompt, the Rich frontend, and the Textual dialog. Foredit / apply_patch, PraisonAI shows a unified diff — either the one the caller supplied (diff / patch argument), or one synthesised on the fly from old_string / new_string (the shape most edit_file calls take):
old_string / new_string (the default edit_file shape):
write (up to 2000 chars, then a \n... (truncated) sentinel is appended), the content is prefixed with the target path:
Preview content is passed through
rich.markup.escape, so diffs or new content containing bracket characters ([a-z], [TODO], etc.) render literally and never crash the approval dialog.Reject with a reason
When you deny a tool call inpraisonai code or praisonai chat (Rich mode), the TUI asks for an optional one-line reason. The agent receives that reason as the denied tool’s result and adjusts its next step — no more silent retries.
bash: rm -rf .venv, you deny with a reason, and the agent picks a safer alternative on its next turn.
How it flows
When the prompt appears
The reason is a plain string returned as the denied tool call’s result — it is not a system prompt, is not persisted to
approvals.json, and never affects future approvals. It only informs the agent’s very next decision on this call.Rich / Textual TUI (praisonai code, praisonai chat)
praisonai code and praisonai chat in Rich or Textual mode render approvals as a scrollable option list (Rich) or a button dialog (Textual) — not the single-key [o]/[s]/[a]/[n] prompt.
git status, the dialog shows the exact pattern each option will persist:
edit / apply_patch, or truncated content for write — above the approval options, so you approve the concrete change, not just the tool name. Rich draws it as a syntax-highlighted Panel (plain-text fallback when colour is unavailable); Textual shows it inside a scrollable region above the buttons. Non-mutating tools show no preview.
- Once — approve this exact call, ask again next time.
- Session Only — reuse the narrow command-scoped pattern in memory for this run only; never written to disk.
- Always Allow — persist the narrow command-scoped pattern (
shell_command:git status *for shell commands;edit:<path>/write:<path>for file tools; a literal single-use target when nothing safe can be derived). It never persiststool:*. - Always Allow All — the only option that persists a blanket
tool:<tool_name>rule (formerlyaction_type:*). Pick it deliberately when you truly want every future call of the tool approved. - Deny — reject the call, then prompts for an optional one-line reason so the agent knows why and can adjust its next step. Ctrl-C / EOF still counts as a plain denial with no reason.
Approving a single command no longer whitelists every use of that tool. Always Allow narrows to the exact command prefix by default; blanket access is a separate, clearly-labelled Always Allow All entry.
ApprovalDialog derives both the narrow and blanket patterns once in __init__ to guarantee they match.
Building a custom TUI on top of
InteractiveCore? Use derive_permission_pattern(request, scope=...) in praisonai_code/cli/interactive/events.py. scope="command" returns the narrow pattern (shell → prefix via core derive_pattern, e.g. shell_command:git status *; file tools → concrete path; anything unusable → literal single-use target). scope="tool" is the only path that emits blanket tool:<tool_name>.Approval modes
Persistence
Press[a] to write an allow-rule to approvals.json via PermissionManager (scoped to the approving agent). The suggested pattern (e.g. bash:git status *, edit:utils.py) is what gets saved:
[a] uses the shared suggest_scope_pattern helper so the CLI, YAML --allow/--deny, and Python PermissionManager all scope identically. Compound commands (&&, |, ;, $(...)) fall back to a literal single-use pattern so a persisted rule can only match the exact invocation you approved. For a blanket <tool>:* rule, author it directly with praisonai permissions allow "bash:*".
Session grants live in an in-memory _session_scoped_targets set and are cleared by clear_approved() at run teardown. Always grants persist across restarts and survive clear_approved().
Manage rules with:
approvals.json entry format
approvals.json entry format
Each entry carries
pattern, approved, scope, created_at, expires_at, agent_name, and a derived flag. derived: true marks approvals whose pattern was auto-generated by reusable command-prefix scopes — user-edited or hand-authored patterns save with derived: false. Old files without the field load cleanly (derived defaults to False).Non-interactive and CI
Best Practices
Start with plan for new repos
Start with plan for new repos
Use
--approval plan until you trust the agent’s behaviour in a codebase.Review external tools
Review external tools
Tools marked
external always prompt — verify third-party integrations before allowing.`Always allow` is now narrow by default — pick `Always Allow All` explicitly for a blanket rule
`Always allow` is now narrow by default — pick `Always Allow All` explicitly for a blanket rule
In both the ConsoleBackend keystroke prompt and the Rich/Textual TUI dialog, the default “Always allow” option persists the narrowest reusable pattern (e.g.
shell_command:git status *, edit:src/app.py) — never the blanket action_type:*. It covers every git status … variant but never expands to git push or other subcommands. To allow every future call of the tool, pick the distinct Always Allow All button (Textual) / tool:* menu entry (Rich), or author the rule directly with praisonai permissions allow "shell_command:*".Prefer [s] session over [a] always for one-off runs
Prefer [s] session over [a] always for one-off runs
[s] grants stay in memory and vanish when the process ends — ideal for a repeated call in a single task. Reserve [a] always for calls you want persisted across every future run.Skim the change preview before pressing [a]
Skim the change preview before pressing [a]
The preview is your last chance to catch an unintended edit. For
edit / apply_patch you get a unified diff (either supplied by the caller or synthesised from old_string / new_string). For write you get up to 2000 characters of the new content, prefixed with the target path. Read-only or non-file tools have no preview.Give a name to your agent before pressing [a]
Give a name to your agent before pressing [a]
A nameless
always grant can’t be persisted — it would match any later agent, so the registry keeps it in-memory for this run only. Set Agent(name=...) so [a] writes a durable, agent-scoped rule.Bot/chat-channel approvals
This page covers the CLI/terminal approval flow. When running PraisonAI on Telegram, Slack, or Discord, approvals render as interactive buttons and are actor-bound — only the requester and configured admins can resolve them.Interactive Callback Authorization
Lock approval buttons to specific users in shared chats — covers Telegram, Slack, and Discord bots.
derive_pattern — shared narrow-pattern derivation used by CLI, YAML, and Python rulesRelated
Permissions CLI
praisonai permissions referencePermission Modes
All modes for agents and CLI
Permissions Module
Python SDK API

