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

# HOL Guard Tool Policy

> Gate command-bearing tools through HOL Guard before they execute

HOL Guard checks every shell command your agent proposes and only lets safe ones run.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    A[🤖 Agent] --> B[🔧 Tool Call]
    B --> C{🛡️ HOL Guard}
    C -->|allow| D[✅ Tool Runs]
    C -->|block| E[🚫 Blocked Response]

    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef guard fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef decision fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef success fill:#10B981,stroke:#7C90A0,color:#fff
    classDef block fill:#8B0000,stroke:#7C90A0,color:#fff

    class A agent
    class B guard
    class C decision
    class D success
    class E block
```

HOL Guard is an external, optional security engine. Install it separately, then wrap your command-bearing tools with one `@wrap_tool_call` policy — every unsafe command is blocked before it runs.

## Quick Start

**1. Install HOL Guard**

HOL Guard is a separate CLI. Install it once:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
pip install hol-guard
```

**2. Add the guard policy to your agent**

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import json
import shutil
import subprocess

from praisonaiagents import Agent, tool
from praisonaiagents.hooks import wrap_tool_call, ToolRequest, ToolResponse

COMMAND_BEARING_TOOLS = {"run_shell"}
GUARD_TIMEOUT_SECONDS = 10


@tool
def run_shell(command: str) -> str:
    """Run a shell command and return its output."""
    result = subprocess.run(command, shell=True, capture_output=True, text=True)
    return result.stdout or result.stderr


def _inspect_with_hol_guard(command: str) -> bool:
    if not shutil.which("hol-guard"):
        return False
    try:
        completed = subprocess.run(
            ["hol-guard", "command", "test", command, "--json"],
            capture_output=True, text=True, timeout=GUARD_TIMEOUT_SECONDS,
        )
    except (subprocess.TimeoutExpired, OSError):
        return False
    if completed.returncode != 0:
        return False
    try:
        verdict = json.loads(completed.stdout)
    except (json.JSONDecodeError, ValueError):
        return False
    if not isinstance(verdict, dict):
        return False
    decision = str(verdict.get("decision", "")).lower()
    return decision in ("allow", "benign")


@wrap_tool_call
def hol_guard_tool_policy(request: ToolRequest, call_next):
    if request.tool_name not in COMMAND_BEARING_TOOLS:
        return call_next(request)

    command = request.arguments.get("command", "")

    if _inspect_with_hol_guard(command):
        return call_next(request)

    blocked_message = "Blocked by HOL Guard: command was not explicitly allowed."
    return ToolResponse(
        tool_name=request.tool_name,
        result=blocked_message,
        error=blocked_message,
        context=request.context,
    )


agent = Agent(
    name="GuardedShellBot",
    instructions="You run shell commands only when they are safe.",
    tools=[run_shell],
    hooks=[hol_guard_tool_policy],
)
```

**3. Run and see a blocked command**

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
agent.start("Delete every file with: rm -rf /")
```

HOL Guard inspects the command, returns a non-allow verdict, and the agent receives the blocked message instead of running the tool.

***

## How It Works

Every command-bearing tool call passes through HOL Guard's CLI before it can execute.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Agent
    participant Policy as wrap_tool_call
    participant Guard as HOL Guard CLI
    participant Tool as run_shell

    User->>Agent: "run rm -rf /"
    Agent->>Policy: ToolRequest(run_shell, {command})
    Policy->>Guard: hol-guard command test <cmd> --json
    Guard-->>Policy: {"decision": "block"}
    Policy-->>Agent: ToolResponse(blocked)
    Agent-->>User: "I can't run that command."
```

| Step | What happens |
| - | - |
| Tool call | The agent proposes a `run_shell` command |
| Guard check | The policy shells out to `hol-guard command test` |
| Verdict | Only `allow` or `benign` reach the real tool |
| Block | Any other outcome returns a `ToolResponse` with `error` set |

Tools that are not in `COMMAND_BEARING_TOOLS` skip the guard entirely and run untouched.

***

## Configuration Options

Tune the policy by editing the constants in the example.

| Option | Type | Default | Description |
| - | - | - | - |
| `COMMAND_BEARING_TOOLS` | `set[str]` | `{"run_shell"}` | Tool names that must pass Guard before running |
| `GUARD_TIMEOUT_SECONDS` | `int` | `10` | Kill Guard's CLI if it hasn't answered in this many seconds |
| Verdict allow-list | `set[str]` | `{"allow", "benign"}` | Only these Guard decisions reach the wrapped tool |

***

## Fail-Closed Decision Matrix

The policy runs the tool only on an explicit safe verdict — everything else blocks.

| Situation | Wrapped tool runs? |
| - | - |
| Guard returns `{"decision": "allow"}` | ✅ yes |
| Guard returns `{"decision": "benign"}` | ✅ yes |
| Guard returns `{"decision": "block"}` / `"review"` / `"unknown"` | ❌ no |
| Guard CLI not on PATH | ❌ no |
| Guard exits non-zero | ❌ no |
| Guard times out (> `GUARD_TIMEOUT_SECONDS`) | ❌ no |
| Guard emits malformed / non-object JSON | ❌ no |
| Tool is not in `COMMAND_BEARING_TOOLS` | ✅ passes through untouched (Guard not called) |

***

## User Interaction Flow

A real user asks for an unsafe command; HOL Guard blocks it and the agent refuses politely.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Agent
    participant Guard as HOL Guard

    User->>Agent: "wipe the disk with rm -rf /"
    Agent->>Guard: check "rm -rf /"
    Guard-->>Agent: block
    Agent-->>User: "That command isn't safe, so I won't run it."
```

***

## Common Patterns

**Widen the tool allow-list** — add more command-bearing tools to the set:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
COMMAND_BEARING_TOOLS = {"run_shell", "run_script", "exec_command"}
```

**Log every blocked verdict** with an `after_tool` hook:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.hooks import after_tool, ToolResponse

@after_tool
def log_blocks(response: ToolResponse):
    if response.error:
        print(f"[guard] blocked {response.tool_name}: {response.error}")
    return response
```

**Combine with `before_tool` argument validation** for defense in depth:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.hooks import before_tool, ToolRequest

@before_tool
def require_command(request: ToolRequest):
    if request.tool_name in COMMAND_BEARING_TOOLS and not request.arguments.get("command"):
        raise ValueError("run_shell requires a non-empty command.")
    return request
```

***

## Best Practices

> [accordion] Set both result and error on the blocked ToolResponse
>
> The tool executor returns `ToolResponse.result` to the model and discards `error`. Set `result` so the model sees the block, and keep `error` populated so `after_tool` hooks and logs still have the signal. Setting only `error` would surface as a `null` result.

> [accordion] Keep Guard external
>
> Do not vendor `hol-guard` into your agent's dependency tree. Installing it separately preserves independent versioning and lets ops teams patch Guard without a code deploy.

> [accordion] Timeout aggressively
>
> Guard is on the hot path. `GUARD_TIMEOUT_SECONDS=10` is a ceiling, not a target — a slow Guard fails closed and blocks the command.

> [accordion] Prefer allow-lists to block-lists
>
> The example checks for `allow`/`benign` explicitly and fails closed on everything else, including verdicts introduced in future Guard versions.

***

## Related

> [card] [Middleware](https://praison.ai/docs/features/middleware.md)
>
> Intercept and modify model and tool calls with before/after hooks and wrap decorators

> [card] [Hooks](https://praison.ai/docs/features/hooks.md)
>
> Event-based hooks for agent lifecycle events


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