aisuite Security Best Practices: Using Tool Policies and Approval Workflows
aisuite provides a flexible tool-policy framework in aisuite/agents/policies.py that allows you to enforce allowlists, require human approval for high-risk operations, and audit every tool invocation through the execution engine in aisuite/utils/tools.py.
The andrewyng/aisuite library enables powerful LLM-driven agents, but exposing tools to language models requires robust security guardrails. By implementing aisuite security tool policies and approval workflows, you can control exactly which functions agents invoke, under what conditions, and with full observability into every decision.
How aisuite Tool Policies Work
The policy enforcement engine operates through four distinct phases implemented across aisuite/utils/tools.py and aisuite/agents/policies.py:
-
Policy Definition: You instantiate a policy class such as
AllowToolsPolicyorRequireApprovalPolicythat implements theevaluate(context)method, returning aToolPolicyDecisionobject. TheAllowToolsPolicyclass checks tool names against an explicit allowlist (defined inaisuite/agents/policies.pyat lines 30-41). -
Context Building: When a tool call is imminent, the system constructs a
ToolPolicyContextcontaining the agent name, tool name, arguments, trace identifiers, and metadata. This occurs within the_prepare_tool_callmethod inaisuite/utils/tools.py(lines 448-464). -
Policy Evaluation: The engine delegates to
_evaluate_tool_policy(lines 399-408 inaisuite/utils/tools.py), which invokes your policy'sevaluatemethod. The resultingToolPolicyDecisionindicates whether the call isallowed=True/False, along with an optional reason and metadata. -
Enforcement and Auditing: If denied, the tool call short-circuits immediately, returning
"Tool call denied by policy"to the LLM (handled in_prepare_tool_callat lines 506-531). All decisions are appended toself.last_policy_events(lines 490-499) and emitted as trace events (tool.allowed,tool.denied) for complete observability.
Implementing Security Policies in aisuite
Using Allowlists with AllowToolsPolicy
The safest default posture is explicit allowlisting. The AllowToolsPolicy class in aisuite/agents/policies.py permits only specified tools while denying all others.
import aisuite as ai
# Restrict agent to read-only operations
allow_policy = ai.AllowToolsPolicy(
allowed_tools=["read_file", "list_dir"],
reason="Only read-only tools are allowed."
)
result = ai.Runner.run_sync(
agent,
prompt="List the files in /tmp and read the first one.",
tool_policy=allow_policy,
)
Requiring Human Approval
For high-risk operations, implement human-in-the-loop controls using RequireApprovalPolicy. This class accepts a callback function that receives the ToolPolicyContext and returns a ToolPolicyDecision.
import aisuite as ai
def approval_callback(context: ai.ToolPolicyContext):
# Auto-approve safe read operations
if context.tool_name.startswith("read_"):
return ai.ToolPolicyDecision(allowed=True)
# Require manual review for destructive operations
return ai.ToolPolicyDecision(
allowed=False,
reason="Manual approval required for this tool."
)
approval_policy = ai.RequireApprovalPolicy(callback=approval_callback)
response = ai.Runner.run_sync(
agent,
prompt="Delete the temporary cache directory.",
tool_policy=approval_policy,
)
The RequireApprovalPolicy implementation resides in aisuite/agents/policies.py at lines 43-61.
Chaining Multiple Policies
For defense in depth, combine policies by creating a composite wrapper that evaluates each policy sequentially, denying immediately on the first rejection:
class CompositePolicy:
def __init__(self, *policies):
self.policies = policies
def evaluate(self, ctx: ai.ToolPolicyContext):
for p in self.policies:
decision = p.evaluate(ctx)
if not decision.allowed:
return decision
return ai.ToolPolicyDecision(allowed=True)
# Layer allowlist with human approval
policy = CompositePolicy(
ai.AllowToolsPolicy(["read_file", "list_dir"]),
ai.RequireApprovalPolicy(approval_callback)
)
result = ai.Runner.run_sync(
agent,
"Read /etc/passwd and list its directory.",
tool_policy=policy,
)
Monitoring Policy Events
After execution, inspect response.tool_policy_events to audit decisions. The execution engine populates this list in aisuite/utils/tools.py (lines 490-499) by appending results to self.last_policy_events.
client = ai.Client()
response = client.run("Summarize the contents of /var/log/syslog.")
for ev in response.tool_policy_events:
print(f"{ev['tool_name']}: allowed={ev['allowed']} (reason={ev.get('reason')})")
Security Best Practices for aisuite Tool Policies
Follow these patterns to secure production deployments:
-
Default-deny posture: Always start with
AllowToolsPolicycontaining an explicit list of safe tools. Never deploy withAllowAllToolPolicy, which bypasses all security checks and is defined inaisuite/agents/policies.py. -
Risk-based approval tiers: Use
RequireApprovalPolicyfor destructive operations (write, delete, execute) while allowing safe read-only tools automatically. -
Immutable audit trails: Export
tool_policy_eventsto persistent storage. The engine emits structured trace events (tool.allowed,tool.denied) that capture the full decision context including agent name, tool name, and justification. -
Runtime policy swapping: The architecture supports switching policies between invocations, enabling strict deny-all modes in production and relaxed constraints in development environments.
Summary
- aisuite enforces tool security through the policy framework in
aisuite/agents/policies.pyand the execution engine inaisuite/utils/tools.py. - AllowToolsPolicy implements explicit allowlisting by checking tool names against permitted lists in the
evaluate()method. - RequireApprovalPolicy enables human-in-the-loop workflows through customizable callback functions that receive full
ToolPolicyContextdata. - The
_evaluate_tool_policyfunction inaisuite/utils/tools.pyorchestrates evaluation, while_prepare_tool_callhandles context creation and denial enforcement. - Always inspect
last_policy_eventsorresponse.tool_policy_eventsto maintain audit trails of all tool invocation decisions.
Frequently Asked Questions
What is the default behavior if no tool policy is specified in aisuite?
If you do not provide a tool_policy parameter, aisuite may default to permissive settings depending on the runner configuration. You should explicitly set AllowToolsPolicy with a restricted allowlist in production to prevent unauthorized tool access, as the AllowAllToolPolicy class bypasses all security checks.
How does aisuite handle policy violations during tool execution?
When a policy's evaluate() method returns allowed=False, the _prepare_tool_call method in aisuite/utils/tools.py immediately short-circuits the tool invocation. It returns a structured error payload with the message "Tool call denied by policy" to the LLM and logs a tool.denied trace event without executing the underlying function.
Can I implement custom policy logic beyond allowlists and approval workflows?
Yes. The policy system requires only that your class implements an evaluate(context) method accepting a ToolPolicyContext and returning a ToolPolicyDecision. You can integrate external authorization services, time-based restrictions, or attribute-based access control by subclassing the base policy interface defined in aisuite/agents/policies.py.
Where are policy decisions logged for compliance auditing?
All policy evaluations are stored in the last_policy_events list within the tool execution engine (aisuite/utils/tools.py, lines 490-499). These events are attached to the response object as tool_policy_events, containing the tool name, decision boolean, reason string, and metadata for every invocation attempt.
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 →