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

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 and the evaluation logic in 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 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) 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 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:


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


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

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:

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:

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 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
  • Built-in options in 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) 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 (demonstrating trace-aware policies) and examples/agents/simple_agent.py (showing safe-tools restrictions). The CLI application at aisuite-code-cli/app.py also demonstrates interactive approval using RequireApprovalPolicy for every tool invocation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →