# How to Implement Approval Workflows for Sensitive Tool Calls in aisuite

> Implement approval workflows for sensitive tool calls in aisuite using tool metadata flags and an ApprovalController. Ensure secure and controlled execution with a human-in-the-loop process.

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

---

**aisuite routes sensitive tool calls through a human-in-the-loop approval workflow using tool metadata flags, an `ApprovalController`, and CLI or web-based UI components.**

Implementing approval workflows for sensitive tool calls in aisuite ensures that high-risk operations—such as shell execution or file deletion—cannot run without explicit human consent. The `andrewyng/aisuite` repository provides built-in support for this pattern through metadata flags on tool definitions and a dedicated controller that pauses execution until a decision is received. By integrating these components, you can enforce granular safety policies across both custom tools and built-in utilities.

## Architecture of Approval Workflows for Sensitive Tool Calls

The workflow is controlled by three core concepts that work together to intercept and mediate sensitive calls.

### Tool Metadata and the `requires_approval` Flag

Every tool in aisuite can declare a `__aisuite_tool_metadata__` object that specifies whether it should trigger an approval prompt. In [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py), built-in utilities such as `run_shell` and `write_file` set `requires_approval=True` to mark them as high-risk.

When a tool is registered with this flag, the framework automatically blocks direct execution and escalates the request to the controller.

### The `ApprovalController` Mediation Cycle

The `ApprovalController`, implemented in [`aisuite/cli/approval.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/cli/approval.py), manages the full request-to-decision lifecycle. Upon encountering a sensitive tool call, the controller creates an **ApprovalContext** containing the tool name, arguments, and description, then pushes it into the current session’s pending approvals queue.

The session persists this context to disk, allowing paused approvals to survive restarts. The controller then waits for an external `allow` or `deny` decision before either resuming or aborting the original tool call.

### UI Components and Notification Layers

Pending approvals surface in multiple interfaces depending on the runtime environment. The React front end in [`viewer-ui/src/App.jsx`](https://github.com/andrewyng/aisuite/blob/main/viewer-ui/src/App.jsx) renders approval cards with **Approve / Deny** buttons, while unattended sessions route requests to Slack or an in-app inbox. UI mock-ups under `platform/ui-mocks/` demonstrate the layout patterns used for these decision panels.

## Step-by-Step Execution Flow

When an LLM or user invokes a sensitive tool, aisuite processes the request through the following stages:

1. **Invocation** – The framework inspects the tool’s metadata via `__aisuite_tool_metadata__`. If `requires_approval` is `True`, execution halts immediately.
2. **Context Creation** – The `ApprovalController` instantiates an `ApprovalContext` and appends it to the session’s pending list.
3. **Human Review** – The CLI prints a prompt, the web UI displays a card, or Slack posts a Block Kit message with action buttons.
4. **Resolution** – Upon receiving `allow`, the tool runs and returns its output to the caller. A `deny` response aborts the call and surfaces an error.
5. **Standing Rules** – If the user selects **Allow every time**, the controller caches a standing approval in the session for subsequent identical calls.

## Code Examples for Implementing Approval Workflows

### Mark a Custom Tool as Requiring Approval

To subject your own utility to the approval workflow, attach a `ToolMetadata` object with `requires_approval=True` in the file that defines your tool logic.

```python

# my_tools.py

from aisuite.utils.tools import tool, ToolMetadata
import os

@tool
def delete_secret(path: str) -> str:
    """Delete a secret file – high‑risk operation."""
    __aisuite_tool_metadata__ = ToolMetadata(
        name="delete_secret",
        category="file",
        requires_approval=True,  # Triggers the approval workflow

        description="Deletes a secret file from the filesystem.",
    )
    os.remove(path)
    return f"Deleted {path}"

```

This pattern mirrors the implementation of built-in risky tools in [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py).

### Run a Tool Through the CLI `ApprovalController`

For programmatic or interactive CLI usage, instantiate the controller and use it to invoke tools. You can enable `auto_approve_low_risk` to suppress prompts for non-sensitive operations.

```python
from aisuite.cli.approval import ApprovalController

# Create a controller with optional low-risk bypass

controller = ApprovalController(
    auto_approve_low_risk=True,
    on_approval=lambda ctx: print(f"Approval needed for {ctx.tool_name}")
)

# Execute a sensitive tool

result = controller.run_tool("run_shell", {"command": "rm -rf /tmp/x"})

# Execution pauses here until a human approves or denies the request

```

The controller resides in [`aisuite/cli/approval.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/cli/approval.py) and supports callbacks for logging or auditing every approval event.

### Display Approval Status in the React Viewer

The viewer UI renders the outcome of resolved approvals using badge components. When an activity includes approval metadata, the interface in [`viewer-ui/src/App.jsx`](https://github.com/andrewyng/aisuite/blob/main/viewer-ui/src/App.jsx) shows a colored indicator.

```jsx
// Inside viewer-ui/src/App.jsx
{activity.approval && (
  <Badge
    icon={CheckCircle2}
    label={activity.approval.decision === "allow" ? "Approved" : "Denied"}
    color={activity.approval.decision === "allow" ? "green" : "red"}
  />
)}

```

This ensures that operators can quickly scan a session history and identify which sensitive calls were reviewed.

### Route Unattended Approvals to Slack

For background or headless sessions, you can mirror approval requests to a Slack channel so reviewers can respond without keeping the web UI open. The [`platform/tests/test_unattended.py`](https://github.com/andrewyng/aisuite/blob/main/platform/tests/test_unattended.py) suite validates this behavior.

```python

# platform/slack/inbox.py

def mirror_approval_to_slack(approval_ctx):
    block = {
        "type": "section",
        "text": {
            "type": "mrkdwn",
            "text": f"*{approval_ctx.tool_name}* needs approval"
        },
        "accessory": {
            "type": "button",
            "text": {"type": "plain_text", "text": "Approve"},
            "action_id": f"approve_{approval_ctx.id}",
            "value": "allow",
        },
    }
    slack_client.chat_postMessage(channel="#ocw-approvals", blocks=[block])

```

Tests in [`platform/tests/test_unattended.py`](https://github.com/andrewyng/aisuite/blob/main/platform/tests/test_unattended.py) confirm that these external decisions are correctly synchronized back to the session’s `ApprovalController`.

## Edge Cases and Safeguards for Sensitive Calls

aisuite includes specific safeguards to keep approval workflows usable under real-world conditions.

- **Low-risk tools** – Tools with `requires_approval=False` bypass the controller entirely, avoiding unnecessary friction for safe operations.
- **Large argument summarization** – When a tool receives a very large payload, the controller generates a condensed summary for the approval prompt rather than rendering the full raw data. This behavior is validated in `test_approval_controller_summarizes_large_arguments`.
- **Standing approvals** – Recurring calls from the same tool on the same target can be auto-approved via session-cached rules, reducing repetitive prompts.
- **Revocation** – A standing approval can be revoked at any time; the next matching call will trigger a full approval prompt again.
- **Timeouts** – If no decision arrives within a configurable window, the request is denied by default to prevent agent hangs.

## Summary

- **aisuite** protects sensitive operations through a built-in approval workflow defined in [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py) and mediated by [`aisuite/cli/approval.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/cli/approval.py).
- Setting **`requires_approval=True`** inside a tool’s `__aisuite_tool_metadata__` is the only change needed to enroll a custom tool in the workflow.
- The **`ApprovalController`** creates an `ApprovalContext`, persists it to the session, and blocks until it receives an explicit `allow` or `deny`.
- Decisions can be collected from the **React viewer** ([`viewer-ui/src/App.jsx`](https://github.com/andrewyng/aisuite/blob/main/viewer-ui/src/App.jsx)), the CLI, or **unattended channels** such as Slack ([`platform/tests/test_unattended.py`](https://github.com/andrewyng/aisuite/blob/main/platform/tests/test_unattended.py)).
- Built-in safeguards include **low-risk auto-approval**, **large-argument summarization**, **standing rules**, and **deny-on-timeout** policies.

## Frequently Asked Questions

### What triggers an approval workflow for a tool in aisuite?

Aisuite checks the `__aisuite_tool_metadata__` object attached to every tool definition. If the `requires_approval` attribute is set to `True`—as seen in built-in utilities like `run_shell` inside [`aisuite/utils/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/utils/tools.py)—the framework routes the call to the `ApprovalController` instead of executing it immediately.

### How does aisuite handle low-risk or non-sensitive tool calls?

Tools that do not set `requires_approval=True` are considered low-risk and execute straight away. When using the CLI controller, you can further streamline operations by passing `auto_approve_low_risk=True` to suppress logging noise for safe utilities.

### Can approval workflows be automated for recurring tool calls?

Yes. The `ApprovalController` supports **standing approvals**. When a user selects an option such as **Allow every time**, the decision is cached in the current session. Subsequent identical calls are auto-approved until the standing rule is explicitly revoked.

### What happens if an approval request times out or is denied?

If a decision is not received within the configured timeout window, the controller automatically denies the request to prevent the calling agent from hanging indefinitely. When explicitly denied, the tool call is aborted and an error is returned to the caller, which the UI reflects via a red **Denied** badge in [`viewer-ui/src/App.jsx`](https://github.com/andrewyng/aisuite/blob/main/viewer-ui/src/App.jsx).