How to Add Human Approval to AI Agent Actions

Published: (March 12, 2026 at 04:02 PM EDT)
3 min read
Source: Dev.to

Source: Dev.to

Introduction

Your AI agent might perform dangerous actions—like deleting a production database table—without any confirmation.
If your agent can send emails, write files, or call APIs, you should add a human‑approval gate for risky operations. Below is a pure‑Python implementation of such a gate.

Approval Gate Implementation

from enum import Enum
from datetime import datetime

class Risk(Enum):
    READ = "read"
    WRITE = "write"
    DESTRUCTIVE = "destructive"

TOOL_RISK = {
    "search_docs": Risk.READ,
    "list_files": Risk.READ,
    "send_email": Risk.WRITE,
    "create_file": Risk.WRITE,
    "delete_file": Risk.DESTRUCTIVE,
    "run_sql": Risk.DESTRUCTIVE,
    "deploy": Risk.DESTRUCTIVE,
}

def approve(tool_name: str, args: dict) -> bool:
    """Return True if the action is allowed, otherwise ask for human approval."""
    risk = TOOL_RISK.get(tool_name, Risk.DESTRUCTIVE)

    if risk == Risk.READ:
        return True

    if risk == Risk.WRITE:
        print(f"[LOG] {datetime.now():%H:%M:%S} | {tool_name}({args})")
        return True

    # Destructive: require human approval
    print(f"\n{'='*50}")
    print(f"APPROVAL REQUIRED: {tool_name}")
    print(f"Arguments: {args}")
    print(f"Risk level: {risk.value}")
    print(f"{'='*50}")
    response = input("Execute this action? (y/n): ").strip().lower()
    return response == "y"

def safe_tool_call(tool_name: str, args: dict, tool_fn):
    """Wrap a tool function with the approval gate."""
    if not approve(tool_name, args):
        return {"status": "blocked", "reason": "Human denied execution"}
    return tool_fn(**args)

# Example tool: delete a file
def delete_file(path: str) -> dict:
    # os.remove(path)  # uncomment in production
    return {"status": "deleted", "path": path}

# Run the example
result = safe_tool_call(
    tool_name="delete_file",
    args={"path": "/data/user_exports.csv"},
    tool_fn=delete_file,
)
print(result)

Running the Example

python approval_gate.py

Sample output when the agent tries to delete a file:

==================================================
APPROVAL REQUIRED: delete_file
Arguments: {'path': '/data/user_exports.csv'}
Risk level: destructive
==================================================
Execute this action? (y/n): n
{'status': 'blocked', 'reason': 'Human denied execution'}

Typing n blocks the action; typing y allows it to proceed. Read‑only operations skip the prompt entirely.

Risk Tiers

TierDescriptionDefault handling
READNon‑destructive queries (e.g., search_docs, list_files)Auto‑approved silently
WRITEActions that modify state but are not destructive (e.g., send_email, create_file)Auto‑approved and logged
DESTRUCTIVEOperations that can cause data loss or major changes (e.g., delete_file, run_sql, deploy)Require explicit human approval

Tools not listed in TOOL_RISK default to the DESTRUCTIVE tier, providing a safe‑by‑default fallback.

Integrating the Gate with an Agent

If your agent selects tools from a dictionary, wrap each call with safe_tool_call:

TOOLS = {
    "search_docs": search_docs,
    "send_email": send_email,
    "delete_file": delete_file,
    # add other tools here
}

for step in agent.run(task):
    tool_fn = TOOLS[step.tool_name]
    result = safe_tool_call(step.tool_name, step.args, tool_fn)
    agent.receive(result)

All tool invocations now pass through the approval gate:

  • READ tools execute immediately.
  • WRITE tools execute and produce a log entry.
  • DESTRUCTIVE tools pause for human confirmation.

Production Considerations

  • Replace input() with a webhook, Slack message, or email notification that pauses execution until an authorized user approves.
  • The core pattern remains unchanged; only the transport mechanism for the approval changes.

If you prefer a ready‑made solution, Nebula provides built‑in gates for destructive actions and sends you an email notification before execution.

Part of the AI Agent Quick Tips series. Previous tip: How to Add Retry Logic to LLM Calls in 5 Min.

0 views
Back to Blog

Related posts

Read more »