AI Agent Action Approval: Human-in-the-Loop for High-Risk Operations
TL;DR — AI agent action approval is the human-in-the-loop governance pattern for high-risk agent operations. Three-tier policy: allow (safe, proceed), deny (dangerous, blocked), escalate (ambiguous, needs human). Agent pauses execution, notifies approver with full context (action, reasoning, data sources, consequences), waits for decision. Fail-safe: auto-deny on timeout (default 5 min) — unanswered escalation never becomes approval. Multi-level escalation: primary reviewer → secondary → management → auto-deny. Microsoft Agent Governance Toolkit provides
require_approvalpolicy action with Slack webhook integration. AWS recommends timeout policies with safe fallbacks and escalation paths. Use for: financial transactions > threshold, data deletion, external communications, code execution, production changes. Don't use for: read-only operations, low-risk SOP actions. Integrates with agentic AI governance, OWASP ASI09, EU AI Act Art. 14, MCP security, and SOP automation.
Microsoft's Agent Governance Toolkit defines the core principle: "In regulated industries — FSI, healthcare, legal — certain agent actions require explicit human authorization. AGT's require_approval policy action pauses the agent, notifies a human, and resumes or denies based on their response."
Strata notes that organizations need "an identity-aware orchestration layer that can pause agent execution, route approval requests to authorized humans, enforce time-boxed decision windows, and log every intervention for audit."
Approval Architecture
(safe)"] Tier2["Deny
(dangerous)"] Tier3["Escalate
(needs human)"] end subgraph Approval["Approval Workflow"] Create["Create Request"] Notify1["Notify Primary
Reviewer"] Wait["Wait for Response"] Escalate["Escalate to
Secondary"] Timeout["Timeout
Auto-Deny"] end subgraph Reviewer["Human Reviewer"] Context["Review Context
(action, reasoning, impact)"] Decision["Approve / Reject"] end subgraph Audit["Audit Trail"] Log["Tamper-Evident
Decision Records"] end Action --> Evaluate Evaluate --> Tier1 Evaluate --> Tier2 Evaluate --> Tier3 Tier1 --> Action Tier2 --> Log Tier3 --> Create Create --> Notify1 Notify1 --> Context Context --> Decision Decision -->|approve| Resume Decision -->|reject| Log Notify1 --> Wait Wait -->|no response| Escalate Escalate --> Wait Wait -->|timeout| Timeout Timeout --> Log Resume --> Log
Three-Tier Policy
| Tier | Outcome | When to Use | Example |
|---|---|---|---|
| Allow | Proceed immediately | Safe, read-only, within SOP | search_documents, get_document |
| Deny | Blocked, no approval possible | Irreversibly destructive | delete_database, drop_table |
| Escalate | Pause for human review | Ambiguous, high-impact | transfer_funds > $10K, send_external_email |
Approval Decision Matrix
| Action Type | Risk Level | Auto-Approve | Require Approval | Always Deny |
|---|---|---|---|---|
| Read/query | Low | ✅ All | — | — |
| Search KB | Low | ✅ All | — | — |
| Send internal Slack | Low | ✅ Business hours | Off-hours | — |
| Send external email | Medium | — | ✅ Always | — |
| Transfer < $1K | Medium | ✅ Business hours | Off-hours | — |
| Transfer $1K-$10K | High | — | ✅ Manager | — |
| Transfer > $10K | Critical | — | ✅ Treasury + Compliance | — |
| Delete document | High | — | ✅ Data steward | — |
| Delete database | Critical | — | — | ✅ Always |
| Execute code | High | — | ✅ Engineering lead | — |
| Deploy to production | Critical | — | ✅ DevOps + Manager | — |
| Access PII | High | — | ✅ Compliance | — |
Implementation
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
import json
class ApprovalState(Enum):
PENDING = "pending"
APPROVED = "approved"
DENIED = "denied"
TIMEOUT = "timeout"
ERROR = "error"
class TimeoutAction(Enum):
DENY = "deny" # Safe default
ALLOW = "allow" # Rare — only for low-criticality
@dataclass
class EscalationRequest:
id: str
agent_id: str
action: str
action_type: str
context: dict
reasoning: str # Why the agent wants this action
potential_impact: str # What happens if approved
approvers: list[str]
escalation_chain: list[str]
timeout_seconds: int = 300
escalation_window: int = 120 # Escalate to secondary after 2 min
state: ApprovalState = ApprovalState.PENDING
created_at: datetime = field(default_factory=datetime.utcnow)
resolved_at: datetime = None
approver: str = None
approval_reason: str = None
class ApprovalEngine:
def __init__(self, notifier, audit_logger, redis_client=None):
self.notifier = notifier # Slack, email, dashboard
self.audit = audit_logger
self.redis = redis_client # Production: Redis for distributed queue
self.pending = {} # In-memory fallback
async def request_approval(
self,
agent_id: str,
action: str,
action_type: str,
context: dict,
reasoning: str,
impact: str,
approvers: list[str],
escalation_chain: list[str] = None,
timeout: int = 300,
) -> EscalationRequest:
"""Create approval request and notify reviewers."""
request = EscalationRequest(
id=str(uuid4()),
agent_id=agent_id,
action=action,
action_type=action_type,
context=context,
reasoning=reasoning,
potential_impact=impact,
approvers=approvers,
escalation_chain=escalation_chain or [],
timeout_seconds=timeout,
)
self.pending[request.id] = request
# Notify primary reviewers
await self.notifier.send(
channels=approvers,
message=f"⚠️ Approval Required: {action_type}",
details={
"action": action,
"reasoning": reasoning,
"impact": impact,
"context": context,
"request_id": request.id,
"timeout": f"{timeout}s",
},
)
# Audit log
self.audit.log(
event="approval_requested",
agent_id=agent_id,
action=action,
approvers=approvers,
request_id=request.id,
)
return request
async def wait_for_decision(
self,
request: EscalationRequest,
) -> ApprovalState:
"""Wait for human decision with escalation and timeout."""
deadline = request.created_at + timedelta(seconds=request.timeout_seconds)
escalation_deadline = request.created_at + timedelta(seconds=request.escalation_window)
while datetime.utcnow() < deadline:
# Check if decision was made
if request.state in (ApprovalState.APPROVED, ApprovalState.DENIED):
return request.state
# Escalate to secondary if primary hasn't responded
if datetime.utcnow() > escalation_deadline and request.escalation_chain:
if not request.escalation_chain_notified:
await self.notifier.send(
channels=request.escalation_chain,
message=f"🚨 Escalation: {request.action_type} (primary unresponsive)",
details={"request_id": request.id},
)
request.escalation_chain_notified = True
await asyncio.sleep(5) # Poll every 5 seconds
# Timeout: auto-deny (fail-safe)
request.state = ApprovalState.TIMEOUT
request.resolved_at = datetime.utcnow()
self.audit.log(
event="approval_timeout",
request_id=request.id,
action=request.action,
agent_id=request.agent_id,
outcome="auto_denied",
)
return ApprovalState.TIMEOUT
async def resolve(self, request_id: str, approved: bool, approver: str, reason: str):
"""Human reviewer approves or denies."""
request = self.pending.get(request_id)
if not request:
raise ValueError(f"Unknown request: {request_id}")
request.state = ApprovalState.APPROVED if approved else ApprovalState.DENIED
request.approver = approver
request.approval_reason = reason
request.resolved_at = datetime.utcnow()
self.audit.log(
event="approval_decision",
request_id=request_id,
approver=approver,
approved=approved,
reason=reason,
action=request.action,
agent_id=request.agent_id,
)
Slack Approval Integration
class SlackApprovalNotifier:
def __init__(self, webhook_url: str, bot_token: str):
self.webhook_url = webhook_url
self.bot_token = bot_token
async def send(self, channels: list[str], message: str, details: dict):
"""Send approval request to Slack channels."""
blocks = [
{
"type": "header",
"text": {"type": "plain_text", "text": message},
},
{
"type": "section",
"fields": [
{"type": "mrkdwn", "text": f"*Action:* {details['action']}"},
{"type": "mrkdwn", "text": f"*Reasoning:* {details['reasoning']}"},
{"type": "mrkdwn", "text": f"*Impact:* {details['impact']}"},
{"type": "mrkdwn", "text": f"*Timeout:* {details['timeout']}"},
],
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {"type": "plain_text", "text": "✅ Approve"},
"style": "primary",
"value": f"approve:{details['request_id']}",
"action_id": "approve_action",
},
{
"type": "button",
"text": {"type": "plain_text", "text": "❌ Deny"},
"style": "danger",
"value": f"deny:{details['request_id']}",
"action_id": "deny_action",
},
],
},
]
for channel in channels:
await self._post_to_channel(channel, blocks)
Common Pitfalls
| Pitfall | Why It's Bad | Fix |
|---|---|---|
| No timeout | Agent hangs forever | Set 5-min timeout with auto-deny |
| Timeout = auto-approve | Unsafe actions execute | Timeout = auto-deny (fail-safe) |
| All actions require approval | Reviewer fatigue, rubber-stamp | Tier policy: allow safe, deny dangerous, escalate ambiguous |
| Insufficient context | Review can't make informed decision | Show action, reasoning, impact, data sources |
| No escalation path | Stalls when primary unavailable | Multi-level: primary → secondary → management |
| Approval only in UI, not in policy | Bypass via direct tool call | Policy engine enforces approval at tool gateway |
| No audit log | Can't prove oversight | Log every decision with approver, timestamp, reasoning |
| Blocking worker while waiting | Wastes compute resources | Async: return pending state, resume on decision |
AI Agent Action Approval Checklist
- [ ] Define three-tier policy: allow, deny, escalate
- [ ] Classify all agent actions by risk level (low, medium, high, critical)
- [ ] Configure auto-approve rules for low-risk actions (business hours, below threshold)
- [ ] Configure approval requirements for high-risk actions
- [ ] Set always-deny for irreversibly destructive actions (delete database)
- [ ] Implement approval engine with request creation and notification
- [ ] Set timeout (default 5 min) with auto-deny fail-safe
- [ ] Configure escalation window (default 2 min) for secondary reviewers
- [ ] Define escalation chain: primary → secondary → management
- [ ] Set up Slack integration with approve/deny buttons
- [ ] Set up email notifications as backup channel
- [ ] Create approval dashboard for pending requests
- [ ] Provide full context to reviewers: action, reasoning, impact, data sources
- [ ] Implement audit logging for every approval decision
- [ ] Log: who approved/denied, when, reasoning, request ID
- [ ] Send audit logs to SIEM (Splunk, ELK, Datadog)
- [ ] Implement async approval: agent returns pending state, doesn't block worker
- [ ] Test fail-safe: verify auto-deny on timeout
- [ ] Test fail-safe: verify auto-deny on handler error
- [ ] Test fail-safe: verify auto-deny when no handler configured
- [ ] Test escalation: verify secondary notified after escalation window
- [ ] Test approval flow: verify agent resumes after approval
- [ ] Test denial flow: verify agent halts after denial
- [ ] Integrate with agentic AI governance policy engine
- [ ] Apply OWASP ASI09 trust exploitation defenses
- [ ] Comply with EU AI Act Article 14 (human oversight)
- [ ] Enforce approval at MCP tool gateway, not just in UI
- [ ] Use for SOP automation edge cases
- [ ] Apply zero-trust architecture to approval flow
- [ ] Monitor approval metrics with platform monitoring
- [ ] Track: approval rate, denial rate, timeout rate, average response time
- [ ] Alert on timeout spikes (reviewers not responding)
- [ ] Alert on denial spikes (agent making bad decisions)
- [ ] Consider AI incident response for approval failures
- [ ] Quarterly review: update approval thresholds and escalation chains
- [ ] Train reviewers on human-agent trust risks
- [ ] Implement "approve forever" for recurring low-risk actions (with admin role)
FAQ
What is AI agent action approval?
AI agent action approval is a governance pattern where certain agent actions require explicit human authorization before execution. The agent can decide an action is needed, but execution pauses until a human approves or rejects. Three-tier policy: allow (safe actions proceed), deny (dangerous actions blocked), escalate (ambiguous actions need human review). Use for: financial transactions above threshold, data deletion, external communications, code execution, and any irreversible action. Implementation: policy engine evaluates action, if escalation required agent pauses and notifies approver, approver reviews context and decides, agent resumes or halts. Fail-safe: auto-deny on timeout (default 5 min) or handler error.
When should AI agent actions require human approval?
Require human approval when: (1) Action is irreversible — data deletion, sending external email, executing code. (2) Financial impact exceeds threshold — transfers > $10K, refunds > $500. (3) Action affects external parties — customer notifications, vendor payments. (4) Action accesses sensitive data — PII, financial records, health data. (5) Action modifies production systems — database schema changes, deployments. (6) Action is outside normal SOP — edge cases the agent hasn't handled before. Don't require approval for: read-only operations (search, query), low-risk actions within SOP, or actions already approved by policy auto-approval rules.
What happens when an approval times out?
When approval times out (default 5 minutes), the action is auto-denied. This is the fail-safe default — if a human is supposed to review an action and nobody does, the safest assumption is that the action should not happen. An unanswered escalation should never silently become an approval. For less critical actions, you can set DefaultTimeoutAction.ALLOW, but this is rare — if the action weren't important, it wouldn't need escalation. Configure escalation paths: if primary reviewer doesn't respond within escalation window (shorter than full timeout), notify secondary reviewers. Log timeout events for audit and process improvement.
How do you implement multi-level approval escalation?
Multi-level escalation: (1) Primary reviewer notified via Slack/email with action context. (2) If no response within escalation window (e.g., 2 min), notify secondary reviewer. (3) If no response within full timeout (e.g., 5 min), auto-deny. (4) Escalation to management for critical actions. Implementation: EscalationHandler with configurable timeout, InMemoryApprovalQueue (or Redis for production), and escalation chain. Each level logs: who was notified, when, response (approve/deny/timeout). Use Slack webhooks for real-time notifications, dashboard for approval queue visibility. Consider business-hours auto-approval for low-risk actions (amount < $1K, 9am-5pm).
How does this integrate with existing governance frameworks?
Action approval integrates with: (1) OWASP ASI09 (Human-Agent Trust Exploitation) — prevents agents from exploiting trust to get unsafe approvals. (2) EU AI Act Article 14 (Human Oversight) — provides the stop mechanism and human intervention capability. (3) NIST AI RMF Manage function — enforce limits and stop agents. (4) MCP security — approval gates sit between agent and MCP tool calls. (5) Policy-as-code — approval rules defined in YAML/OPA, enforced by external engine. (6) Audit logging — every approval decision logged with approver identity, timestamp, reasoning. (7) Circuit breakers — approval failures can trigger circuit breaker to pause all agent actions.
Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →