# How to Use ToolPolicyContext for Policy Decisions in AISuite

> Safely manage tool policy decisions in AISuite. Learn how ToolPolicyContext exposes tool details to allow or deny operations, ensuring robust agent behavior.

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

---

**`ToolPolicyContext` is the data structure that AISuite passes to every tool policy before executing a tool call, exposing fields like `tool_name`, `tool_arguments`, and `agent_state` so your policy can return a boolean or a `ToolPolicyDecision` to allow or deny the operation.**

When building AI agents with AISuite, controlling which tools the LLM can invoke is critical for security and compliance. The framework provides a robust policy evaluation mechanism centered around the `ToolPolicyContext` class defined in [`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py), which aggregates all relevant metadata about an impending tool execution. By leveraging this context object, developers can implement fine-grained access controls that inspect everything from the tool's arguments to the current conversation state.

## What Is ToolPolicyContext?

In the AISuite architecture, `ToolPolicyContext` serves as the single source of truth for policy decisions. According to the type definitions in [`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py), this dataclass encapsulates the complete execution context available to a policy at decision time.

The context exposes the following fields:

- **tool_name**: The identifier of the tool being invoked.
- **tool_metadata**: Static configuration from the tool definition, including description, parameters, and custom policy tags.
- **tool_arguments**: The actual arguments generated by the LLM for this specific call.
- **agent_state**: A snapshot of the current agent context, including conversation history and variables.
- **request_id**: A unique identifier for the LLM request that triggered the tool call.
- **user_id**: An optional identifier for the end-user, enabling multi-tenant policy enforcement.

## How Policy Evaluation Works

When a tool call is imminent, AISuite constructs a `ToolPolicyContext` instance and routes it through the internal `_evaluate_tool_policy` function in [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py). As implemented in andrewyng/aisuite, this evaluation follows a strict precedence:

1. **No policy configured**: The tool executes immediately without checks.
2. **Callable policy**: The function receives the context and must return a `bool` (True allows, False denies).
3. **Object-based policy**: The object’s `evaluate(self, context: ToolPolicyContext)` method is called, which may return either a boolean or a `ToolPolicyDecision` object.

If the policy denies the call, AISuite inserts the error message `"Tool call denied by policy"` into the LLM response instead of executing the tool. Every decision is recorded as a policy event in `last_policy_events`, enabling downstream tracing and audit trails.

## Implementing Tool Policies with ToolPolicyContext

### Simple Callable Policies

The fastest way to restrict tool access is a plain function that inspects the `tool_name` field. This approach works well for allowlists or simple denials.

```python
import aisuite as ai

def allow_only_read_file(context: ai.ToolPolicyContext) -> bool:
    """Allow only the read_file tool; reject all others."""
    return context.tool_name == "read_file"

# Apply when creating a runner

runner = ai.AgentRunner(
    agent=my_agent,
    tool_policy=allow_only_read_file,
)

```

### Rich Decisions with ToolPolicyDecision

For policies requiring explanatory messages or complex logic, implement a class that returns `ToolPolicyDecision`. This is defined alongside `ToolPolicyContext` in [`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py).

```python
import aisuite as ai

class ApprovalPolicy:
    """Requires human approval for tools tagged as sensitive."""
    def __init__(self, callback):
        self.callback = callback
    
    def evaluate(self, context: ai.ToolPolicyContext) -> ai.ToolPolicyDecision:
        # Check metadata for policy tags

        if context.tool_metadata.get("policy") == "requires_approval":
            approved = self.callback(context)
            return ai.ToolPolicyDecision(
                allowed=approved,
                reason="approved by user" if approved else "approval denied"
            )
        return ai.ToolPolicyDecision(allowed=True)

# Usage

client = ai.Client(tool_policy=ApprovalPolicy(my_approval_fn))

```

### Inspecting Agent State and Arguments

Sophisticated policies can enforce role-based access control by examining `agent_state` or validate inputs via `tool_arguments`.

```python
import aisuite as ai

def admin_only_shell(context: ai.ToolPolicyContext) -> bool:
    """Block shell commands unless the user has admin role."""
    if context.tool_name == "exec_shell":
        user_role = context.agent_state.get("user_role", "guest")
        return user_role == "admin"
    return True

client = ai.Client(tool_policy=admin_only_shell)

```

## Core Implementation Files

Understanding the source layout helps when debugging policy behavior or extending the framework:

- **[`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py)**: Defines `ToolPolicyContext`, `ToolPolicyDecision`, and the policy protocol interfaces.
- **[`aisuite/agents/policies.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/policies.py)**: Contains concrete policy implementations like `AllowToolsPolicy` and `RequireApprovalPolicy`.
- **[`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py)**: Houses the `_evaluate_tool_policy` function and the logic for recording policy events.
- **[`examples/cli/create_demo_trace.py`](https://github.com/andrewyng/aisuite/blob/main/examples/cli/create_demo_trace.py)**: Demonstrates a demo policy that denies shell commands for safety testing.
- **[`tests/agents/test_tool_policy.py`](https://github.com/andrewyng/aisuite/blob/main/tests/agents/test_tool_policy.py)**: Validates the evaluation flow and context passing.
- **[`tests/agents/test_tool_metadata_policy.py`](https://github.com/andrewyng/aisuite/blob/main/tests/agents/test_tool_metadata_policy.py)**: Verifies that tool metadata is correctly propagated into the policy context.

## Summary

- **`ToolPolicyContext`** aggregates tool metadata, arguments, agent state, and request IDs into a single object passed to every policy check.
- Policies can be **simple callables** returning booleans or **class-based implementations** returning `ToolPolicyDecision` objects with explicit reasons.
- The evaluation logic in [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py) automatically records all decisions as policy events for auditing.
- When denied, AISuite returns the specific error message `"Tool call denied by policy"` to the LLM instead of executing the tool.
- Access `agent_state` for conversation-aware policies and `tool_metadata` for configuration-driven rules.

## Frequently Asked Questions

### What fields does ToolPolicyContext expose?

`ToolPolicyContext` provides `tool_name`, `tool_metadata`, `tool_arguments`, `agent_state`, `request_id`, and optional `user_id`. These fields are defined in [`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py) and populated by the framework before each policy check.

### Can a policy return a custom denial message?

Yes. Instead of returning a boolean, return a `ToolPolicyDecision` object with `allowed=False` and a custom `reason` string. AISuite includes this reason in the `last_policy_events` log, though the LLM receives the standard "Tool call denied by policy" message.

### How do I access the conversation history inside a policy?

Inspect the `agent_state` field of the `ToolPolicyContext`. This dictionary contains the current agent context, including conversation history and any variables set by the agent, allowing you to make decisions based on the full interaction state.

### Where are policy decisions logged?

Each evaluation result is stored as a policy event in `last_policy_events`, accessible on the runner or client instance. This audit trail captures the decision, timestamp, and reason, which is essential for debugging and compliance monitoring.