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 proceedsreason: 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 identifiertool_name: The requested tool's namearguments: The arguments passed to the tooltool_metadata: Optional metadata including risk levels and descriptionstrace_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:
- AllowAllToolPolicy – Always permits tool execution (default behavior)
- DenyAllToolPolicy – Always blocks execution with an optional reason
- AllowToolsPolicy – Permits only tools specified in an allowlist
- 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.pyand the_prepare_tool_callfunction - Implement custom logic by creating a class with
evaluate(context: ToolPolicyContext) -> ToolPolicyDecisionfollowing the protocol inaisuite/agents/types.py - Built-in options in
aisuite/agents/policies.pyinclude allowlists, denylists, and callback-based approval flows - Apply policies by passing them to
Client.run(tool_policy=your_policy)orRunner.run_sync() - Leverage context fields like
agent_name,tool_metadata, andargumentsto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →