How to Control Agent Tool Execution with Tool Policies in aisuite
aisuite provides a flexible policy system that lets developers intercept and approve or deny every tool call before execution, using either simple boolean checks or structured ToolPolicyDecision objects.
Every time an aisuite agent attempts to invoke a tool, the framework evaluates a tool policy to determine whether the call should proceed. This policy-based gatekeeper mechanism, implemented in aisuite/agents/policies.py and aisuite/utils/tools.py, gives you centralized control over safety, compliance, and operational constraints without modifying individual tool implementations.
What Is a Tool Policy in aisuite?
A tool policy is a callable or class that receives a ToolPolicyContext object and returns either:
- A boolean (
Trueto allow,Falseto deny) - A
ToolPolicyDecisioninstance containing anallowedflag and optionalreasonstring
If denied, the agent receives the error message Tool call denied by policy and execution continues without the tool running.
The core data structures are defined in aisuite/agents/types.py:
| Structure | Purpose |
|---|---|
ToolPolicyContext |
Holds pending tool call details: tool name, arguments, metadata, agent state |
ToolPolicyDecision |
Structured response with allowed: bool and reason: str |
Built-In Tool Policy Types
AllowPolicy and DenyPolicy
The simplest policies use bare functions. AllowPolicy logic returns True only for explicitly permitted tools:
import aisuite as ai
def allow_read_file(context: ai.ToolPolicyContext) -> bool:
"""Permit only the read_file tool."""
return context.tool_name == "read_file"
result = ai.run_sync(
agent,
"Read the first line of /etc/hosts",
tool_policy=allow_read_file,
)
DenyPolicy inverts this logic, returning False for blacklisted tools.
AllowToolsPolicy
For whitelisting multiple tools without writing custom logic, use the built-in AllowToolsPolicy class defined in aisuite/agents/policies.py:
import aisuite as ai
policy = ai.AllowToolsPolicy(["read_file", "search_web"])
result = ai.run_sync(
agent,
"Search the web for the latest AI news",
tool_policy=policy,
)
RequireApprovalPolicy
For human-in-the-loop or external approval workflows, the class-based RequireApprovalPolicy accepts a callback function:
import aisuite as ai
class RequireApprovalPolicy:
def __init__(self, approve_cb):
self.approve_cb = approve_cb
def evaluate(self, context: ai.ToolPolicyContext) -> ai.ToolPolicyDecision:
approved = self.approve_cb(context)
return ai.ToolPolicyDecision(
allowed=approved,
reason="User approved" if approved else "User denied"
)
def ask_user(context):
# Replace with actual UI prompt in production
return True
policy = RequireApprovalPolicy(ask_user)
result = ai.run_sync(
agent,
"Execute `ls -l /var/log`",
tool_policy=policy,
)
Custom Class-Based Policies
For complex logic like rate-limiting, per-user quotas, or time-based restrictions, implement a class with an evaluate(self, context) method:
class RateLimitedPolicy:
def __init__(self, max_calls_per_minute: int):
self.max_calls = max_calls_per_minute
self.call_times = []
def evaluate(self, context: ai.ToolPolicyContext) -> ai.ToolPolicyDecision:
# Custom rate-limiting logic here
pass
How Tool Policy Evaluation Works
The execution flow in aisuite/utils/tools.py follows these steps, as implemented in _prepare_tool_call and _evaluate_tool_policy:
- The agent emits a tool call request
_prepare_tool_callbuilds aToolPolicyContextwith complete call metadata_evaluate_tool_policyinvokes the configured policy (default: allow-all)- If allowed, the tool function executes; otherwise, an error is recorded and the call is skipped
All policy evaluation events are stored in last_policy_events for tracing and auditing. The test suite in tests/agents/test_tool_policy.py demonstrates allowed and denied behaviors with concrete assertions.
Tool Policies vs. Other Access Control Methods
| Approach | Granularity | Use Case |
|---|---|---|
| Tool policy | Per-call evaluation | Dynamic, context-aware decisions |
| Tool decorators | Tool definition time | Static capability restrictions |
| Agent configuration | Runtime parameter selection | Broad policy assignment |
Tool policies excel when decisions depend on runtime context—user identity, time of day, or external service availability—that cannot be determined when tools are defined.
Complete Example: Blocking Dangerous Commands
From examples/cli/create_demo_trace.py, here's a policy that blocks shell execution and returns explanatory reasons:
import aisuite as ai
class SafeExecutionPolicy:
BLOCKED_TOOLS = {"shell", "exec", "eval"}
def evaluate(self, context: ai.ToolPolicyContext) -> ai.ToolPolicyDecision:
if context.tool_name in self.BLOCKED_TOOLS:
return ai.ToolPolicyDecision(
allowed=False,
reason=f"Security policy prohibits {context.tool_name}"
)
return ai.ToolPolicyDecision(allowed=True)
policy = SafeExecutionPolicy()
Summary
- aisuite tool policies gate every tool call through a configurable
evaluatefunction or callable - Return
True/Falsefor simple cases, orToolPolicyDecisionfor structured responses with reasons - Built-in policies in
aisuite/agents/policies.pycover common whitelist and approval workflows - The evaluation pipeline in
aisuite/utils/tools.pyprovides full audit trails vialast_policy_events - Class-based policies support complex logic like rate-limiting and contextual access control
Frequently Asked Questions
What happens when a tool policy denies a call?
The tool does not execute, and the agent receives the string Tool call denied by policy as the tool result. The agent can then respond to this error message or attempt alternative approaches. The denial is recorded in last_policy_events for later inspection.
Can I combine multiple tool policies?
aisuite accepts a single policy per agent run. To combine rules, implement a composite policy class that evaluates sub-policies in sequence and returns the first denial or final approval. The ToolPolicyDecision structure supports detailed reasoning about which sub-policy triggered a block.
Where are tool policy decisions logged?
Policy evaluation events are stored in the last_policy_events list on the agent or runner object. This enables programmatic inspection after execution and supports compliance auditing without external logging infrastructure.
How do I migrate from function-based to class-based policies?
Replace your boolean-returning function with a class implementing evaluate(self, context: ToolPolicyContext) -> ToolPolicyDecision. The method signature change is backward-compatible through aisuite's detection logic—both styles are supported simultaneously in the same codebase.
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 →