What are Approvals?
Approvals are human-in-the-loop workflows that require manual review before an AI agent can execute sensitive or high-risk actions. They provide a safety mechanism to prevent agents from making irreversible or costly mistakes.
How Approvals Work
Agent Requests Action
Your AI agent calls guard.check() for an action that requires approval
Approval Request Created
AgentWarden creates an approval request and returns requires_approval: true
Human Reviews
An authorized user reviews the request in the dashboard
Decision Made
The user approves or denies the request
Agent Proceeds (or Not)
Your application polls for the decision and acts accordingly
When to Use Approvals
Consider requiring approval for actions that:
High Financial Impact Large transactions, refunds over a threshold, subscription cancellations
Irreversible Changes Data deletion, account termination, production deployments
External Communications Mass emails, social media posts, customer notifications
Security Sensitive Permission changes, access grants, credential updates
Configuring Approvals
Via Dashboard
When creating or editing a permission:
Open Permission
Navigate to your agent and click on a permission (or create a new one)
Toggle Approval
Enable the Requires Approval toggle
Via API
curl -X POST https://api.agentwarden.io/api/agents/AGENT_ID/permissions \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action": "deploy.production",
"requires_approval": true
}'
SDK Integration
Basic Pattern
from agentwarden import AgentWarden
guard = AgentWarden( api_key = "your-api-key" )
# Check if action is allowed
result = guard.check(
agent_id = "devops-agent" ,
action = "deploy.production" ,
context = {
"version" : "v2.1.0" ,
"environment" : "production" ,
"requested_by" : "ai-agent"
}
)
if result.allowed:
# No approval needed - proceed immediately
deploy_to_production()
guard.log( "devops-agent" , "deploy.production" , "success" )
elif result.requires_approval:
# Approval required - wait for human decision
print ( "⏳ Deployment requires approval" )
approval_id = result.approval_id
# Notify admins
notify_admins(
title = "Production Deployment Approval Needed" ,
details = f "Version { version } waiting for approval" ,
approval_url = f "https://app.agentwarden.io/approvals/ { approval_id } "
)
# Queue the deployment for later
queue_deployment( version = "v2.1.0" , approval_id = approval_id)
else :
# Action blocked for other reasons
print ( f "🚫 Deployment blocked: { result.reason } " )
Polling for Approval
If your application needs to wait for approval:
import time
def wait_for_approval ( approval_id , timeout = 3600 ):
"""
Poll for approval decision with timeout
Args:
approval_id: The approval request ID
timeout: Maximum seconds to wait (default 1 hour)
Returns:
True if approved, False if denied or timeout
"""
start_time = time.time()
while time.time() - start_time < timeout:
# Check approval status
status = guard.get_approval_status(approval_id)
if status == "approved" :
return True
elif status == "denied" :
return False
# Wait 30 seconds before checking again
time.sleep( 30 )
# Timeout reached
return False
# Usage
if result.requires_approval:
print ( "⏳ Waiting for approval..." )
if wait_for_approval(result.approval_id):
print ( "✅ Approved! Proceeding..." )
deploy_to_production()
guard.log( "devops-agent" , "deploy.production" , "success" )
else :
print ( "❌ Denied or timeout" )
guard.log( "devops-agent" , "deploy.production" , "denied" )
Webhook-Based Approach (Recommended)
Instead of polling, use webhooks to be notified when a decision is made:
from flask import Flask, request
app = Flask( __name__ )
@app.route ( '/webhooks/approval-decision' , methods = [ 'POST' ])
def handle_approval_decision ():
data = request.json
approval_id = data[ 'approval_id' ]
decision = data[ 'decision' ] # 'approved' or 'denied'
action = data[ 'action' ]
context = data[ 'context' ]
if decision == 'approved' :
# Execute the pending action
if action == 'deploy.production' :
deploy_to_production(context[ 'version' ])
elif action == 'stripe.refund' :
process_refund(context[ 'amount' ])
else :
# Log that it was denied
print ( f "Action { action } was denied" )
return { 'status' : 'ok' }
Approval Dashboard
Viewing Pending Approvals
Navigate to Dashboard
Click Dashboard or Approvals in the sidebar
View Pending
See all pending approval requests
Review Details
Click on a request to see full context
Approval Details
Each approval request shows:
Agent that requested the action
Action being requested
Context provided (amounts, parameters, etc.)
Timestamp of the request
Requester (if available)
Making a Decision
Review the request details
Click Approve
Optionally add a comment
Confirm approval
Review the request details
Click Deny
Provide a reason (recommended)
Confirm denial
Approval Context
Always provide rich context when requesting approval:
Good Context Example
result = guard.check(
agent_id = "customer-support-bot" ,
action = "stripe.refund.large" ,
context = {
"amount" : 500.00 ,
"customer_id" : "cus_ABC123" ,
"customer_email" : "john@example.com" ,
"order_id" : "ord_XYZ789" ,
"reason" : "Defective product" ,
"requested_by" : "support-agent-42" ,
"conversation_url" : "https://zendesk.com/tickets/12345"
}
)
Poor Context Example
result = guard.check(
agent_id = "customer-support-bot" ,
action = "stripe.refund.large" ,
context = {
"amount" : 500.00 # Not enough context to make a decision!
}
)
The reviewer needs enough information to make an informed decision. Include:
Who is affected (customer ID, email)
What is being done (amount, resource)
Why it’s needed (reason, ticket link)
When it was requested (timestamp)
Where to find more info (links)
Approval Timeouts
You can set timeouts for approval requests:
# In your application logic
APPROVAL_TIMEOUT = 3600 # 1 hour
if result.requires_approval:
approval_id = result.approval_id
created_at = time.time()
# Store approval request with timeout
store_approval_request({
'id' : approval_id,
'expires_at' : created_at + APPROVAL_TIMEOUT ,
'action' : 'deploy.production' ,
'context' : context
})
Automatic approval timeouts are coming soon as a built-in feature.
Approval Workflows by Use Case
E-commerce Refunds
# Tiered refund approval
def request_refund ( customer_id , amount , reason ):
# Small refunds - auto-approve
if amount <= 50 :
action = "stripe.refund.small"
# No approval needed
# Medium refunds - auto-approve
elif amount <= 200 :
action = "stripe.refund.medium"
# No approval needed
# Large refunds - require approval
else :
action = "stripe.refund.large"
# Approval required
result = guard.check(
agent_id = "support-bot" ,
action = action,
context = {
"amount" : amount,
"customer_id" : customer_id,
"reason" : reason
}
)
if result.requires_approval:
notify_finance_team(customer_id, amount, reason)
return "pending_approval"
elif result.allowed:
process_refund(customer_id, amount)
return "approved"
else :
return "denied"
DevOps Deployments
def deploy ( environment , version ):
action = f "deploy. { environment } "
result = guard.check(
agent_id = "devops-agent" ,
action = action,
context = {
"environment" : environment,
"version" : version,
"commit" : get_git_commit(),
"tests_passed" : run_tests(),
"deployed_by" : "automation"
}
)
if result.requires_approval:
# Production deploys need approval
slack_notify(
channel = "#deployments" ,
message = f "🚀 Production deployment of { version } requires approval"
)
return "pending"
elif result.allowed:
# Staging deploys proceed automatically
execute_deployment(environment, version)
return "deployed"
Content Moderation
def delete_user_content ( user_id , content_id , reason ):
result = guard.check(
agent_id = "moderation-bot" ,
action = "content.delete" ,
context = {
"user_id" : user_id,
"content_id" : content_id,
"reason" : reason,
"flagged_by" : "ai_moderation" ,
"content_url" : f "https://app.com/content/ { content_id } "
}
)
if result.requires_approval:
# Borderline cases need human review
create_moderation_ticket(user_id, content_id, reason)
return "under_review"
elif result.allowed:
# Clear violations can be auto-deleted
delete_content(content_id)
notify_user(user_id, reason)
return "deleted"
Approval Metrics
Track approval metrics in your dashboard:
Approval Rate : % of requests approved vs denied
Average Response Time : How long approvals take
Pending Count : Current backlog
Top Actions : Which actions require most approvals
View Metrics See approval analytics in your dashboard
Best Practices
Always include detailed context in approval requests. The reviewer should be able to make a decision without additional research.
2. Set Reasonable Timeouts
Don’t set approval timeouts too short. Consider time zones and business hours when setting SLAs.
3. Notify the Right People
Send approval notifications to people who can actually make the decision. Use role-based routing.
Create automatic approval for low-risk variants and require approval only for high-risk ones.
Regularly review approval patterns. If you’re always approving certain requests, consider making them automatic.
6. Handle Denials Gracefully
When an approval is denied, provide clear feedback to the user and log the decision.
Have a plan for what happens if an approval times out or is denied.
Security Considerations
Who Can Approve?
By default, all users with admin role can approve requests. On Enterprise plans, you can configure:
Role-based approvals
Multi-level approvals (2+ approvers required)
Specific approvers for specific actions
Approval Audit Trail
All approval decisions are logged with:
Who made the decision
When it was made
The decision (approve/deny)
Any comments provided
IP address of the approver
This creates a complete audit trail for compliance.
Approval Notifications
Configure where approval notifications are sent:
Email
Slack
SMS (Enterprise)
Webhook to your system
Configure Notifications Set up approval notifications
Advanced: Conditional Approvals
Some use cases require approvals only under certain conditions:
def smart_approval_check ( amount , customer_lifetime_value ):
"""
Require approval based on business logic
"""
# High-value customers get higher auto-approval limits
if customer_lifetime_value > 10000 :
threshold = 500
else :
threshold = 100
# Determine if approval is needed
if amount > threshold:
action = "stripe.refund.requires_approval"
else :
action = "stripe.refund.auto_approve"
return guard.check(
agent_id = "support-bot" ,
action = action,
context = {
"amount" : amount,
"customer_ltv" : customer_lifetime_value
}
)
FAQ
What happens if no one approves a request?
Requests remain pending indefinitely unless you implement timeouts in your application logic. We recommend setting reasonable timeouts.
Can I automate approvals based on rules?
Not currently, but this is on our roadmap. For now, use different permissions with different approval requirements.
Can I see who approved/denied a request?
Yes, all approval decisions include the user who made the decision and when.
Can I require multiple approvals?
Multi-level approvals are available on Enterprise plans. Contact sales for details.
How do I cancel a pending approval?
You can manually deny the request in the dashboard, or implement auto-cancellation after a timeout in your code.
Next Steps