How to Implement Approval Workflows for Sensitive Tool Calls in aisuite
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, 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, 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 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:
- Invocation – The framework inspects the tool’s metadata via
__aisuite_tool_metadata__. Ifrequires_approvalisTrue, execution halts immediately. - Context Creation – The
ApprovalControllerinstantiates anApprovalContextand appends it to the session’s pending list. - Human Review – The CLI prints a prompt, the web UI displays a card, or Slack posts a Block Kit message with action buttons.
- Resolution – Upon receiving
allow, the tool runs and returns its output to the caller. Adenyresponse aborts the call and surfaces an error. - 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.
# 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.
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.
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 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 shows a colored indicator.
// 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 suite validates this behavior.
# 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 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=Falsebypass 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.pyand mediated byaisuite/cli/approval.py. - Setting
requires_approval=Trueinside a tool’s__aisuite_tool_metadata__is the only change needed to enroll a custom tool in the workflow. - The
ApprovalControllercreates anApprovalContext, persists it to the session, and blocks until it receives an explicitallowordeny. - Decisions can be collected from the React viewer (
viewer-ui/src/App.jsx), the CLI, or unattended channels such as Slack (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—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.
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 →