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:

  1. Policy Definition: You instantiate a policy class such as AllowToolsPolicy or RequireApprovalPolicy that implements the evaluate(context) method, returning a ToolPolicyDecision object. The AllowToolsPolicy class checks tool names against an explicit allowlist (defined in aisuite/agents/policies.py at lines 30-41).

  2. Context Building: When a tool call is imminent, the system constructs a ToolPolicyContext containing the agent name, tool name, arguments, trace identifiers, and metadata. This occurs within the _prepare_tool_call method in aisuite/utils/tools.py (lines 448-464).

  3. Policy Evaluation: The engine delegates to _evaluate_tool_policy (lines 399-408 in aisuite/utils/tools.py), which invokes your policy's evaluate method. The resulting ToolPolicyDecision indicates whether the call is allowed=True/False, along with an optional reason and metadata.

  4. Enforcement and Auditing: If denied, the tool call short-circuits immediately, returning "Tool call denied by policy" to the LLM (handled in _prepare_tool_call at lines 506-531). All decisions are appended to self.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 AllowToolsPolicy containing an explicit list of safe tools. Never deploy with AllowAllToolPolicy, which bypasses all security checks and is defined in aisuite/agents/policies.py.

  • Risk-based approval tiers: Use RequireApprovalPolicy for destructive operations (write, delete, execute) while allowing safe read-only tools automatically.

  • Immutable audit trails: Export tool_policy_events to 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.py and the execution engine in aisuite/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 ToolPolicyContext data.
  • The _evaluate_tool_policy function in aisuite/utils/tools.py orchestrates evaluation, while _prepare_tool_call handles context creation and denial enforcement.
  • Always inspect last_policy_events or response.tool_policy_events to 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:

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 →