# Implementing Allow and Deny Tool Policies in aisuite: A Complete Security Guide

> Master aisuite tool policies: Securely control LLM function access with allow/deny lists and approval workflows. A comprehensive guide for robust AI security.

- Repository: [Andrew Ng/aisuite](https://github.com/andrewyng/aisuite)
- Tags: how-to-guide
- Published: 2026-06-15

---

**aisuite’s tool-policy framework lets you control which Python functions an LLM may invoke by implementing whitelist, blacklist, or human-in-the-loop approval workflows through a simple protocol-based interface.**

The aisuite library provides a unified abstraction for building LLM agents across multiple providers, and implementing allow and deny tool policies in aisuite gives developers granular control over autonomous tool execution. By leveraging the `ToolPolicy` protocol and built-in security classes, you can enforce strict boundaries on agent capabilities without modifying underlying model configurations.

## Core Tool Policy Architecture

The policy system centers on three components defined in [`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py): the context object carrying runtime metadata, the decision object containing the verdict, and the protocol that binds them together.

### ToolPolicyContext and ToolPolicyDecision

The `ToolPolicyContext` dataclass encapsulates everything a policy needs to evaluate a request, including the `tool_name`, `args`, and caller information. The `ToolPolicyDecision` dataclass returns the evaluation result with an `allowed` boolean and an optional `reason` string that communicates block decisions back to the LLM.

### The ToolPolicy Protocol

Any object implementing the `ToolPolicy` protocol must provide an `evaluate(context)` method that accepts a `ToolPolicyContext` and returns either a boolean or a `ToolPolicyDecision`. This contract, defined in [`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py), ensures that custom policies integrate seamlessly with the runner execution flow.

## Built-in Policy Implementations

The concrete security policies reside in [`aisuite/agents/policies.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/policies.py) and are re-exported through [`aisuite/__init__.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/__init__.py) for convenient access. These cover the four primary access control patterns:

### AllowToolsPolicy (Whitelist)

The `AllowToolsPolicy` class enforces strict whitelisting by accepting an `allowed_tool_names` list. Only tools explicitly named in this array receive execution permission, making it ideal for read-only or limited-capability agents.

```python
import aisuite as ai

# Whitelist only the `read_file` tool

policy = ai.AllowToolsPolicy(allowed_tool_names=["read_file"])

agent = ai.Agent(
    name="file-helper",
    model="openai:gpt-4o",
    instructions="Help the user read files but never write them.",
    tools=[*ai.toolkits.files(root=".")],   # provides read_file, write_file, …

)

result = ai.Runner.run(agent, "Show me the README", tool_policy=policy)

print(result.final_output)      # succeeds – read_file is allowed

```

### DenyAllToolPolicy (Sandbox Mode)

For completely restricted environments, `DenyAllToolPolicy` blocks every tool invocation. It accepts a customizable `reason` parameter that propagates to the LLM when calls are rejected, effectively creating a sandboxed chat mode.

```python
import aisuite as ai

policy = ai.DenyAllToolPolicy(reason="Tool calls are disabled for this run")

result = ai.Runner.run_sync(
    agent=ai.Agent(
        name="no-tools",
        model="anthropic:claude-3.5-sonnet",
        instructions="Answer questions without invoking any tools.",
        tools=[*ai.toolkits.files(root=".")],
    ),
    prompt="What is the size of this repository?",
    tool_policy=policy,
)

print(result.final_output)   # The LLM will be told that tool calls are blocked

```

### RequireApprovalPolicy (Human-in-the-Loop)

The `RequireApprovalPolicy` delegates decisions to a user-provided callback, enabling dynamic approval workflows. The callback receives the `ToolPolicyContext` and must return a boolean or `ToolPolicyDecision`, allowing for console prompts, GUI dialogs, or external approval services.

```python
import aisuite as ai

def approve(context: ai.ToolPolicyContext) -> ai.ToolPolicyDecision:
    # Simple console prompt – in a real UI you could pop up a dialog.

    answer = input(f"Allow tool `{context.tool_name}` with args {context.args}? (y/n) ")
    allowed = answer.lower().startswith("y")
    return ai.ToolPolicyDecision(allowed=allowed,
                                 reason="user approved" if allowed else "user denied")

policy = ai.RequireApprovalPolicy(callback=approve)

result = ai.Runner.run(
    agent=ai.Agent(
        name="interactive-agent",
        model="openai:gpt-4o",
        instructions="You may call tools, but the user must approve each call.",
        tools=[*ai.toolkits.files(root=".")],
    ),
    prompt="Summarize the contents of LICENSE.txt",
    tool_policy=policy,
)

print(result.final_output)

```

### AllowAllToolPolicy (Default)

When no policy is specified, `Runner` implicitly uses `AllowAllToolPolicy`, which permits all tool calls without restriction. Explicitly passing this policy makes the default explicit in your code, useful for prototyping before implementing stricter controls.

```python
import aisuite as ai

result = ai.Runner.run_sync(
    agent=ai.Agent(
        name="quick-demo",
        model="openai:gpt-4o-mini",
        instructions="Feel free to call any provided tools.",
        tools=[*ai.toolkits.files(root=".")],
    ),
    prompt="List all .py files in the repo",
    tool_policy=ai.AllowAllToolPolicy(),
)

print(result.final_output)

```

## Policy Integration in the Runner

The enforcement point occurs in [`aisuite/agents/runner.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/runner.py), where the `Runner.run` and `Runner.run_sync` methods accept an optional `tool_policy` argument. The execution flow follows this sequence:

1. The LLM generates a tool call request during agent execution.
2. The runner constructs a `ToolPolicyContext` containing the tool name, arguments, and caller metadata.
3. The policy's `evaluate()` method receives this context and returns a decision.
4. If the decision's `allowed` flag is `True`, the tool executes and its result feeds back to the model.
5. If `False`, the runner raises a `ToolPolicyError` and the LLM receives a blocking notification.
6. The final `RunResult` includes complete `intermediate_messages` containing any blocked attempts for audit purposes.

This architecture ensures that policy checks occur immediately before function execution, preventing unauthorized operations without affecting the agent's reasoning loop.

## Summary

- The `ToolPolicy` protocol in [`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py) requires a single method: `evaluate(context) -> bool | ToolPolicyDecision`.
- Four built-in policies in [`aisuite/agents/policies.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/policies.py) cover whitelist (`AllowToolsPolicy`), blacklist (`DenyAllToolPolicy`), human approval (`RequireApprovalPolicy`), and unrestricted (`AllowAllToolPolicy`) access patterns.
- The `Runner` class automatically consults policies before executing any tool function, raising `ToolPolicyError` for blocked attempts.
- Blocked tool calls appear in `RunResult.intermediate_messages`, providing complete audit trails for security compliance.
- Custom policies require only protocol adherence, making it trivial to implement business-specific logic such as time-based restrictions or argument validation.

## Frequently Asked Questions

### What happens when a tool policy denies a function call?

When a policy returns `allowed=False`, the runner raises a `ToolPolicyError` and injects a blocking notification into the conversation history. The LLM receives a message indicating the tool was rejected, and the blocked attempt appears in the `intermediate_messages` of the final `RunResult` for complete auditability.

### Can I implement custom business logic in a tool policy?

Yes. Any class implementing the `evaluate(context)` method that returns a boolean or `ToolPolicyDecision` satisfies the `ToolPolicy` protocol defined in [`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py). The framework passes a `ToolPolicyContext` containing the tool name, arguments, and caller metadata, enabling complex decisions based on user roles, time of day, or argument content validation.

### How do I apply different policies to different agent runs?

Pass the desired policy instance to the `tool_policy` parameter of `Runner.run()` or `Runner.run_sync()`. Each execution operates independently, allowing you to use `DenyAllToolPolicy` for untrusted inputs and `AllowToolsPolicy` for verified workflows without modifying the underlying `Agent` definition.

### Where can I find reference implementations and tests?

The test suite in [`tests/agents/test_tool_policy.py`](https://github.com/andrewyng/aisuite/blob/main/tests/agents/test_tool_policy.py) demonstrates each built-in policy's behavior, while [`tests/agents/test_tool_metadata_policy.py`](https://github.com/andrewyng/aisuite/blob/main/tests/agents/test_tool_metadata_policy.py) covers edge cases for context inspection. These files provide concrete examples of policy instantiation and expected `ToolPolicyDecision` structures.