> ## 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.

# Declarative Permissions

> Pre-declare allow/deny rules in YAML, CLI, or Python for CI-safe agent runs

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

agent = Agent(name="ci-agent", instructions="Run safely with pre-declared tool permissions.")
agent.start("Run tests and deploy to staging.")
```

Declarative permissions let you pre-declare which tool calls to allow, deny, or ask about — so unattended CLI/CI runs stay safe without interactive prompts.

<Info>
  Since PR #2208, `bash:` / `shell:` rules are matched against every sub-operation in a compound command. See [Command-Aware Permissions](/docs/features/command-aware-permissions).
</Info>

The user starts an unattended run; declarative rules resolve each tool call to allow, deny, or ask.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    YAML[📄 agents.yaml] --> PM[🛡️ PermissionManager]
    CFG[📄 .praisonai/config.yaml] --> PM
    CLI[⌨️ CLI flags] --> PM
    PY[🐍 ApprovalConfig] --> PM
    PM --> Decision{allow / deny / ask}

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef process fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef ok fill:#10B981,stroke:#7C90A0,color:#fff

    class YAML,CFG,CLI,PY input
    class PM process
    class Decision ok

```

<Info>
  `deny` rules now also shape the tool surface advertised to the LLM (since v1.6.91). The model never sees tools it can't call — fewer denied-call loops, fewer wasted tokens. See [Approval › How tools are pruned from the LLM](/docs/features/approval#how-tools-are-pruned-from-the-llm).
</Info>

## Quick Start

<Steps>
  <Step title="YAML">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # agents.yaml
    permissions:
      "read:*": allow
      "bash:rm *": deny
      "*": ask

    agents:
      assistant:
        role: Assistant
        instructions: Help safely
    ```

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai run agents.yaml
    ```
  </Step>

  <Step title="CLI flags">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai run "deploy the app" \
      --allow 'read:*' \
      --allow 'bash:git *' \
      --deny 'bash:rm *' \
      --permission-default ask
    ```
  </Step>

  <Step title="Python">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent

    agent = Agent(
        name="CI Worker",
        instructions="Run safely in CI",
        approval={
            "permissions": {
                "read:*": "allow",
                "bash:rm *": "deny",
            },
        },
    )
    ```
  </Step>

  <Step title="In project config">
    Declare rules directly in `.praisonai/config.yaml` — automatically inherited by every `praisonai run` in the directory:

    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # .praisonai/config.yaml
    permissions:
      default: ask
      rules:
        - { pattern: "bash:git *", action: allow }
        - { pattern: "bash:rm *",  action: deny }
    ```

    Or use the flat mapping form:

    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # .praisonai/config.yaml
    permissions:
      "read:*": allow
      "bash:rm *": deny
      "*": ask
    ```
  </Step>
</Steps>

***

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant CI as CI Runner
    participant Agent
    participant Backend as CLI Approval Backend
    participant Mgr as PermissionManager

    CI->>Agent: praisonai run --no-tty
    Agent->>Backend: tool call (bash:git push)
    Backend->>Mgr: check("bash:git push")
    Mgr-->>Backend: allow
    Backend-->>Agent: approved
```

On each tool call, the backend builds a target string (`tool_name:arg`) and `PermissionManager.check()` returns **allow**, **deny**, or **ask**. Non-interactive runs honour declared rules; **ask** without a human present falls back to **deny**.

***

## Pattern Syntax

Patterns use `<tool_name>:<arg-glob>`:

| Pattern                | Meaning                                                      |
| ---------------------- | ------------------------------------------------------------ |
| `read:*`               | Any read tool call                                           |
| `bash:git *`           | Git shell commands                                           |
| `write:/etc/*`         | Writes under `/etc/` (including truncating redirections)     |
| `external_dir:/data/*` | Allow access to `/data/` when `workspace_root` is set        |
| `external_dir:~/*`     | Allow access to home directory when `workspace_root` is set  |
| `external_dir:*`       | Deny all out-of-workspace access (hard block, no ask prompt) |

### external\_dir: targets

`external_dir:<parent>/*` is emitted for any path that resolves outside `workspace_root`. Use these patterns to pre-authorise or hard-block external filesystem access without interactive prompts. See [Workspace Boundary](/docs/features/workspace-boundary).

### Compound shell commands

`bash:` and `shell:` targets are decomposed into their constituent operations — `&&`, `;`, `|`, subshells, `$(...)`, backticks, and truncating redirections. A single `deny` rule fires when the blocked executable appears **anywhere** inside the compound command. For example, `deny: bash:rm *` blocks `cd /tmp && rm -rf x` and `echo $(rm -rf x)`, not just a bare `rm` call.

<Card title="Command-Aware Permissions" icon="shield-halved" href="/docs/features/command-aware-permissions">
  Full behaviour details, evasion table, and aggregation rules
</Card>

Detailed dict form supports `is_regex`, `priority`, `agent_name`, and `description`:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
permissions:
  "read:*": allow
  "bash:rm *":
    action: deny
    description: Block destructive shell ops
    priority: 100
```

***

## Configuration Surfaces

<Info>
  **Coming soon:** Interactive persistent approvals will be able to auto-derive a command-prefix glob instead of storing the literal command. Until [PraisonAI PR #2576](https://github.com/MervinPraison/PraisonAI/pull/2576) is merged, pass the glob explicitly — e.g. `manager.approve("bash:git status *", True, scope="always")` — to cover every `git status` variant without re-prompting. See [Reusable Approval Scopes](/docs/features/reusable-approval-scopes).
</Info>

| Surface              | Where                                                               | Example               |
| -------------------- | ------------------------------------------------------------------- | --------------------- |
| Per-agent definition | `mode:` / `permission:` in `.praisonai/agents/*.{md,yaml}`          | `mode: read-only`     |
| Project config       | `.praisonai/config.yaml` `permissions:`                             | `"bash:rm *": deny`   |
| YAML                 | Top-level or per-agent `approval.permissions:` in `agents.yaml`     | `"bash:rm *": deny`   |
| CLI                  | `--allow`, `--deny`, `--permissions <file>`, `--permission-default` | `--deny 'bash:rm *'`  |
| Python               | `ApprovalConfig(permissions={...})`                                 | `{"read:*": "allow"}` |

**Precedence:** agent definition `mode:` / `permission:` → agent-level `approval.permissions` → top-level `agents.yaml` `permissions:` → `.praisonai/config.yaml` `permissions:` → CLI `--allow`/`--deny` (override file) → `--permission-default` → built-in defaults. CLI flags override config-yaml rules per-pattern. See [Agent Presets & Modes](/docs/features/agent-presets-and-modes) for the full per-agent permission reference.

Load from file:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai run agents.yaml --permissions permissions.yaml --deny 'bash:curl *'
```

***

## Common Patterns

**Read-only CI runner** — `--permission-default deny` with `--allow 'read:*'`.

**Git-only worker** — allow `bash:git *`, deny everything else.

**Deny destructive commands** — baseline `"bash:rm *": deny` and `"write:/etc/*": deny`.

**Lock down external filesystem access** — add `"external_dir:*": deny` when `workspace_root` is set for a hard block on everything outside the project.

***

## Best Practices

<AccordionGroup>
  <Accordion title="Set permission-default deny in CI">
    Use `--permission-default deny` or `"*": deny` so undeclared tools are blocked.
  </Accordion>

  <Accordion title="Prefer specific patterns first">
    Higher-priority rules win — declare `bash:git push *` before broad `bash:*`.
  </Accordion>

  <Accordion title="Version-control policy files">
    Keep `permissions.yaml` next to `agents.yaml` in git.
  </Accordion>

  <Accordion title="Test with --no-tty locally">
    Verify rules before deploying to CI.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Single-Source Config" icon="gear" href="/docs/features/single-source-config">
    Model + MCP + permissions in one config file
  </Card>

  <Card title="Command-Aware Permissions" icon="shield-halved" href="/docs/features/command-aware-permissions">
    How compound commands are decomposed and checked
  </Card>

  <Card title="Permissions" icon="shield" href="/docs/features/permissions">
    Programmatic PermissionManager API
  </Card>

  <Card title="Workspace Boundary" icon="shield-halved" href="/docs/features/workspace-boundary">
    Gate shell and file access outside your project root
  </Card>

  <Card title="Approval" icon="check" href="/docs/features/approval">
    Interactive approval backends
  </Card>

  <Card title="Tool Approval CLI" icon="terminal" href="/docs/cli/tool-approval">
    CLI flags reference
  </Card>
</CardGroup>
