Tool Approval

Control tool execution approval in PraisonAI CLI

PraisonAI asks before running anything dangerous: a fresh install on an interactive terminal will prompt you the first time a tool wants to touch your files or shell.

Diagram

Yes

Approved

Denied

reason?

No / pipe / CI

PRAISONAI_TOOL_SAFETY=off
or –dangerously-skip-approval

👤 Agent calls
shell/file tool

🖥️ Interactive
terminal?

🔒 Ask the user

✅ Tool runs

🚫 Tool denied

Default Behaviour

PraisonAI is safe by default: a fresh install on an interactive terminal asks before running dangerous tools (shell exec, file writes, code execution). Pipes, CI runners, and redirected I/O fall back to deny by default — dangerous tools never run unattended without an explicit policy.

ContextDefaultWhat you see
Interactive terminal (TTY)AskAn approval prompt appears the first time a dangerous tool is called
Pipe / CI / non-TTYDenyThe dangerous tool is hard-denied before it runs
PRAISONAI_TOOL_SAFETY=off setBypassLegacy ungated behaviour; nothing is gated
approval=False (Python)DenySame as non-TTY: deny-by-default, no prompt
approval="bypass" (Python)BypassSame as PRAISONAI_TOOL_SAFETY=off
python
from praisonaiagents import Agent

# Safe-by-default: on a TTY, asks; off-TTY, denies — no kwargs needed
agent = Agent(name="Dev Agent", instructions="Help with file ops",
              tools=["write_file", "execute_command"])
agent.start("create a hello.txt file")

A session counts as interactive only when both sys.stdin and sys.stdout are TTYs. This avoids surprises in CI / Docker / scheduled-task contexts.

Reject with a Reason

When you reject a tool call, PraisonAI can capture a one-line explanation and hand it back to the agent as the tool’s result, so the next turn course-corrects instead of retrying blindly. On the terminal ConsoleBackend, pick [d] deny & redirect to open the What should the agent do instead? follow-up; the Rich prompt and Textual TUI ask for an optional reason after Reject.

Allow execute_command?  [o] once   [s] this session   [a] always (bash:git status *)   [n] no   [d] deny & redirect
Choice [o/s/a/n/d] (n): d
What should the agent do instead?: don't touch production.env — use staging.env instead

Leave the follow-up prompt blank to reject without guidance — the plain-denial behaviour is unchanged.

FrontendReject promptNotes
Rich (default terminal)Asks for an optional reason after choosing RejectReason threaded into the reject response
Textual (TUI)Reject dialog takes an optional reasonReason threaded into the reject response
Console backendFirst-class [d] deny & redirect key that opens a second promptDeny and steer in one keystroke

On the console path, the guidance travels as ApprovalDecision.feedback (a new Optional[str] field on ApprovalDecision, defaulting to None). When present, tool_execution.py rewrites the denied tool’s error_msg to "Tool '<name>' was denied by the user. Do not retry it as-is. User guidance: <feedback>", so the agent’s next turn sees the correction as the tool’s result — not just a log line. The Rich/Textual TUI still surfaces the same value through ApprovalResponse.reason and the praisonai_code wrapper (InteractiveCLIApprovalBackend) now sets feedback on the returned decision as well, keeping the two paths in sync.

Bypassing the Prompt

Per-run bypass — --dangerously-skip-approval

For one-off runs you trust completely:

bash
praisonai code "rename src/ to lib/" --dangerously-skip-approval

This is identical to --no-safe. Both do two things:

  • Set env vars — PRAISON_APPROVAL_MODE=auto, PRAISONAI_TOOL_SAFETY=off — consumed by the CLI’s own tool wiring.
  • Register AutoApproveBackend on the approval registry — consumed by the core @require_approval decorator that gates critical tools like acp_execute_command. Without this the flag was a no-op for any registry-decorated tool, so a scripted run with no TTY auto-denied every prompt while still exiting 0.

A safe-mode run removes the AutoApproveBackend a prior --no-safe installed in the same process (REPL/worker/test), and removes only that backend — a caller-supplied backend or one installed by --plan is preserved. A per-agent backend still wins (the core consults Agent(approval=…) first), and --plan remains mutually exclusive with --no-safe / --dangerously-skip-approval (exits 1).

Session-wide bypass — PRAISONAI_TOOL_SAFETY=off

For a whole shell session:

bash
export PRAISONAI_TOOL_SAFETY=off
praisonai code "..."   # all calls in this shell skip the safe-default ask

Accepted values that bypass: off, full, none, 0, false (case-insensitive). Any other value falls back to the default preset.

Python-level bypass — approval="bypass"

python
from praisonaiagents import Agent

agent = Agent(name="Dev Agent", approval="bypass", tools=["write_file", "execute_command"])
agent.start("task")

Use approval=False instead if you want deny-by-default without the prompt (CI-style behaviour from inside a TTY-attached process).

Declarative Permissions

Pre-declare allow/deny rules for CI-safe runs. See Declarative Permissions.

bash
praisonai run agents.yaml \
  --allow 'read:*' \
  --allow 'bash:git *' \
  --deny 'bash:rm *' \
  --permission-default ask
FlagDescription
--allow <pattern>Allow matching tool calls (repeatable)
--deny <pattern>Deny matching tool calls (repeatable)
--permissions <file>Load rules from YAML or JSON
--permission-default <action>Default for unmatched patterns (allow, deny, ask)

Overview

Certain tools are marked with risk levels and require approval before execution:

Risk LevelToolsDefault Behavior
CRITICALexecute_command, kill_process, execute_codeAlways prompts
HIGHwrite_file, delete_file, move_file, copy_file, edit_file, apply_patchAlways prompts
MEDIUMevaluate, crawl, scrape_pageAlways prompts
LOW(none by default)Always prompts

YAML Configuration

Configure approval settings in your YAML configuration file using the approval: block:

yaml
permissions:
  "read:*": allow
  "bash:rm *": deny

approval:
  enabled: true                    # bool, default false
  backend: "console"               # console | slack | telegram | discord | webhook | http | agent | auto | none
  approve_all_tools: false         # bool, default false
  timeout: 60                      # float seconds, default null (no timeout); accepts "none"
  approve_level: "high"            # low | medium | high | critical, default null
  guardrails: "..."                # optional guardrail description

Shorthand Forms

yaml
approval: true                # → enabled, console backend
approval: false               # → disabled
approval: slack               # → enabled, slack backend
approval: null                # → disabled

Legacy YAML Aliases (Backward Compatible)

Legacy keyNew key
backend_namebackend
all_toolsapprove_all_tools
approval_timeouttimeout

Primary keys win when both are present.

Validation

Unknown keys now raise ValueError with the list of allowed keys — silent typos no longer pass through undetected.

CLI ⇄ YAML ⇄ Python Mapping

All three surfaces use the same unified configuration:

CLI flagYAML keyPython field
--trustN/A (use backend: auto)backend="auto", enabled=True
--approval <backend>backend: <backend>backend="<backend>", enabled=True
--approve-all-toolsapprove_all_tools: trueapprove_all_tools=True
--approval-timeout <sec>timeout: <sec>timeout=<sec>
--approve-level <level>approve_level: <level>approve_level="<level>"
--guardrail "<text>"guardrails: "<text>"guardrails="<text>"

praisonai code Safety Flags

FlagDefaultEffect
--safe / --no-safe--safe (ON)Safe mode ON by default. --no-safe sets PRAISONAI_TOOL_SAFETY=off and restores legacy ungated behaviour.
--dangerously-skip-approvalOFFSkip all approval prompts and run dangerous tools unguarded. Equivalent to --no-safe.
--planOFFRead-only — every mutating tool is denied. Discoverable alias for --approval plan (PermissionMode.PLAN). Cannot be combined with --no-safe / --dangerously-skip-approval (fails closed).
--trustOFFAuto-approve all tool executions.
--approval <backend>—Route approvals to a backend (console, slack, …).

--plan is the opposite of --dangerously-skip-approval — it denies the mutating class of tools outright rather than approving them all. See Permission Modes.

What safe mode does

praisonai code --safe writes PRAISON_APPROVAL_MODE=prompt. The CLI’s tool loader accepts this value (plus auto / manual / scoped); any other string is logged and coerced to prompt. The in-wrapper runtime still orchestrates ACP plans in auto, but the core @require_approval gate on each tool is the sole approval point — it prompts on a TTY and denies without one. edit_file / apply_patch only get a standing approval when auto is combined with an explicit edit-group request.


Auto-Approve All Tools (--trust)

Use the --trust flag to auto-approve all tool executions without prompting:

bash
praisonai "hello world" --trust
Trust mode auto-approves all tools example

Usage

bash
# Auto-approve all tools (use with caution!)
praisonai "run ls -la command" --trust

Output:

⚠️  Trust mode enabled - all tool executions will be auto-approved
Tools used: execute_command
[directory listing...]

Level-Based Approval (--approve-level)

Use --approve-level to auto-approve tools up to a specific risk level:

bash
# Auto-approve low, medium, and high risk tools
# Still prompt for critical tools
praisonai "write to file and run command" --approve-level high

Available levels:

  • low – Only auto-approve low risk tools
  • medium – Auto-approve low and medium risk tools
  • high – Auto-approve low, medium, and high risk tools
  • critical – Auto-approve all tools (same as --trust)

Examples

bash
# Auto-approve only low risk tools
praisonai "task" --approve-level low

# Auto-approve up to medium risk
praisonai "task" --approve-level medium

# Auto-approve up to high risk (prompt for critical)
praisonai "task" --approve-level high

# Auto-approve everything (same as --trust)
praisonai "task" --approve-level critical

Approval Backend (--approval)

Use --approval to route tool approvals to a specific backend — Slack, Telegram, Discord, a webhook, or more:

bash
# Route approvals to Slack
praisonai "deploy to production" --approval slack

# Route approvals to Telegram
praisonai "delete old logs" --approval telegram

# Auto-approve everything (same as --trust)
praisonai "run tests" --approval auto

# Interactive console prompt (default)
praisonai "clean up files" --approval console

Available Backends

ValueBackendRequired Env Vars
consoleInteractive terminal prompt (default) — see Interactive Tool Approval for the once/session/always scope choices—
slackSlack Block Kit message + reply pollingSLACK_BOT_TOKEN, SLACK_CHANNEL
telegramTelegram inline keyboard + pollingTELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID
discordDiscord embed + text reply pollingDISCORD_BOT_TOKEN, DISCORD_CHANNEL_ID
webhookPOST to HTTP endpoint + poll for decisionAPPROVAL_WEBHOOK_URL
httpLocal web dashboard (browser-based)—
agentDelegate to an AI reviewer agent—
autoAuto-approve all (same as --trust)—
noneDisable approval entirely—

Works With All CLI Commands

bash
# Direct prompt
praisonai "task" --approval slack

# Run command
praisonai run "task" --approval telegram

# Chat / TUI
praisonai chat --approval discord

Programmatic Control

python
from praisonaiagents import Agent

# Safe-by-default (TTY = ask, non-TTY = deny)
agent = Agent(name="Dev Agent", tools=["write_file", "execute_command"])

# Deny-by-default without any prompt (CI-style)
agent = Agent(name="CI Agent", approval=False, tools=["write_file"])

# Full bypass — legacy ungated behaviour
agent = Agent(name="Trusted Agent", approval="bypass", tools=["write_file"])

Custom approval callbacks:

python
from praisonaiagents.approval import (
    set_approval_callback,
    ApprovalDecision,
    remove_approval_requirement,
    add_approval_requirement
)

# Auto-approve all tools
def auto_approve(function_name, arguments, risk_level):
    return ApprovalDecision(approved=True, reason="Auto-approved")

set_approval_callback(auto_approve)

# Level-based approval
def level_approve(function_name, arguments, risk_level):
    levels = {"low": 1, "medium": 2, "high": 3, "critical": 4}
    if levels.get(risk_level, 4) <= levels["high"]:
        return ApprovalDecision(approved=True)
    return ApprovalDecision(approved=False, reason="Too risky")

set_approval_callback(level_approve)

# Remove approval requirement for specific tool
remove_approval_requirement("execute_command")

# Add approval requirement for custom tool
# Custom tools registered here are also honoured on the interactive ask path (ConsoleBackend)
add_approval_requirement("my_dangerous_tool", risk_level="high")

Risk Level Reference

Critical Risk Tools

  • execute_command – Run shell commands
  • kill_process – Terminate processes
  • execute_code – Execute arbitrary code

High Risk Tools

  • write_file – Write to files
  • delete_file – Delete files
  • move_file – Move/rename files
  • copy_file – Copy files
  • edit_file – Edit existing file content
  • apply_patch – Apply a patch/diff to files
  • execute_query – Database queries

Medium Risk Tools

  • evaluate – Evaluate expressions
  • crawl – Web crawling
  • scrape_page – Web scraping

Best Practices

Use deny-by-default in CI

Set approval=False (or let the non-TTY default kick in automatically) so an agent run in CI never silently executes a destructive tool, even on a misconfigured runner that mistakenly attaches a TTY.

python
from praisonaiagents import Agent

# Explicit deny-by-default for CI pipelines
agent = Agent(name="CI Agent", approval=False, tools=["write_file", "execute_command"])
agent.start("run tests and update report")
Never set PRAISONAI_TOOL_SAFETY=off in a shared shell profile

PRAISONAI_TOOL_SAFETY=off is a per-session bypass intended for one-off trusted runs. Putting it in ~/.bashrc or ~/.zshrc re-introduces every safety gap that the safe-by-default system closed — any agent running in that shell will execute dangerous tools unguarded.

For development/testing: use –trust for faster iteration

When you’re actively monitoring the AI’s actions, --trust speeds up iteration by skipping prompts entirely.

bash
praisonai "refactor utils.py" --trust
For production scripts: use –approve-level high

Allow file operations but still require approval for shell commands:

bash
praisonai "generate and save report" --approve-level high

Related Features