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

# Policy Engine

> Policy-based execution control for agent operations

Define allow/deny rules so agents cannot run dangerous tools — attach a `PolicyEngine` before the agent starts.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonaiagents.policy import PolicyEngine, Policy, PolicyRule, PolicyAction

engine = PolicyEngine()
engine.add_policy(Policy(
    name="no_delete",
    rules=[
        PolicyRule(
            action=PolicyAction.DENY,
            resource="tool:delete_*",
            reason="Delete operations blocked",
        )
    ],
))

agent = Agent(
    name="SecureAgent",
    instructions="You are a file management assistant.",
)
agent.policy = engine
agent.start("Help me organise my project files")
```

The user requests a risky action; policy rules allow or deny tools before execution.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Agent[🤖 Agent] --> Engine[🛡️ PolicyEngine]
    Engine --> Check{Rule match?}
    Check -->|deny| Block[🚫 Blocked]
    Check -->|allow| Tool[🔧 Tool runs]

    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef policy fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef result fill:#10B981,stroke:#7C90A0,color:#fff
    classDef deny fill:#F59E0B,stroke:#7C90A0,color:#fff

    class Agent agent
    class Engine,Check policy
    class Tool result
    class Block deny
```

## Quick Start

<Steps>
  <Step title="Simple Usage">
    Block delete tools on a file-management agent:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent
    from praisonaiagents.policy import (
        PolicyEngine, Policy, PolicyRule, PolicyAction,
        create_read_only_policy,
    )

    engine = PolicyEngine()
    engine.add_policy(create_read_only_policy())

    agent = Agent(name="Assistant", instructions="Manage files safely.")
    agent.policy = engine
    agent.start("List files in the current directory")
    ```
  </Step>

  <Step title="With Configuration">
    Use strict mode and custom deny lists:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent
    from praisonaiagents.policy import (
        PolicyEngine, PolicyConfig, create_deny_tools_policy,
    )

    engine = PolicyEngine(PolicyConfig(strict_mode=True))
    engine.add_policy(create_deny_tools_policy(
        ["execute_*", "shell_*"],
        reason="System commands are blocked",
    ))

    agent = Agent(name="Reviewer", instructions="Read and summarise code only.")
    agent.policy = engine
    ```
  </Step>

  <Step title="Denial in action">
    Attach a deny policy and watch the tool call get blocked before it runs:

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

    def delete_file(path: str) -> str:
        """Delete a file at the given path."""
        return f"Deleted {path}"

    engine = PolicyEngine()
    engine.add_policy(create_deny_tools_policy(
        ["delete_*"], reason="Delete operations are blocked",
    ))

    agent = Agent(
        name="ReadOnly",
        instructions="You are a read-only assistant.",
        tools=[delete_file],
    )
    agent.policy = engine

    # The agent's tool call is denied by the policy before delete_file runs
    agent.start("Please delete /etc/passwd")
    ```

    Policy denials return an error to the model **before** the tool executes — the same pre-dispatch check applies to native and MCP tools alike. Guardrails run at this same hook: `GuardrailChain.validate_tool_call` / `LLMGuardrail.validate_tool_call` can veto a tool call before dispatch (see the **Guardrails** card below).
  </Step>
</Steps>

***

## Read-only preset — what it blocks

`create_read_only_policy()` returns a `Policy` at priority `100` with **11 deny rules** — 2 file-resource rules and 9 tool-name rules — covering the SDK's built-in mutating tools.

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

engine = PolicyEngine()
engine.add_policy(create_read_only_policy())

agent = Agent(
    name="Reviewer",
    instructions="Read and summarise the repo — never modify anything.",
)
agent.policy = engine

# Any of these, if attached as tools, are denied before dispatch:
#   write_file, delete_file, edit_file, apply_patch, copy_file,
#   move_file, append_file, atomic_write, hard_delete, ...
agent.start("Summarise src/main.py in five bullet points")
```

The preset matches by both **prefix** (`tool:write_*`) and **suffix** (`tool:*_write*`), so custom-named tools like `atomic_write` and `file_write` are caught too.

| Rule name | Resource pattern | What it blocks |
| - | - | - |
| `deny_write` | `file:write` | `check_file()` write path |
| `deny_delete` | `file:delete` | `check_file()` delete path |
| `deny_write_tools` | `tool:write_*` | `write_file`, `write_json`, etc. |
| `deny_write_tools_suffix` | `tool:*_write*` | `file_write`, `atomic_write`, etc. |
| `deny_delete_tools` | `tool:delete_*` | `delete_file`, `delete_row`, etc. |
| `deny_delete_tools_suffix` | `tool:*_delete*` | `soft_delete`, `hard_delete`, etc. |
| `deny_edit_tools` | `tool:edit_*` | `edit_file`, `edit_json`, etc. |
| `deny_patch_tools` | `tool:apply_patch*` | `apply_patch`, `apply_patch_batch`, etc. |
| `deny_copy_tools` | `tool:copy_*` | `copy_file`, `copy_folder`, etc. |
| `deny_move_tools` | `tool:move_*` | `move_file`, `move_folder`, etc. |
| `deny_append_tools` | `tool:append_*` | `append_file`, `append_json`, etc. |

An explicit `ALLOW` rule at higher priority still wins — a deny does not silently override it. Add `ALLOW` rules for any specific tool you need to permit.

<Warning>
  **Upgrade note (PraisonAI PR #3632)**: earlier releases shipped `create_read_only_policy()` with glob patterns that never matched the SDK's real tool names. If you relied on the preset as a safety net before this release, your agent could still call `write_file` / `delete_file` / `edit_file` / etc. Upgrade to the fixed release and re-run your safety tests.
</Warning>

***

## Network egress

Gate outbound requests by destination host using `check_network()` and the `create_network_policy()` preset.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Agent[🤖 Agent] --> Engine[🛡️ PolicyEngine]
    Engine --> Net["check_network(host)"]
    Net -->|deny| Block[🚫 Blocked]
    Net -->|allow| Out[🌐 Request sent]

    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef policy fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef result fill:#10B981,stroke:#7C90A0,color:#fff
    classDef deny fill:#F59E0B,stroke:#7C90A0,color:#fff

    class Agent agent
    class Engine,Net policy
    class Out result
    class Block deny
```

Attach a network policy, then consult the engine before any outbound call:

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

engine = PolicyEngine()
engine.add_policy(create_network_policy(
    allow_hosts=["api.mycorp.com", "*.docs.mycorp.com"],
    deny_hosts=["169.254.169.254"],  # block cloud metadata
))

agent = Agent(
    name="Fetcher",
    instructions="Fetch pages only from approved hosts.",
)
agent.policy = engine

# Before any outbound request, the caller consults:
assert engine.check_network("api.mycorp.com").allowed is True
assert engine.check_network("docs.mycorp.com").allowed is False        # not on the allow-list
assert engine.check_network("169.254.169.254").allowed is False        # deny wins
assert engine.check_network("API.mycorp.com.").allowed is True         # case/dot normalized
```

<Warning>
  **Scope: decision seam only.** The runtime does not yet automatically call `check_network()` before every tool, MCP, or LLM request. Callers integrating outbound HTTP must invoke it themselves. See [PraisonAI#5333](https://github.com/MervinPraison/PraisonAI/issues/5333) for the wider egress-enforcement roadmap.
</Warning>

### Which policy shape fits

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Start{Egress goal?} --> DenyOnly[Block specific hosts]
    Start --> AllowOnly[Only these hosts]
    Start --> Both[Allowlist + blocklist]

    DenyOnly --> D["deny_hosts=[...]<br/>others fall through"]
    AllowOnly --> A["allow_hosts=[...]<br/>default-deny the rest"]
    Both --> B["allow + deny<br/>deny wins"]

    classDef q fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef opt fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef res fill:#10B981,stroke:#7C90A0,color:#fff

    class Start q
    class DenyOnly,AllowOnly,Both opt
    class D,A,B res
```

| Policy shape | Behaviour |
| - | - |
| `deny_hosts` only | Listed hosts blocked; everything else falls through to the engine default (allow unless `strict_mode`). |
| `allow_hosts` (non-empty) | Default-deny: only listed hosts allowed, everything else denied. |
| `allow_hosts` + `deny_hosts` | Deny wins; allow-list is the explicit allowlist; everything else denied. |

### Resource format

`check_network(host)` normalizes the host and evaluates a `net:<host>` resource.

| Step | Rule |
| - | - |
| Whitespace | Leading/trailing whitespace stripped |
| Trailing dot | A single trailing `.` removed (`api.example.com.` → `api.example.com`) |
| Case | Lowercased (`API.Example.COM` → `api.example.com`) |

The destination host is authoritative — a caller-supplied `context["host"]` cannot override it.

### Wildcards

Host patterns support fnmatch wildcards.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.policy import PolicyEngine, create_network_policy

engine = PolicyEngine()
engine.add_policy(create_network_policy(allow_hosts=["*.mycorp.com"]))

assert engine.check_network("api.mycorp.com").allowed is True     # subdomain matches
assert engine.check_network("evil.example.com").allowed is False  # net:* default-deny row
```

When an allow-list is present, `create_network_policy` adds an implicit `net:*` default-deny rule at priority `0`.

### Case / trailing-dot normalization

`API.Example.COM.`, `api.example.com`, and `api.example.com.` all match the same rule.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.policy import PolicyEngine, create_network_policy

engine = PolicyEngine()
engine.add_policy(create_network_policy(deny_hosts=["api.example.com"]))

assert engine.check_network("API.EXAMPLE.COM").allowed is False
assert engine.check_network("Api.Example.Com.").allowed is False
assert engine.check_network("api.example.com").allowed is False
```

***

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Agent
    participant Engine as PolicyEngine
    participant Tool

    Agent->>Engine: check("tool:delete_file")
    alt denied
        Engine-->>Agent: PolicyResult(allowed=false)
    else allowed
        Engine-->>Agent: PolicyResult(allowed=true)
        Agent->>Tool: execute
    end
```

| Component | Purpose |
| - | - |
| `PolicyRule` | Wildcard resource patterns with `ALLOW`, `DENY`, `ASK`, or `LOG` |
| `PolicyEngine` | Evaluates rules by priority; optional strict mode |
| `agent.policy` | Attach the engine before `start()` |

Pattern examples: `tool:read_file`, `tool:delete_*`, `tool:*`.

***

## Configuration Options

| Option | Type | Default | Description |
| - | - | - | - |
| `strict_mode` | `bool` | `False` | Deny operations not explicitly allowed |
| `action` | `PolicyAction` | — | `ALLOW`, `DENY`, `ASK`, `LOG`, `RATE_LIMIT` |
| `resource` | `str` | `None` | Glob pattern by resource type: `tool:<name>`, `file:<op>`, or `net:<host>` (e.g. `net:*.mycorp.com`) |
| `priority` | `int` | `0` | Higher priority rules evaluate first |

***

## Policies from Plugins

A plugin declared `PluginType.POLICY` that returns `Policy` objects from `get_policies()` is added to the Agent's `PolicyEngine` automatically. If the Agent has no user `policy=`, one is created; if it has one, plugin rules are added to the existing engine.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, Plugin, PluginInfo, PluginType
from praisonaiagents.plugins import get_plugin_manager
from praisonaiagents.policy import Policy, PolicyRule, PolicyAction

class NoDeletePlugin(Plugin):
    @property
    def info(self):
        return PluginInfo(name="no_delete", plugin_type=PluginType.POLICY)
    def get_policies(self):
        return [Policy(
            name="no_delete",
            rules=[PolicyRule(
                action=PolicyAction.DENY,
                resource="tool:delete_*",
                reason="Delete operations blocked by plugin",
            )],
        )]

get_plugin_manager().register(NoDeletePlugin())
get_plugin_manager().enable("no_delete")

agent = Agent(name="Assistant", instructions="Manage files safely.")
# agent.policy now denies tool:delete_* before dispatch — no policy= needed.
agent.start("Please delete /etc/passwd")
```

On `Agent.__init__`, `_merge_plugin_subsystems()` calls `PluginManager.get_all_policies()` and adds each rule to the engine, guarded by a name-uniqueness check:

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Plugin as POLICY Plugin
    participant Mgr as PluginManager
    participant Merge as Agent._merge_plugin_subsystems
    participant Engine as PolicyEngine

    Plugin->>Mgr: get_policies() → [Policy("no_delete")]
    Merge->>Mgr: get_all_policies()
    Mgr-->>Merge: [Policy("no_delete")]
    Merge->>Engine: name already present?
    alt name is free
        Merge->>Engine: add_policy(rule)
    else name exists (user or earlier plugin)
        Note over Merge: skipped with a warning
    end
```

### Precedence — user-supplied policy always wins

If a policy with the same name already exists on the engine when the plugin's rule is added, the plugin's rule is **logged and skipped** — a plugin can never silently loosen a user-configured restriction.

| Situation | Behaviour |
| - | - |
| User policy has a rule named `X`; plugin policy tries to add `X` | Plugin's `X` is skipped with a warning — user wins. |
| Plugin has two policies both named `X` | The **first** wins; the second is skipped with a warning. |
| Agent has no user `policy=` | A `PolicyEngine` is created, then plugin rules are added. |
| Plugin declared `PluginType.HOOK` (not `POLICY`) | Not collected — `get_policies()` output is ignored. |
| Plugin disabled | Not collected — enabled-only iteration under the manager lock. |
| `get_policies()` raises | Warning logged, other plugins keep loading, Agent construction succeeds. |

See [Typed Plugins (Guardrail / Policy / Skill)](/docs/features/plugins#typed-plugins-guardrail--policy--skill) for the full plugin-side reference.

***

## Best Practices

<AccordionGroup>
  <Accordion title="Attach policy before the first turn">
    Set `agent.policy = engine` immediately after creating the agent.
  </Accordion>

  <Accordion title="Start with read-only presets">
    `create_read_only_policy()` blocks the SDK's built-in mutating tools — `write_*`, `delete_*`, `edit_*`, `apply_patch*`, `copy_*`, `move_*`, `append_*`, and the `*_write*` / `*_delete*` suffix forms. Attach it, then add explicit `ALLOW` rules at higher priority for any specific tool you need to permit.
  </Accordion>

  <Accordion title="Use wildcards sparingly">
    Prefer `tool:delete_*` over `tool:*` deny rules so read tools keep working.
  </Accordion>

  <Accordion title="Enable strict mode in production">
    `PolicyConfig(strict_mode=True)` blocks unknown tool names by default.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Guardrails" icon="shield" href="/docs/features/guardrails">
    Validate agent output before returning to users
  </Card>

  <Card title="Approval" icon="hand" href="/docs/features/approval">
    Require human confirmation for sensitive actions
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.