# How to Implement Custom Tool Policies in aisuite: A Complete Guide

> Learn to implement custom tool policies in aisuite by creating and passing a custom policy class to Client.run(). This guide offers a complete walkthrough for effective integration.

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

---

**To implement custom tool policies in aisuite, create a class that implements the `evaluate(context: ToolPolicyContext) -> ToolPolicyDecision` method and pass it to the `tool_policy` parameter in `Client.run()`.**

aisuite regulates tool execution through a pluggable policy system that vets every tool call before it runs. This mechanism centers on the `ToolPolicy` protocol defined in [`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py) and the evaluation logic in [`aisuite/agents/policies.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/policies.py). By implementing custom policies, you can enforce security rules, require human approval, or restrict tool access based on runtime context.

## Understanding the Tool Policy Architecture

The policy system intercepts tool calls at the moment of execution, allowing you to approve, deny, or audit every operation.

### Policy Evaluation Flow

When an agent issues a tool call, the framework constructs a **ToolPolicyContext** and invokes your policy's `evaluate()` method. Specifically, `aisuite.utils.tools._prepare_tool_call` (around line 440) builds the context containing the agent name, tool name, arguments, and trace information, then passes it to `_evaluate_tool_policy`.

Your policy must return a **ToolPolicyDecision** (defined in [`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py) line 84) with two fields:
- `allowed`: Boolean indicating whether the call proceeds
- `reason`: Optional string explaining the decision (displayed in logs and traces)

### Core Data Structures

The `ToolPolicyContext` dataclass (in [`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py)) exposes these key fields:
- `agent_name`: The invoking agent's identifier
- `tool_name`: The requested tool's name
- `arguments`: The arguments passed to the tool
- `tool_metadata`: Optional metadata including risk levels and descriptions
- `trace_id`: Execution trace identifier for auditing

## Built-in Policy Implementations

aisuite ships with four ready-to-use policies in [`aisuite/agents/policies.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/policies.py) that demonstrate the required interface:

1. **AllowAllToolPolicy** – Always permits tool execution (default behavior)
2. **DenyAllToolPolicy** – Always blocks execution with an optional reason
3. **AllowToolsPolicy** – Permits only tools specified in an allowlist
4. **RequireApprovalPolicy** – Defers decisions to a user-supplied callback function

The `AllowToolsPolicy` implementation illustrates the pattern:

```python

# aisuite/agents/policies.py

class AllowToolsPolicy:
    def __init__(self, allowed_tools: list[str], reason: Optional[str] = None):
        self.allowed_tools = set(allowed_tools)
        self.reason = reason

    def evaluate(self, context: ToolPolicyContext) -> ToolPolicyDecision:
        allowed = context.tool_name in self.allowed_tools
        return ToolPolicyDecision(
            allowed=allowed,
            reason=None if allowed else self.reason or "tool not in allowlist",
        )

```

## Creating a Custom Tool Policy

To implement a custom tool policy in aisuite, define a class that conforms to the `ToolPolicy` protocol by implementing `evaluate(context: ToolPolicyContext)`. This method can inspect any field of the context and return either a boolean or a full `ToolPolicyDecision` object.

Here is a risk-based policy that blocks high-risk tools for untrusted agents:

```python

# my_policies.py

from aisuite.agents.types import ToolPolicyContext, ToolPolicyDecision

class RiskBasedPolicy:
    """Block high-risk tools unless the request originates from a trusted agent."""
    def __init__(self, trusted_agents: set[str]):
        self.trusted_agents = trusted_agents

    def evaluate(self, context: ToolPolicyContext) -> ToolPolicyDecision:
        meta = context.tool_metadata
        if meta and meta.risk_level == "high" and context.agent_name not in self.trusted_agents:
            return ToolPolicyDecision(
                allowed=False,
                reason=f"High-risk tool '{meta.name}' blocked for untrusted agent",
            )
        return ToolPolicyDecision(allowed=True)

```

Your custom class can maintain internal state (like the set of trusted agents above) and implement arbitrary logic, including external API calls, database lookups, or ML-based risk scoring.

## Applying Custom Policies in Practice

Plug your policy into any execution path that reaches `Client.run()` or the lower-level `Runner.run_sync()` by supplying the `tool_policy` argument.

### Whitelist Approach

Use the built-in `AllowToolsPolicy` for simple allowlists:

```python
from aisuite import Client
from aisuite.agents.policies import AllowToolsPolicy

client = Client()
policy = AllowToolsPolicy(["read_file", "list_dir"])

result = client.run(
    agent="file_agent",
    prompt="List my home directory",
    tool_policy=policy
)

```

### Interactive Approval

Implement human-in-the-loop approval with `RequireApprovalPolicy`:

```python
from aisuite.agents.policies import RequireApprovalPolicy

def approve_callback(context: ToolPolicyContext):
    print(f"Tool request: {context.tool_name} with args {context.arguments}")
    return input("Approve? (y/n): ").lower() == "y"

policy = RequireApprovalPolicy(approve_callback)

client.run(
    agent="interactive_bot",
    prompt="Run dangerous command",
    tool_policy=policy
)

```

### Risk-Based Enforcement

Apply your custom implementation exactly like built-in policies:

```python
from my_policies import RiskBasedPolicy

policy = RiskBasedPolicy(trusted_agents={"admin_bot", "ci_runner"})

result = client.run(
    agent="service_bot",
    prompt="Shutdown server",
    tool_policy=policy
)

```

The policy object can be reused across multiple `run()` calls or instantiated per-request for dynamic context.

## Summary

- **Tool policies** in aisuite wrap every tool invocation via [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py) and the `_prepare_tool_call` function
- **Implement** custom logic by creating a class with `evaluate(context: ToolPolicyContext) -> ToolPolicyDecision` following the protocol in [`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py)
- **Built-in options** in [`aisuite/agents/policies.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/policies.py) include allowlists, denylists, and callback-based approval flows
- **Apply** policies by passing them to `Client.run(tool_policy=your_policy)` or `Runner.run_sync()`
- **Leverage context** fields like `agent_name`, `tool_metadata`, and `arguments` to make dynamic security decisions

## Frequently Asked Questions

### What data is available in ToolPolicyContext for custom policies?

The `ToolPolicyContext` object (defined in [`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py)) exposes the agent name, tool name, arguments dictionary, optional tool metadata (including risk levels and descriptions), trace ID for correlation, and execution tags. You can inspect any of these fields in your `evaluate()` method to make contextual decisions about whether to allow the tool call.

### Can I use async logic in my custom tool policy?

While the current `evaluate()` method signature in aisuite is synchronous, you can implement async handling by pre-fetching data before creating the policy or by using `asyncio.run()` inside the method for specific async operations. However, for production use with high concurrency, consider implementing caching or pre-computed risk scores to avoid blocking the agent's execution loop.

### How do I combine multiple policies or create complex rules?

aisuite supports single policy objects per run, but you can implement composite policies by creating a wrapper class that instantiates multiple sub-policies and implements your own precedence logic. For example, create a `CompositePolicy` that runs an `AllowToolsPolicy` check first, then falls back to a `RiskBasedPolicy` if the allowlist passes, returning the most restrictive decision.

### Where can I see examples of custom policies in action?

Reference implementations exist in the aisuite repository under [`examples/cli/create_demo_trace.py`](https://github.com/andrewyng/aisuite/blob/main/examples/cli/create_demo_trace.py) (demonstrating trace-aware policies) and [`examples/agents/simple_agent.py`](https://github.com/andrewyng/aisuite/blob/main/examples/agents/simple_agent.py) (showing safe-tools restrictions). The CLI application at [`aisuite-code-cli/app.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite-code-cli/app.py) also demonstrates interactive approval using `RequireApprovalPolicy` for every tool invocation.