AI agents

Human-in-the-Loop for Tool Calls: When to Require Approval

Antonio Evans10 min read

A horizontal pipeline diagram: an agent box on the left proposing a write action, an arrow into a gate labeled with two paths, one path going straight through for low-risk calls.
Key points

Key points

  1. 1Require human approval only for writes that are irreversible or have a large blast radius, and let reversible, logged actions run on their own.
  2. 2Classify every tool by reversibility and scope, then attach the full payload and a diff to each approval request so reviewers judge the actual change.
  3. 3Give each action type its own timeout and a safe default, and make irreversible actions fail closed rather than auto-approve.
  4. 4Track approval latency and review load so the queue stays short and reviewers keep reading instead of rubber-stamping.

Classify tools before you gate them

The first oversight decision is not "who approves?" but "which calls need a person at all?". Treating every tool call as approval-worthy produces a queue nobody reads. Treating none as approval-worthy produces an incident. The work is to sort your tool registry into categories that map cleanly to a default oversight level.

Anthropic's guidance on tool design starts from the fact that tools are a contract between a deterministic system and a non-deterministic agent, and that the agent can occasionally fail to grasp how to use a tool at all. That framing matters here: you cannot assume the agent will only call send_customer_email with sensible arguments, so the gate has to sit where the cost of a bad argument is high. The same write-up stresses namespacing tools to define clear boundaries, which also gives you clean categories to attach approval policy to (writing effective tools for agents).

Permission tiers are explicit design work, not a UX preference. Read operations, write operations, and destructive operations should carry different approval requirements, and standing permission for an agent to send email, move funds, or modify production systems without a human in the loop is something security standards prohibit (AI agent tool use best practices). That gives you a three-way split to start from.

A practical classification uses two axes: can the effect be undone, and how far does it spread.

TierExamplesDefault treatment
ReadSearch, fetch record, query statusRun freely, log the call
Reversible internal writeTag a ticket, save a draft, post to an internal dashboardRun, log, sample for review
External or costly writeSend customer email, issue refund, change permissionsRequire approval, fail closed
Destructive or high blast radiusDelete data, rotate credentials, bulk update recordsRequire approval plus a named second reviewer

Two rules make this table usable. First, if you cannot state how to undo an action in one sentence, it belongs in the bottom two rows. Second, if a tool can do both a read and a write depending on an argument, split it. Single-responsibility tools give the model an unambiguous function and give you cleaner error handling and easier observability (AI agent tool design). A manage_customer tool with an action parameter cannot be gated sensibly because the gate would have to inspect the argument before deciding, which is exactly where mistakes hide.

There is also a scale signal. One practitioner guideline warns that if an agent has more than eight tools registered at once, you have a design problem rather than a capability gap (AI agent tool use best practices). Fewer tools per agent means a shorter, more reviewable approval list.

Put the pause in the harness, not the prompt

An instruction like "ask before sending email" is not a control. It is a suggestion the model can reason its way past, especially after a long context full of plausible justification. The production harness accounts for the large majority of agent reliability, with validation logic, permission checks, and retry handling living in the surrounding orchestration code rather than inside the prompt (AI agent tool use best practices). Approval belongs in that layer.

The mechanism is straightforward at the protocol level. A tool call is a structured request from the model to run a named function with arguments, and your code decides what to do with it before any side effect happens (function calling). The agent loop is: model proposes a call, your code executes or defers it, the result goes back as an observation, and the model continues (function calling in AI agents). Approval is the branch where your code returns "pending" instead of an observation.

Framework support exists in several shapes. The Microsoft Agent Framework wraps a function in an approval-required wrapper, and the agent run completes with a request object containing the function name and arguments instead of a result; the caller gathers the decision and passes it back in a new run (function tools with approvals). Cloudflare documents three distinct placement options, including a durable workflow gate that can wait for months without keeping an agent running (human-in-the-loop patterns).

Durability is the part teams underestimate. An approval can take hours or days. A process that holds a live agent in memory while waiting will lose the request on the next deploy. Temporal's approval pattern blocks the workflow with a timeout and unblocks on a signal carrying the approver identity, decision, comments, and timestamp, which also avoids hand-rolled polling and race conditions between timeouts and late approvals (approval pattern).

A minimal shape for the gate, framework-agnostic:

def dispatch(call, policy):
    tier = policy.tier_for(call.name)
    if tier in ("read", "reversible_internal"):
        result = execute(call)
        audit.record(call, result, approver=None)
        return result
    item = queue.enqueue(
        action=call.name,
        arguments=call.arguments,
        payload=policy.render_payload(call),
        diff=policy.render_diff(call),
        action_type=policy.action_type(call),
        requested_at=now(),
    )
    audit.record(call, result=None, approver=None, approval_id=item.id)
    return {"status": "awaiting_approval", "approval_id": item.id}

Note what the function does not do: it does not ask the model whether the action is safe. The policy table, written by a person, makes that call.

Design the approval item so a reviewer can decide in seconds

Most approval queues fail on presentation, not on policy. A reviewer who sees "Agent wants to call update_account" cannot judge anything and will either approve blindly or open four other systems to reconstruct context. Both outcomes are expensive.

The item should carry the full payload, the intended change as a diff, and the reason. A useful approval item has five fields: the action, the justification, the blast radius, and the confidence with an alternative, plus the timeout behavior (approval timeouts). Treat that list as a checklist.

FieldWhat it containsWhy it matters
ActionTool name and exact argumentsRemoves ambiguity about which system is touched
PayloadFull request body or message text as it will be sentA reviewer must read the words the customer will read
DiffBefore and after for record changesShows scope, not just intent
Blast radiusCount and identity of affected records or recipientsDistinguishes one record from ten thousand
JustificationThe agent's stated reason, plus source referencesLets a reviewer spot invented facts
Timeout behaviorDeadline and what happens on expiryPrevents silent rot

The diff is the field teams skip and the one that catches the most problems. "Update 3 records" reads fine. A diff showing a status changed from active to cancelled across three enterprise accounts, with no matching support ticket, is an obvious reject. Rendering a diff costs you one query against the current state before enqueueing, which is cheap compared with a wrong external write.

For external communications, the payload is non-negotiable. A reviewer approving an email needs the exact subject line and body, not a summary. This is where tool design pays off: if your email tool takes a structured recipient, subject, and body rather than a free-form blob, the approval screen can render each field and highlight anything unusual, like a recipient domain outside your customer list.

Return errors to the agent in a form it can act on. When a tool fails, an opaque status code leaves the agent stuck or looping; an error that states what failed, why, and what to try instead lets it self-correct (error handling for AI agents). The same principle applies to a rejection: send back the reviewer's reason so the agent can re-plan instead of retrying the identical call.

Set timeouts with a default chosen by reversibility

An approval queue without a timeout policy fills with items nobody decided. Two failure modes follow. The graveyard: nothing happens on expiry, the agent stalls, and people learn the queue is where requests go to die. The global auto-approve: someone adds "anything older than 24 hours is approved," and the most dangerous item in the queue, the one nobody felt comfortable approving, now goes through automatically (approval timeouts).

The fix is a per-action-type policy with four possible defaults, chosen by asking how expensive it is to undo the action if the default is wrong.

DefaultUse forBehavior on expiry
ApproveInternal, reversible, low impactExecute and log
Hold and escalateAnything leaving your system or moving moneyRoute to next approver, keep waiting
CancelActions whose value expiresClose, notify requester
Re-planActions built on data that may be staleReturn to agent to re-check facts

Started timeouts should be roughly 30 minutes for time-sensitive customer replies, 4 working hours for refunds, and 24 hours for internal changes, then tuned from real data (approval timeouts). The tuning signal is the distribution, not the average. If the 90th percentile approval time exceeds the timeout, the timeout fires on normal days and people start ignoring it. If the median is a few seconds, reviewers are likely not reading, which is the more serious problem.

Temporal's pattern handles the race you will otherwise hit: a signal and a timeout arriving close together. The workflow unblocks on whichever fires first and records which one did, so a late approval cannot be applied to an already-expired request (approval pattern). Cloudflare's durable gate shows the timeout as a parameter on the wait itself, which keeps the policy visible in the workflow code (human-in-the-loop patterns).

One worked example, because the arithmetic decides the design. Suppose an agent handles 2,000 tool calls a day. A blanket approval policy on all writes sends 400 items to review. At 90 seconds per item, read properly, that is 10 hours of reviewer time, so either you staff two reviewers or the median review time collapses to a few seconds and the gate is theater. Now apply the classification: 30 of those writes are external or destructive. That is 45 minutes of review a day for one person, which is sustainable indefinitely. The ratio between these two numbers is the argument for classification, and it is usually between ten and fifty to one.

Route reviews so the queue stays short

A single shared inbox for approvals degrades fast. The person who owns refunds is not the person who owns permission changes, and neither wants to see internal dashboard posts. Routing should follow action type to a named rota, with an explicit target response time.

Three routing rules cover most cases. Send each action type to a rota of at least three people, with a published target response time so the expectation is not implicit. Keep an escalation list per action type, so the hold-and-escalate default has somewhere to go. And route high blast radius items to a second reviewer regardless of who owns the type.

The framework comparison matters less than the placement. LangGraph pauses a graph with an interrupt and resumes on human input, CrewAI exposes a human input flag, and dedicated tools exist for pushing approval requests into Slack or email (human-in-the-loop for AI agents). Pick based on where your agent already runs, then make the rota and the target response time explicit in the policy table alongside the timeout.

Two anti-patterns to route around. First, letting the agent pick its own approver. Second, letting the agent construct the justification without showing its sources. If the reason field is free text the agent wrote with no references, a reviewer is reading a persuasive essay rather than evidence. Require the justification to cite the records it read.

Keep an audit trail that answers who approved what

Audit is not a log of tool calls. It is a record of decisions. For every gated call you need the proposed payload, the policy that routed it, the approver identity, the decision, the reason, and the timestamps for request, decision, and execution. Temporal's structured approval data type is a good model: approver, decision, comments, and timestamp, carried as data rather than a boolean (approval pattern).

Write the audit record at request time, not at execution time. If the process dies between approval and execution, you still want the proposal on record. Then write a second record on execution with the outcome. The pair answers "what did we approve?" and "what actually happened?" separately, which is what an auditor asks.

Agents may hallucinate actions, misuse permissions, or overreach, and a lack of traceability means nobody knows who authorized what or why (human-in-the-loop for AI agents). A structured approval record with a named approver turns that gap into an answerable question.

For the non-gated traffic, log the call with the same schema and approver: null. That gives you one query surface for both paths and lets you sample reversible writes to check the classifier is still right.

Measure review load and tighten the classifier

The classifier is a hypothesis. Measure it. Three numbers tell you whether oversight is working:

  • Approval volume per action type per day, which shows where the gate costs the most.
  • Median and 90th percentile decision time per action type, which shows whether reviewers are deciding or deferring.
  • Rejection rate per action type, which shows whether the gate is catching real problems.

A rejection rate near zero across a type over a long window suggests the gate is either unnecessary or the reviewers are not reading. Pair it with decision time: fast decisions and zero rejections mean rubber-stamping, and the fix is better approval items (full payload, real diff) rather than removing the gate. Slow decisions and a healthy rejection rate mean the gate is doing work and you should staff it properly.

A low rejection rate with slow decisions and full payloads is the good case, and it is the signal to consider moving the type down a tier. Sample the approved items monthly and read a handful yourself. If you cannot find a single one you would have rejected, the tier is probably one level too strict.

When you want one credential to reach every model that backs these agents, so the oversight layer sees consistent tool schemas and call shapes across providers, Quanta Inference routes text, image, video, audio, and embedding models through a single OpenAI-compatible API key: https://quantaintelligence.ai/inference.

FAQ

FAQ

Which tool calls should require human approval?

Only writes that are hard to undo or that spread widely: payments and refunds, deletions, external emails, and permission or access changes. Everything else should run with logging and occasional sampling. The test is a single sentence: if you cannot describe how to reverse the action, it needs a gate.

Should an approval request ever auto-approve?

Yes, but only for action types that are internal, reversible, and low impact, such as posting to an internal dashboard or saving a draft. Anything leaving your system or moving money should hold and escalate instead, and destructive actions should fail closed. A single global auto-approve rule after a fixed delay is the failure mode to avoid (approval timeouts).

How long should an approval timeout be?

Set it per action type, starting around 30 minutes for time-sensitive customer replies, a few working hours for refunds, and 24 hours for internal changes. Then check your own data: if the 90th percentile decision time exceeds the timeout, raise it, because timeouts firing on normal days train people to ignore the queue.

Where should the approval pause live in the code?

In the orchestration harness that executes tool calls, never in the system prompt. The harness is where validation, permission checks, and retry handling belong, and prompt-based filters can be reasoned around (AI agent tool use best practices). Make the wait durable, since approvals can take days.

What does a good approval item contain?

The exact tool name and arguments, the full payload as it will be sent, a before-and-after diff for record changes, the blast radius with affected record counts, and the agent's justification with source references. Add the deadline and the timeout behavior. A reviewer should be able to decide without opening another system.

Sources

Sources

  1. Writing effective tools for AI agents, using AI agents - Anthropic
  2. AI Agent Tool Use Best Practices for Practitioners - MLflow
  3. AI Agent Tool Design: What Works and What Doesn't
  4. Function calling | OpenAI API
  5. Function Calling in AI Agents - Prompt Engineering Guide
  6. Using function tools with human in the loop approvals
  7. Human-in-the-loop patterns · Cloudflare Agents docs
  8. Approval Pattern | Temporal Documentation
  9. Approval Timeouts for AI Agents: Defaults by Reversibility
  10. Error Handling for AI Agents: A Different Standard Than Classic Apps
  11. Human in the Loop AI Agents: Approval Workflows That Work
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles