# How RequireApprovalPolicy Controls Tool Execution in aisuite: A Complete Guide

> Learn how RequireApprovalPolicy controls tool execution in aisuite. This guide explains its role in human-in-the-loop approval and automated policy enforcement before function runs.

- Repository: [Andrew Ng/aisuite](https://github.com/andrewyng/aisuite)
- Tags: how-to-guide
- Published: 2026-07-28

---

**RequireApprovalPolicy** is a built-in safety mechanism that intercepts every LLM tool invocation in aisuite and delegates the execution decision to a custom callback function, enabling human-in-the-loop approval or automated policy enforcement before any Python function runs.

When building AI agents with the `andrewyng/aisuite` framework, controlling which tools the LLM can execute is critical for security and compliance. The **RequireApprovalPolicy** class, defined in [`aisuite/agents/policies.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/policies.py), provides a programmable gatekeeper that evaluates every tool call against custom business logic before the underlying Python callable is invoked.

## What is RequireApprovalPolicy?

`RequireApprovalPolicy` is one of the built-in tool policies available in aisuite's agent framework. It is designed to execute a user-defined callback for every tool invocation, allowing developers to implement approval workflows, audit logging, or automated risk assessment before the actual tool function executes.

The policy is exposed at the top-level `aisuite` namespace and is configured when initializing an `Agent` instance through the `tool_policy` parameter.

## The ToolPolicyContext Object

When a tool is called during agent execution, aisuite constructs a `ToolPolicyContext` containing the complete invocation details. According to the implementation in [`aisuite/agents/policies.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/policies.py), this context includes:

- `tool_name`: The string identifier of the tool being invoked
- `arguments`: The JSON-decoded arguments supplied by the model
- `tool_metadata`: Optional `ToolMetadata` attached to the callable via the `@ai.tool` decorator
- `agent`: The `Agent` instance that requested the tool execution
- `client`: The `Client` used for the LLM request

This context object is passed as the single argument to your approval callback, giving you full visibility into what the model is attempting to do.

## Implementing the Approval Callback

The core of `RequireApprovalPolicy` is the callback function you provide during instantiation. As implemented in lines 43-61 of [`aisuite/agents/policies.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/policies.py), this callback must return one of two types:

1. **Boolean**: `True` allows execution, `False` denies it
2. **ToolPolicyDecision**: An object specifying both `allowed` (bool) and `reason` (str)

If you return a boolean, aisuite automatically wraps it in a `ToolPolicyDecision` with the reason set to `"approved"` or `"denied"`. Returning any other type raises a `TypeError`.

The callback signature should accept a `ToolPolicyContext` and return the decision:

```python
def approval_callback(ctx: ai.ToolPolicyContext) -> ai.ToolPolicyDecision:
    if ctx.tool_metadata.risk_level == "high":
        return ai.ToolPolicyDecision(
            allowed=False,
            reason="high-risk operations require manual review"
        )
    return True  # Equivalent to ToolPolicyDecision(allowed=True, reason="approved")

```

## Execution Flow and Decision Handling

Once the callback returns a decision, aisuite processes the result as follows:

- If `allowed=False`, the tool's Python function is **not executed**. The denial reason is returned to the agent as the tool result.
- If `allowed=True`, the tool executes normally with the provided arguments.
- The final `ToolPolicyDecision` is stored in the execution trace at `step.data["allowed"]` and `step.data["reason"]`, providing full observability for debugging and audit trails.

This mechanism is tested in [`tests/agents/test_tool_metadata_policy.py`](https://github.com/andrewyng/aisuite/blob/main/tests/agents/test_tool_metadata_policy.py) (lines 15-30), where callbacks record invocation details and explicitly deny high-risk operations to verify the policy enforcement chain.

## Complete Implementation Example

The following example demonstrates configuring an agent with `RequireApprovalPolicy` to gate database deletion operations based on risk metadata:

```python
import aisuite as ai

# 1. Define a tool with metadata indicating it requires approval

@ai.tool
def delete_user(user_id: str) -> str:
    """Delete a user from the database."""
    return f"User {user_id} deleted"

# Attach metadata to mark as high-risk

delete_user = ai.tool(
    delete_user,
    metadata=ai.ToolMetadata(
        category="admin",
        risk_level="high",
        requires_approval=True,
    ),
)

# 2. Define the approval callback with business logic

def approval_callback(ctx: ai.ToolPolicyContext) -> ai.ToolPolicyDecision:
    # Automatically deny high-risk tools in this example

    if ctx.tool_metadata and ctx.tool_metadata.risk_level == "high":
        return ai.ToolPolicyDecision(
            allowed=False,
            reason="high-risk operation blocked by policy"
        )
    return True

# 3. Create agent with the RequireApprovalPolicy

agent = ai.Agent(
    name="assistant",
    model="openai:gpt-4o",
    tools=[delete_user],
    tool_policy=ai.RequireApprovalPolicy(approval_callback),
)

# 4. Execute - policy is consulted before delete_user runs

result = ai.Runner.run_sync(agent, "Remove user 12345")
print(result.final_output)   # "high-risk operation blocked by policy"

print(result.steps[-1].data) # {'allowed': False, 'reason': '...', ...}

```

In this implementation, the `approval_callback` receives the full `ToolPolicyContext` including the `ToolMetadata`, allowing risk-based decisions. The execution trace preserves the denial reason for observability.

## Summary

- **RequireApprovalPolicy** acts as a programmable gatekeeper for all tool executions in aisuite, defined in [`aisuite/agents/policies.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/policies.py)
- The policy receives a `ToolPolicyContext` containing complete invocation details including `tool_name`, `arguments`, `tool_metadata`, `agent`, and `client`
- Approval callbacks return boolean values or `ToolPolicyDecision` objects; invalid return types raise `TypeError`
- When denied, the tool function is skipped entirely and the reason is recorded in `step.data["reason"]`
- The policy integrates with `ToolMetadata` defined in [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py) to enable risk-based access control

## Frequently Asked Questions

### What happens when the approval callback returns False?

When the callback returns `False` or a `ToolPolicyDecision` with `allowed=False`, aisuite skips the Python function execution entirely. The denial reason is stored in the step's trace data at `step.data["allowed"]` and `step.data["reason"]`, then returned to the agent as the tool result instead of the actual function output.

### Can I access custom tool metadata in the approval callback?

Yes. The `ToolPolicyContext` passed to your callback includes a `tool_metadata` field containing the `ToolMetadata` object attached to the callable via the `@ai.tool` decorator. This metadata is defined in [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py) and can include custom fields like `risk_level` or `category` to implement granular policies.

### What return types are valid for the approval callback?

The callback must return either a boolean (`True` to allow, `False` to deny) or a `ToolPolicyDecision` object specifying both `allowed` and `reason` fields. According to the source in [`aisuite/agents/policies.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/policies.py), any other return type raises a `TypeError`. Boolean returns are automatically wrapped in `ToolPolicyDecision` with default reasons.

### How do I configure an agent to use RequireApprovalPolicy?

Instantiate `RequireApprovalPolicy` with your callback function and pass it to the `tool_policy` parameter when creating an `Agent`. The class is exported at the top-level `aisuite` namespace, making it accessible as `ai.RequireApprovalPolicy`. You can combine it with tools that have `ToolMetadata` attached to create sophisticated approval workflows.