Human Approval: Approval Gates for Agents
What this article covers
- What Approval Gates are and how they keep AI agents under control
- When to require human approval and when to skip it
- How to implement approvals in LangGraph, CrewAI, and AutoGen
- What escalation patterns and veto mechanisms look like
- How to avoid approval fatigue while staying safe
Introduction
AI agents work autonomously. They plan, invoke tools, and make decisions without human intervention. That’s exactly what makes them useful, and exactly what makes them risky. A single bad call can trigger an email to all customers, delete a database, or rack up unexpected costs.
Human approval, also called Human-in-the-Loop, is the safeguard that lets agents operate independently but brings a human into the picture for critical actions. The agent plans, the human reviews, the agent executes. You get speed without surrendering control.
This article is part of the Agent Security series and builds on the foundations laid out in Human Approvals.
Why do you need human approval?
Imagine your agent is supposed to pay invoices automatically. It reads invoices, checks amounts, and triggers transfers. Without approval, it pays every invoice it finds. What happens when an invoice shows up for 50,000 euros because a vendor made a typo? The agent pays it, because it doesn’t do sanity checks.
Or picture this: your agent answers customer inquiries. It has a tool to send emails. A customer asks about a refund. The agent decides on its own to promise a 10,000 euro refund and confirm it via email. Without approval, that’s already happened.
Human approval prevents such actions from running unchecked. The agent prepares everything, you review it, you approve or reject. It takes seconds, but it saves your business.
Human approval in a nutshell
An Approval Gate is a checkpoint in an agent’s workflow. Before the agent executes an action marked as critical, it pauses. It shows you what it wants to do, with what parameters, and why. You decide: approve, reject, or modify.
The core idea is simple: not every action needs approval, but every irreversible, external, or expensive action does.
Who this article is for
This article is for developers building AI agents with frameworks like LangGraph, CrewAI, or AutoGen who want to safeguard critical actions. You should understand how Tool Calling works and what Agent Systems are. Basic Python knowledge is helpful for the code examples.
Key terminology
| Term | Definition |
|---|---|
| Approval Gate | A checkpoint where a human must approve a planned action |
| Human-in-the-Loop | A principle where a human intervenes in an automated workflow |
| Veto | The right to reject a planned action by the agent |
| Escalation | Forwarding a decision to a higher authority |
| Approval Fatigue | Exhaustion that sets in when too many approvals are requested |
| Sync Approval | Approval where the agent waits for a human decision |
| Async Approval | Approval where the agent keeps working and checks back later |
| Risk Score | A rating of how risky an action is, used to prioritize approvals |
| Whitelist | A list of allowed actions that don’t need approval |
| Blacklist | A list of forbidden actions that must never run |
When approval is necessary
Irreversible actions
Anything that can’t be undone needs approval. Deleting files, removing database records, triggering transfers. Once the action runs, there’s no going back.
External communication
Anything visible to the outside world needs approval. Sending emails, posting to Slack, publishing social media posts, making API calls to external services. Wrong content can damage your reputation or violate data protection rules.
Expensive actions
Anything that costs money needs approval. Spinning up cloud resources, making paid API calls, placing orders. Set a threshold: actions under 10 euros run automatically, anything above that requires approval.
Actions involving sensitive data
Anything that touches sensitive data needs approval. Exporting customer data, sending personal information to external services, creating database backups. Even if the action itself seems harmless, the context might be critical.
Implementation patterns
Sync Approval
With synchronous approval, the agent pauses completely. It sends you the request and waits. Only when you respond does it continue or abort. It’s the simplest approach, but it blocks the entire workflow.
import asyncio
class ApprovalGate:
def __init__(self):
self.pending = {}
async def request_approval(self, action, params, reason):
approval_id = generate_id()
self.pending[approval_id] = {
"action": action,
"params": params,
"reason": reason,
"status": "pending"
}
# Agent pauses here and waits
while self.pending[approval_id]["status"] == "pending":
await asyncio.sleep(1)
return self.pending[approval_id]["status"] == "approved"
def approve(self, approval_id):
self.pending[approval_id]["status"] = "approved"
def reject(self, approval_id):
self.pending[approval_id]["status"] = "rejected"
Async Approval
With asynchronous approval, the agent keeps working. It executes tasks that don’t need approval and comes back to the approval request later. It’s more efficient but harder to implement.
class AsyncApprovalGate:
def __init__(self):
self.queue = []
def submit_for_approval(self, action, params, reason):
task_id = generate_id()
self.queue.append({
"id": task_id,
"action": action,
"params": params,
"reason": reason,
"status": "pending"
})
return task_id
def check_status(self, task_id):
task = next(t for t in self.queue if t["id"] == task_id)
return task["status"]
def process_pending(self):
pending = [t for t in self.queue if t["status"] == "pending"]
return pending
Risk-score-based approval
Instead of manually categorizing every action, you can calculate a Risk Score. Low-risk actions run automatically, high-risk actions need approval.
def calculate_risk_score(action, params):
risk = 0
if action in ["delete_file", "drop_table"]:
risk += 50
if action in ["send_email", "post_slack"]:
risk += 30
if "amount" in params and params["amount"] > 1000:
risk += 40
if "external_api" in params:
risk += 20
return risk
def needs_approval(risk_score, threshold=50):
return risk_score >= threshold
Releases in Frameworks
LangGraph Interrupt
LangGraph provides a built-in interrupt function. The agent pauses at a defined point and waits for input. This is the most direct way to implement approval gates.
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
def execute_with_approval(state):
action = state["planned_action"]
if is_critical(action):
# Agent pauses here
approval = interrupt({
"action": action["name"],
"params": action["params"],
"reason": action["reason"]
})
if not approval:
return {"status": "rejected"}
result = run_tool(action)
return {"result": result, "status": "completed"}
workflow = StateGraph(AgentState)
workflow.add_node("plan", plan_step)
workflow.add_node("execute", execute_with_approval)
workflow.add_edge("plan", "execute")
workflow.add_edge("execute", END)
app = workflow.compile(checkpointer=MemorySaver())
CrewAI human_input
CrewAI offers a human_input=True parameter for tasks. The agent asks for confirmation before execution. This approach is straightforward but less granular than LangGraph.
from crewai import Agent, Task, Crew
reviewer = Agent(
role="Reviewer",
goal="Review emails before sending",
backstory="You are responsible for quality assurance.",
allow_delegation=False
)
send_task = Task(
description="Compose a reply email to the customer.",
agent=reviewer,
human_input=True
)
crew = Crew(agents=[reviewer], tasks=[send_task])
result = crew.kickoff()
AutoGen human_in_the_loop
AutoGen lets you deploy a UserProxyAgent. This agent forwards each planned action to a real person. You can configure how often and for which actions the human is consulted.
from autogen import ConversableAgent, UserProxyAgent
user_proxy = UserProxyAgent(
name="Human",
human_input_mode="ALWAYS",
max_consecutive_auto_reply=0
)
assistant = ConversableAgent(
name="Assistant",
system_message="You are a helpful agent."
)
user_proxy.initiate_chat(
assistant,
message="Summarize the latest emails and send them to Slack."
)
Escalation Patterns
Tiered Escalation
Not every approval needs to go to the first contact. You can define levels. Low-risk actions go to the operator, medium-risk actions go to the team lead, and critical actions go to management.
def escalate(approval_request):
risk = approval_request["risk_score"]
if risk < 30:
return "operator"
elif risk < 70:
return "team_lead"
else:
return "management"
def route_approval(approval_request):
approver = escalate(approval_request)
notify(approver, approval_request)
return wait_for_response(approver)
Timeout Escalation
If no one responds within a set timeframe, the request automatically escalates to the next level. This prevents critical actions from sitting unreviewed.
import time
def request_with_timeout(approval, timeout_seconds=300):
start = time.time()
while time.time() - start < timeout_seconds:
if check_response(approval["id"]):
return get_response(approval["id"])
time.sleep(5)
# Timeout reached, escalate
return escalate_to_next_level(approval)
Veto Rights
A veto is the power to reject an action without proposing an alternative. Veto rights should be clearly defined. Any approver can exercise a veto, but not every approver can approve an action. This prevents a single person from executing critical actions without oversight.
Avoiding Approval Fatigue
The Problem
If your agent submits every small action for approval, you eventually confirm blindly. You click “approve” without reading. This is more dangerous than no approval at all because it creates a false sense of security.
Solutions
Increase granularity: Request approvals only for actions that are truly critical. Read access, computations, and internal transformations don’t need approval.
Use risk scores: Automate the decision of whether an approval is necessary. A low score means automatic execution, a high score means approval required.
Batch actions: If the agent wants to execute 50 similar actions, show them as a list and ask once whether all should be approved. Instead of 50 individual approvals, you need one.
Maintain whitelists: Actions that are regularly approved can go on a whitelist. They then run automatically. Review the whitelist periodically.
Common Pitfalls
-
Too many approvals: If every action needs approval, nobody reviews them properly. Use approvals strategically, only for critical actions.
-
No clear criteria: If it’s undefined when an approval is needed, developers decide differently. Document the criteria and automate them through risk scores.
-
No timeout: If an approver doesn’t respond, the agent stalls. Define timeouts and escalation levels so the workflow continues.
-
Approvals without context: The agent requests approval, but the approver doesn’t see why. Always show the planned action, its parameters, and the agent’s reasoning.
-
No logging: Who approved what? Without logging this, you lose traceability. Store every approval with timestamp, approver, and decision.
-
Sync approval in long workflows: If the agent fully pauses at each approval, the workflow takes forever. Use async approval where possible so the agent keeps working.
-
No separation of roles: If the same person plans, approves, and executes, there’s no control. Separate roles: the agent plans, another person approves.
-
Veto without justification: If someone vetoes without saying why, the agent learns nothing. Require justification for every veto and log it in the audit trail.
Hardware, Costs, and Security
Human approvals don’t cost hardware, but they cost time. Each approval delays the workflow by however long a person needs to review the request. In synchronous approvals, the agent stalls; in asynchronous approvals, it keeps working, but the critical action waits.
Implementation costs are low. LangGraph, CrewAI, and AutoGen all offer built-in mechanisms. You just need a user interface for approvers to grant approvals. This could be a chat interface, a web dashboard, or an email-based solution.
From a security standpoint, approvals are one of the most important measures. They are the last barrier before an irreversible action runs. Combine them with the other measures from agent security: sandboxing, tool permissions, and guardrails. If you work locally with Ollama, the same principles apply.
How the agent decides which actions to plan in the first place ties into planning and reflection. Good planning reduces the number of critical actions and thus the number of required approvals.
Further Reading
- Agent Security - Overview of all security measures
- Secure Operations - Overview of all articles on secure operations
- Human Approvals - Fundamentals of Human-in-the-Loop
- Tool Calling - How agents invoke tools
- Agent Systems - Architecture of agent systems
- Planning and Reflection - How agents plan
- LangGraph - Framework with interrupt support
- Ollama - Run local AI models
FAQ
What is an Approval Gate?
An approval gate is a control point in an AI agent’s workflow. Before the agent executes an action marked as critical, it pauses and requests human approval. The action only proceeds after approval is granted.
When do I need human approvals?
Human approvals make sense for any action that is irreversible, has external consequences, or incurs costs. This includes file deletions, email sends, API calls to external services, and payments.
What’s the difference between sync and async approval?
In sync approval, the agent pauses completely and waits for a decision. In async approval, the agent continues working and checks later whether approval was granted. Async is more efficient but more complex.
What is approval fatigue?
Approval fatigue occurs when an agent requests too many approvals. Approvers start confirming blindly without examining the requests. This is dangerous because it creates a false sense of security. The solution is to use approvals strategically and employ risk scores.
What is a veto right?
A veto right allows an approver to reject a planned action without needing to propose an alternative. Veto rights should be clearly defined and tied to specific roles.
How do I implement approvals in LangGraph?
LangGraph offers the interrupt function. The agent pauses at a defined point in the graph and waits for input. You can store the input via a checkpointer and resume later.
Do I need approvals with local models?
Yes. Even if you work locally with Ollama and don’t send data externally, the agent can still perform irreversible actions. Approvals are independent of where the model runs.
What is a risk score?
A risk score is a numerical rating of how risky an action is. It’s computed from factors like action type, parameters, and context. Actions with high scores require approval; actions with low scores run automatically.
How do I keep the agent from stalling during approvals?
Use async approval. The agent continues executing non-critical tasks while waiting for approval on the critical action. Alternatively, you can set timeouts and escalate when they’re exceeded.
Can I automate approvals?
Partially. You can calculate risk scores and automatically approve actions with low scores. Actions with high scores should always involve a human. Full automation contradicts the purpose of Human-in-the-Loop.
How do I log approvals?
Record the timestamp, approver, planned action, parameters, and decision (approved, rejected, modified) for each approval. The log should be stored outside the agent’s reach so it can’t manipulate it.
Sources
- LangGraph Documentation: Human-in-the-Loop Workflows
- CrewAI Documentation: Human Input in Tasks
- AutoGen Documentation: UserProxyAgent and Human-in-the-Loop
- OWASP: Top 10 for Large Language Model Applications
- NIST: AI Risk Management Framework


