> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Approval Protocol

> Extensible tool-execution approval system for agents

Agents can require human approval before executing dangerous tools. Set `approval=True` on any agent to auto-approve, or pass a custom backend for console, webhook, Slack, or any approval channel.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

agent = Agent(name="assistant", instructions="Ask before destructive tools.", approval=True)
agent.start("List files in the current directory.")
```

<Note>
  **Button-based approvals are now available.** On channels that implement the [`SupportsPresentation`](/docs/docs/features/bot-presentations) protocol (Telegram, Slack, Discord), approval prompts can render as inline buttons instead of relying on `yes`/`no` text replies. Use `MessagePresentation.approval(...)` to build the prompt. The text-keyword backends documented on this page continue to work unchanged — they are the recommended fallback for channels that do not yet support presentations. See [Interactive Bot Messages](/docs/docs/features/bot-presentations).

  **Button-based approvals** (via `MessagePresentation.approval(...)`) are actor-bound by default in shared chats. **Text-keyword backends** (`TelegramApproval`, `SlackApproval`, `DiscordApproval`) require an explicit `allowed_approvers=[...]` allowlist (or `TELEGRAM_APPROVERS` / `SLACK_APPROVERS` / `DISCORD_APPROVERS` env var) to lock down who may resolve an approval — added in [PR #2582](https://github.com/MervinPraison/PraisonAI/pull/2582). Without an allowlist, any responder in the group can approve. See [Interactive Callback Authorization](/docs/docs/features/interactive-callback-authorization).
</Note>

> For unified ApprovalSpec configuration (YAML/CLI/Python), see the [Approval documentation](/docs/features/approval). Backend selection uses the same string values in `--approval` and YAML `backend:` fields.

The user enables approval on an agent; each dangerous tool call pauses until the configured backend allows or denies it.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Approval Protocol"
        Agent[Agent] --> A[🔧 Tool Call]
        A --> B{🔒 Required?}
        B -->|No| D[✅ Execute]
        B -->|Yes| C[📋 Backend]
        C -->|Approved| D
        C -->|Denied| E[❌ Blocked]
    end

    classDef agent fill:#8B0000,color:#fff
    classDef tool fill:#189AB4,color:#fff

    class Agent agent
    class A,B,C,D,E tool
```

## Quick Start

<Steps>
  <Step title="Auto-Approve (Bots / Trusted Envs)">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent

    agent = Agent(
        name="bot",
        instructions="You are a helpful assistant.",
        approval=True
    )
    agent.start("List files in current directory")
    ```
  </Step>

  <Step title="Custom Approval Backend">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent
    from praisonaiagents.approval import ApprovalRequest, ApprovalDecision

    class WebhookBackend:
        async def request_approval(self, request: ApprovalRequest) -> ApprovalDecision:
            # POST to your approval service
            return ApprovalDecision(approved=True, approver="webhook")

        def request_approval_sync(self, request: ApprovalRequest) -> ApprovalDecision:
            return ApprovalDecision(approved=True, approver="webhook")

    <Warning>
    **Calling `request_approval_sync()` from inside an async function is no longer supported.** As of PR #1583, both `WebhookApproval.request_approval_sync()` and `HTTPApproval.request_approval_sync()` delegate to `praisonai._async_bridge.run_sync()`, which raises `RuntimeError` when called from a running event loop. Use `await approval.request_approval(request)` from async code; reserve `request_approval_sync` for synchronous entry points (CLI, sync test harnesses).
    </Warning>

    agent = Agent(
        name="bot",
        instructions="You are a helpful assistant.",
        approval=WebhookBackend()
    )
    agent.start("List files in current directory")
    ```
  </Step>

  <Step title="Default (Console Prompt)">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent

    # No approval= param → dangerous tools prompt in terminal
    agent = Agent(
        name="interactive",
        instructions="You are a helpful assistant.",
    )
    agent.start("Delete temporary files")
    ```
  </Step>
</Steps>

***

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Agent
    participant Backend
    participant Registry

    Agent->>Agent: Check self._approval_backend
    alt Agent has approval backend
        Agent->>Backend: request_approval(ApprovalRequest)
        Backend-->>Agent: ApprovalDecision
    else No agent backend (fallback)
        Agent->>Registry: approve_sync(agent_name, tool, args)
        Registry->>Registry: Check: required? env? yaml? already?
        alt Fast path
            Registry-->>Agent: ApprovalDecision(approved=True)
        else Needs backend
            Registry->>Backend: request_approval(ApprovalRequest)
            Backend-->>Registry: ApprovalDecision
            Registry-->>Agent: ApprovalDecision
        end
    end
```

| Step                 | What Happens                                                    |
| -------------------- | --------------------------------------------------------------- |
| **Agent backend**    | `approval=True` or custom backend on the Agent is checked first |
| **Required?**        | Only tools in the dangerous-tools set need approval             |
| **Env check**        | `PRAISONAI_AUTO_APPROVE=true` skips all prompts                 |
| **YAML check**       | Tools listed in YAML `approve` field are auto-approved          |
| **Already approved** | Once approved in a session, no re-prompt                        |
| **Backend**          | ConsoleBackend (default), AutoApproveBackend, or your custom    |

***

## Configuration Options

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    subgraph "Choose Your Approval Strategy"
        PARAM["🤖 Agent Parameter<br/>Agent(approval=True)"]
        ENV["🌍 Environment Variable<br/>PRAISONAI_AUTO_APPROVE=true"]
        GLOBAL["🔧 Global Registry<br/>set_backend(AutoApproveBackend())"]
        CUSTOM["🔌 Custom Backend<br/>Agent(approval=MyBackend())"]
    end

    classDef param fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef env fill:#10B981,stroke:#7C90A0,color:#fff
    classDef global fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef custom fill:#189AB4,stroke:#7C90A0,color:#fff

    class PARAM param
    class ENV env
    class GLOBAL global
    class CUSTOM custom
```

### Agent Parameter (Recommended)

| Value                     | Behavior                                        |
| ------------------------- | ----------------------------------------------- |
| `approval=True`           | Auto-approve all dangerous tools for this agent |
| `approval=False` / `None` | Use registry fallback (default: console prompt) |
| `approval=MyBackend()`    | Use a custom approval backend for this agent    |

### Other Methods

| Method                                                         | Scope        | Use Case                         |
| -------------------------------------------------------------- | ------------ | -------------------------------- |
| `PRAISONAI_AUTO_APPROVE=true`                                  | All agents   | CI/CD, testing                   |
| `get_approval_registry().set_backend(backend)`                 | All agents   | Global policy                    |
| `get_approval_registry().set_backend(backend, agent_name="x")` | Single agent | Registry-level per-agent control |
| YAML `approve: [tool1, tool2]`                                 | Per-task     | Declarative configs              |

***

## Built-in Backends

| Backend                  | Import                                                                      | Behavior                                       |
| ------------------------ | --------------------------------------------------------------------------- | ---------------------------------------------- |
| `AutoApproveBackend`     | `from praisonaiagents.approval.backends import AutoApproveBackend`          | Always approves                                |
| `ConsoleBackend`         | `from praisonaiagents.approval import ConsoleBackend`                       | Rich terminal prompt (default)                 |
| `AgentApproval`          | `from praisonaiagents.approval import AgentApproval`                        | Delegates to another AI agent                  |
| `SlackApproval`          | `from praisonai.bots import SlackApproval`                                  | Slack Block Kit + polling                      |
| `TelegramApproval`       | `from praisonai.bots import TelegramApproval`                               | Telegram inline keyboard + polling             |
| `DiscordApproval`        | `from praisonai.bots import DiscordApproval`                                | Discord embed + text reply polling             |
| `WebhookApproval`        | `from praisonai.bots import WebhookApproval`                                | POST to webhook + poll status                  |
| `HTTPApproval`           | `from praisonai.bots import HTTPApproval`                                   | Local web dashboard                            |
| `GatewayApprovalBackend` | `from praisonai_bot.gateway.gateway_approval import GatewayApprovalBackend` | Queue approvals into the gateway control plane |
| `CallbackBackend`        | `from praisonaiagents.approval import CallbackBackend`                      | Wraps a legacy callback function               |

***

## Gateway-wired approvals

Setting `channels.<platform>.allow_shell: true` with `auto_approve_shell: false` on a gateway channel wires one of these backends automatically — no Python required. If none of them resolve, the gateway installs a `CallbackBackend` that denies shell tools (`execute_command`, `shell_command`, `acp_execute_command`) so shell never silently auto-approves. See [Fail-closed behaviour](/docs/docs/features/bot-shell-execution#fail-closed-behaviour).

The routing precedence, exactly as implemented:

```
approval_mode == "gateway"                → GatewayApprovalBackend()
approval_mode == "http"                   → HTTPApproval(host, port)
approval_mode == "webhook" OR
  approval_webhook_url is set             → WebhookApproval(webhook_url)
                                             ↳ webhook mode with NO url:
                                               warn + fall through to gateway
channel_type == "slack"    + resolved id  → SlackApproval(...)
channel_type == "telegram" + resolved id  → TelegramApproval(...)
channel_type == "discord"  + resolved id  → DiscordApproval(...)
otherwise                                 → GatewayApprovalBackend()  (fallback)
```

<Card title="Bot Shell Execution" icon="terminal" href="/docs/docs/features/bot-shell-execution">
  Full field reference, per-platform ID resolution, env vars, and recipes.
</Card>

<Note>
  **Durable chat backends:** `SlackApproval`, `TelegramApproval`, `DiscordApproval`, `WebhookApproval`, and `HTTPApproval` each accept an optional `store=` parameter. Pass an `ApprovalStore` to persist the pending approval before polling, then call `backend.rehydrate()` on startup to recover in-flight approvals after a restart. The shared `DEFAULT_APPROVAL_TIMEOUT` (300s) from `praisonai_bot.bots._approval_base` is the single source of truth for the wait window. See [Durable Approvals](/docs/docs/features/durable-approvals#durable-chat-native-backends).
</Note>

***

## Custom Backend

Implement `request_approval` (async) and optionally `request_approval_sync` to create any approval channel:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonaiagents.approval import ApprovalRequest, ApprovalDecision

class SlackBackend:
    async def request_approval(self, request: ApprovalRequest) -> ApprovalDecision:
        approved = await self._ask_slack(
            f"Approve {request.tool_name} for {request.agent_name}?"
        )
        return ApprovalDecision(
            approved=approved,
            approver="slack",
            metadata={"channel": "#approvals"},
        )

    def request_approval_sync(self, request: ApprovalRequest) -> ApprovalDecision:
        # Sync fallback for non-async tool execution
        return ApprovalDecision(approved=True, approver="slack")

agent = Agent(
    name="slack-bot",
    instructions="You are a helpful assistant.",
    approval=SlackBackend()
)
```

***

## Slack Approval

Route tool approvals to Slack — get a rich message with tool details and reply **yes** or **no** to approve or deny.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Agent
    participant SlackApproval
    participant Slack

    Agent->>SlackApproval: request_approval(tool, args)
    SlackApproval->>Slack: Post Block Kit message
    Note over Slack: 🔒 Tool Approval Required<br/>Tool: execute_command<br/>Reply yes/no
    Slack-->>SlackApproval: User replies "yes"
    SlackApproval->>Slack: Update message ✅ Approved
    SlackApproval-->>Agent: ApprovalDecision(approved=True)
```

<Steps>
  <Step title="Install">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    pip install praisonai
    ```
  </Step>

  <Step title="Set Token">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    export SLACK_BOT_TOKEN=xoxb-your-token
    ```
  </Step>

  <Step title="Use It">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent
    from praisonai.bots import SlackApproval

    agent = Agent(
        name="assistant",
        instructions="You are a helpful assistant.",
        tools=[execute_command],
        approval=SlackApproval(channel="#approvals")
    )
    agent.start("Delete old log files")
    ```
  </Step>
</Steps>

<Tip>
  The Slack bot token needs `chat:write` and `channels:history` (or `im:history` for DMs) scopes.
</Tip>

### Configuration

| Parameter           | Default                            | Description                                                                                                                                                                                                                                                                                    |
| ------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`             | `SLACK_BOT_TOKEN` env              | Slack bot token (`xoxb-...`)                                                                                                                                                                                                                                                                   |
| `channel`           | Bot's own DM                       | Channel ID, name, or user ID                                                                                                                                                                                                                                                                   |
| `timeout`           | `300` (5 min)                      | Seconds to wait for response                                                                                                                                                                                                                                                                   |
| `poll_interval`     | `3.0`                              | Seconds between polls                                                                                                                                                                                                                                                                          |
| `allowed_approvers` | `SLACK_APPROVERS` env, else `None` | Iterable of Slack user IDs (or comma-separated string) permitted to approve/deny. `None` = any responder (legacy). Set this for shared channels.                                                                                                                                               |
| `store`             | `None`                             | Optional durable `ApprovalStore`. When set, the pending approval is persisted before polling and the decision recorded, so an outstanding approval survives a restart. Call `backend.rehydrate()` on startup to recover in-flight approvals. `None` keeps the legacy in-memory-only behaviour. |

Make Slack approvals restart-safe by adding `store=`:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonai.bots import SlackApproval, ApprovalStore

slack = SlackApproval(
    channel="#approvals",
    store=ApprovalStore(path="~/.praisonai/state/approvals.sqlite"),
)
```

### Approval Keywords

| Action      | Keywords                                                   |
| ----------- | ---------------------------------------------------------- |
| **Approve** | yes, y, approve, approved, ok, allow, go, proceed, confirm |
| **Deny**    | no, n, deny, denied, reject, block, stop, cancel, refuse   |

<Accordion title="Cross-Platform: Telegram Bot → Slack Approval">
  You can use **any** bot platform for user interaction while routing approvals to Slack:

  ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  from praisonaiagents import Agent
  from praisonai.bots import Bot, SlackApproval

  agent = Agent(
      name="assistant",
      instructions="You are a helpful assistant.",
      approval=SlackApproval(channel="U0ABPEV1HK8")  # DM to admin
  )

  # Users talk via Telegram; dangerous tools approved in Slack
  bot = Bot("telegram", agent=agent)
  bot.run()
  ```
</Accordion>

***

## Telegram Approval

Route tool approvals to Telegram — get a message with inline keyboard buttons.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Agent
    participant TelegramApproval
    participant Telegram

    Agent->>TelegramApproval: request_approval(tool, args)
    TelegramApproval->>Telegram: sendMessage + InlineKeyboard
    Note over Telegram: 🔒 Tool Approval Required<br/>✅ Approve  ❌ Deny
    Telegram-->>TelegramApproval: callback_query "approve"
    TelegramApproval->>Telegram: editMessageText ✅ Approved
    TelegramApproval-->>Agent: ApprovalDecision(approved=True)
```

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonai.bots import TelegramApproval

agent = Agent(
    name="assistant",
    tools=[execute_command],
    approval=TelegramApproval(chat_id="YOUR_CHAT_ID")  # token from TELEGRAM_BOT_TOKEN env
)
```

<Tip>
  **Group chat security:** Use `allowed_approvers` to restrict who can tap the Approve/Deny buttons. Without it, any group member can resolve the approval.

  ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  agent = Agent(
      name="ops",
      instructions="Run destructive commands only after approval.",
      approval=TelegramApproval(
          chat_id="-100999888",
          allowed_approvers=["999", "111"],  # only these Telegram user IDs may resolve
      ),
  )
  ```

  Or via environment variable (useful for containerised deployments):

  ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  export TELEGRAM_APPROVERS=999,111
  ```
</Tip>

| Parameter           | Default                               | Description                                                                                                                                                                                 |
| ------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`             | `TELEGRAM_BOT_TOKEN` env              | Telegram bot token                                                                                                                                                                          |
| `chat_id`           | *(required)*                          | Chat ID to send approvals to                                                                                                                                                                |
| `timeout`           | `300` (5 min)                         | Seconds to wait for response                                                                                                                                                                |
| `poll_interval`     | `2.0`                                 | Seconds between polls                                                                                                                                                                       |
| `allowed_approvers` | `TELEGRAM_APPROVERS` env, else `None` | Iterable of Telegram user IDs (or comma-separated string) permitted to tap Approve/Deny or reply with keywords. `None` = any responder (legacy). Set this for group chats.                  |
| `store`             | `None`                                | Optional durable `ApprovalStore`. When set, the pending approval survives a restart; call `backend.rehydrate()` on startup to recover it. `None` keeps the legacy in-memory-only behaviour. |

<Note>
  Since [PR #2582](https://github.com/MervinPraison/PraisonAI/pull/2582), callback taps are bound to both `message_id` **and** `chat_id`, so a same-`message_id` button in a different chat cannot resolve this approval. An unauthorised tap shows the presser a *"You are not authorized to approve this action."* alert.
</Note>

***

## Discord Approval

Route tool approvals to Discord — get a rich embed and reply **yes** or **no**.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonai.bots import DiscordApproval

agent = Agent(
    name="assistant",
    tools=[execute_command],
    approval=DiscordApproval(channel_id="YOUR_CHANNEL_ID")  # token from DISCORD_BOT_TOKEN env
)
```

| Parameter           | Default                              | Description                                                                                                                                                                                 |
| ------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`             | `DISCORD_BOT_TOKEN` env              | Discord bot token                                                                                                                                                                           |
| `channel_id`        | *(required)*                         | Channel ID to send approvals to                                                                                                                                                             |
| `timeout`           | `300` (5 min)                        | Seconds to wait for response                                                                                                                                                                |
| `poll_interval`     | `3.0`                                | Seconds between polls                                                                                                                                                                       |
| `allowed_approvers` | `DISCORD_APPROVERS` env, else `None` | Iterable of Discord user IDs (or comma-separated string) permitted to reply Approve/Deny. `None` = any responder (legacy). Set this for shared channels.                                    |
| `store`             | `None`                               | Optional durable `ApprovalStore`. When set, the pending approval survives a restart; call `backend.rehydrate()` on startup to recover it. `None` keeps the legacy in-memory-only behaviour. |

***

## Webhook Approval

POST approval requests to any HTTP endpoint and poll for decisions. Ideal for enterprise dashboards, CI/CD, and custom integrations.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Agent
    participant WebhookApproval
    participant Server

    Agent->>WebhookApproval: request_approval(tool, args)
    WebhookApproval->>Server: POST /approve {tool, args, risk}
    Server-->>WebhookApproval: {status: "pending"}
    loop Poll
        WebhookApproval->>Server: GET /approve/{request_id}
        Server-->>WebhookApproval: {approved: true}
    end
    WebhookApproval-->>Agent: ApprovalDecision(approved=True)
```

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import os
from praisonaiagents import Agent
from praisonai.bots import WebhookApproval

agent = Agent(
    name="assistant",
    tools=[execute_command],
    approval=WebhookApproval(
        webhook_url="https://your-app.com/api/approvals",
        headers={"Authorization": f"Bearer {os.environ['APPROVAL_WEBHOOK_TOKEN']}"},
    ),
)
```

| Parameter       | Default                    | Description                                                                                                                                                                                 |
| --------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `webhook_url`   | `APPROVAL_WEBHOOK_URL` env | URL to POST requests to                                                                                                                                                                     |
| `status_url`    | `webhook_url/{request_id}` | URL template for polling                                                                                                                                                                    |
| `headers`       | `{}`                       | Extra HTTP headers (e.g. auth)                                                                                                                                                              |
| `timeout`       | `300` (5 min)              | Seconds to wait                                                                                                                                                                             |
| `poll_interval` | `5.0`                      | Seconds between polls                                                                                                                                                                       |
| `store`         | `None`                     | Optional durable `ApprovalStore`. When set, the pending approval survives a restart; call `backend.rehydrate()` on startup to recover it. `None` keeps the legacy in-memory-only behaviour. |

<Tip>
  If the webhook returns `{"approved": true}` in the POST response, the decision is immediate — no polling needed.
</Tip>

***

## Agent Approval

Delegate approval decisions to another AI agent. The approver agent evaluates the tool call and responds with APPROVE or DENY.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonaiagents.approval import AgentApproval

# Security reviewer agent
reviewer = Agent(
    name="security-reviewer",
    instructions="Only approve low-risk read operations. Deny anything destructive.",
)

worker = Agent(
    name="assistant",
    tools=[execute_command],
    approval=AgentApproval(approver_agent=reviewer),
)
```

| Parameter        | Default       | Description                         |
| ---------------- | ------------- | ----------------------------------- |
| `approver_agent` | Auto-created  | Agent instance to evaluate requests |
| `llm`            | `gpt-4o-mini` | LLM for default approver agent      |

<Note>
  `AgentApproval` lives in the core SDK (`praisonaiagents.approval`) — no extra dependencies needed.
</Note>

***

## HTTP Approval

Serve a local web dashboard for approvals. Open the URL in your browser and click Approve or Deny.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonai.bots import HTTPApproval

agent = Agent(
    name="assistant",
    tools=[execute_command],
    approval=HTTPApproval(host="127.0.0.1", port=8899),
)
# When a tool needs approval, open http://127.0.0.1:8899/approve/<request_id>
```

| Parameter | Default       | Description                                                                                                                                                                                 |
| --------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `host`    | `127.0.0.1`   | Bind address                                                                                                                                                                                |
| `port`    | `8899`        | Port to listen on                                                                                                                                                                           |
| `timeout` | `300` (5 min) | Seconds to wait for response                                                                                                                                                                |
| `store`   | `None`        | Optional durable `ApprovalStore`. When set, the pending approval survives a restart; call `backend.rehydrate()` on startup to recover it. `None` keeps the legacy in-memory-only behaviour. |

***

## Multi-Agent Example

Different agents can have different approval policies:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonaiagents.approval import AgentApproval
from praisonai.bots import SlackApproval, TelegramApproval, HTTPApproval

# Bot agent: auto-approve everything
bot = Agent(name="bot", approval=True)

# Interactive agent: console prompt (default)
interactive = Agent(name="interactive")

# Slack-approved agent
slack_agent = Agent(name="slack-approved", approval=SlackApproval(channel="#approvals"))

# Telegram-approved agent
telegram_agent = Agent(name="tg-approved", approval=TelegramApproval(chat_id="123456"))

# AI-reviewed agent (no human needed)
reviewer = Agent(name="reviewer", instructions="Only approve safe read operations")
ai_agent = Agent(name="ai-reviewed", approval=AgentApproval(approver_agent=reviewer))

# Local dashboard agent
http_agent = Agent(name="dashboard", approval=HTTPApproval(port=8899))
```

***

## Registry (Advanced)

For global or centralized approval control, use the registry directly:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, AutoApproveBackend
from praisonaiagents.approval import get_approval_registry

registry = get_approval_registry()

# Global auto-approve
registry.set_backend(AutoApproveBackend())

# Per-agent override via registry
registry.set_backend(AutoApproveBackend(), agent_name="trusted-bot")
```

<Note>
  The `approval=` parameter on `Agent` takes priority over the global registry.
  If an agent has `approval=True`, it will auto-approve regardless of registry settings.
</Note>

***

## Dangerous Tools (Default)

These tools require approval by default:

| Tool              | Risk Level |
| ----------------- | ---------- |
| `execute_command` | critical   |
| `kill_process`    | critical   |
| `execute_code`    | critical   |
| `write_file`      | high       |
| `delete_file`     | high       |
| `move_file`       | high       |
| `execute_query`   | high       |
| `crawl`           | medium     |
| `scrape_page`     | medium     |

Add or remove requirements:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.approval import get_approval_registry

registry = get_approval_registry()
registry.add_requirement("my_dangerous_tool", risk_level="high")
registry.remove_requirement("crawl")
```

***

## ApprovalStoreProtocol

Optional persistence layer for pending approvals — orthogonal to approval backends. Implement `persist`, `list_pending`, and `resolve` to survive restarts.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.approval import ApprovalStoreProtocol, ApprovalRequest, ApprovalDecision

class MyStore:
    async def persist(self, approval_id: str, request: ApprovalRequest, *, expires_at: float) -> None: ...
    async def list_pending(self): ...
    async def resolve(self, approval_id: str, decision: ApprovalDecision) -> None: ...
```

The wrapper ships `ApprovalStore` (SQLite) in `praisonai.bots`. See [Durable Approvals](/docs/docs/features/durable-approvals).

<Note>
  All five chat backends (`SlackApproval`, `TelegramApproval`, `DiscordApproval`, `WebhookApproval`, `HTTPApproval`) accept a `store=` and expose `rehydrate()` via the shared `DurableApprovalMixin` in `praisonai_bot.bots._approval_base`. See [Durable Chat-Native Backends](/docs/docs/features/durable-approvals#durable-chat-native-backends).
</Note>

***

## Best Practices

<AccordionGroup>
  <Accordion title="Use approval=True for bot agents">
    Set `approval=True` directly on agent constructors for unattended bots. This is the simplest, most agent-centric approach.
  </Accordion>

  <Accordion title="Use environment variable for CI/CD">
    Set `PRAISONAI_AUTO_APPROVE=true` in your CI environment to avoid blocking on prompts during automated testing.
  </Accordion>

  <Accordion title="Create custom backends for production">
    Build webhook or messaging backends that route approvals to the right team. The async protocol supports long-running approval flows.
  </Accordion>

  <Accordion title="Use per-agent backends for multi-agent systems">
    Different agents may need different approval policies. Pass different backends to each agent's `approval=` parameter.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="CLI Tool Approval" icon="terminal" href="/docs/docs/cli/tool-approval">
    `--trust`, `--approve-level`, and `--approval` CLI flags
  </Card>

  <Card title="Messaging Bots" icon="robot" href="/docs/docs/features/messaging-bots">
    Telegram, Discord, Slack, WhatsApp bots
  </Card>

  <Card title="Schedule Tools" icon="clock" href="/docs/tools/schedule-tools">
    Agent-centric scheduling tools
  </Card>

  <Card title="Tools" icon="wrench" href="/docs/docs/tools/tools">
    Built-in tools reference
  </Card>
</CardGroup>
