AI agents

Approval Fatigue: Measuring Reviewer Load and Tuning the Gate

Antonio Evans9 min read

A split scene of a review dashboard next to a factory-style gate.
Key points

Key points

  1. 1Approval fatigue shows up as fast approvals: log time from request to decision, review volume per reviewer, and approvals under a fixed threshold such as five seconds.
  2. 2Track the rubber-stamping rate per tool and per reviewer, because a low override rate is equally consistent with a good classifier and with reviewers who stopped reading.
  3. 3Route each tool action to one of three gates (auto-approve, human queue, block) using reversibility, blast radius and classifier confidence, not a single global policy.
  4. 4Give every queue item a timeout and a default action chosen by how hard the action is to undo, so silence never approves a refund.

Measure the queue before you tune the gate

Most teams instrument the agent and not the reviewer. They log model latency, token counts and tool error rates, then discover months later that nobody reads the approval prompts. The fix starts with three numbers per tool, captured in the approval service rather than in the agent, so the data survives prompt rewrites and model swaps.

  1. Review volume: how many approval requests each tool generates per hour, per day and per reviewer. This tells you whether the queue is sized for the people clearing it.
  2. Time per decision: the gap between when an item was first shown to a reviewer and when it was approved or rejected. Capture the render timestamp on the client, not the fetch timestamp on the server.
  3. Rubber-stamping rate: the share of approvals completed under a fixed threshold (five seconds is a reasonable starting point for a request that asks a person to read a rationale and inspect parameters) and the share of approvals where the reviewer changed no field.

The third metric is the one that matters most and the one most teams skip. Anthropic's own measurement of its coding product found that Claude Code users approved 93% of permission prompts, which is what a queue looks like when the prompts have stopped being read as reported in this analysis of approval gate design. A high approval rate is not proof the agent is safe. It is equally consistent with reviewers who have disengaged, and the two look identical in a dashboard unless you plot time per decision.

The psychology is well documented. Vigilance research going back to Mackworth's experiments shows detection accuracy falling 10 to 15% within the first 30 minutes of a monitoring task, with the decrement becoming significant inside 15 minutes summarized alongside the automation bias literature. Automation bias compounds it: when reviewers are shown the model's suggested category, accuracy on the wrong suggestions drops sharply, and seniority only slows the decline documented in the radiology study coverage. A reviewer clearing a queue for an afternoon is running exactly that experiment, so instrument the queue as a human-factors system, not just as a workflow.

A fourth metric is worth adding if you can afford it: planted-error detection rate. Seed a small percentage of requests with a known-bad action and measure whether reviewers catch them. That converts "did anyone read this?" from an inference into a measurement.

A minimal schema for review telemetry

The schema below is deliberately boring. One row per approval request, written by the approval service, with the tool name and risk class denormalized so you can group without joins.

CREATE TABLE approval_events (
  request_id      uuid PRIMARY KEY,
  tool_name       text NOT NULL,          -- e.g. 'send_customer_email'
  risk_class      text NOT NULL,          -- 'auto' | 'queue' | 'block'
  model_confidence numeric,               -- classifier score, nullable
  requester       text NOT NULL,          -- agent or workflow id
  shown_at        timestamptz NOT NULL,   -- first render to reviewer
  decided_at      timestamptz,            -- approve/reject click
  decision        text,                   -- 'approved' | 'rejected' | 'expired'
  reviewer        text,
  fields_edited   int DEFAULT 0,          -- did reviewer change the proposal?
  planted_error   boolean DEFAULT false   -- was this a seeded bad action?
);

Two design rules make this usable. First, store shown_at from the browser, because server-side timestamps on a paginated queue will understate how long an item sat unread. Second, never update rows in place: append a new event when the decision changes, so an approval that was later reversed stays visible in the history.

Then compute the three metrics as a weekly rollup per tool and per reviewer:

SELECT tool_name,
       count(*)                                                 AS requests,
       percentile_cont(0.5) WITHIN GROUP (ORDER BY decided_at - shown_at) AS median_seconds,
       avg(CASE WHEN decided_at - shown_at < interval '5 seconds' THEN 1.0 ELSE 0 END) AS fast_approval_rate,
       avg(CASE WHEN fields_edited = 0 AND decision = 'approved' THEN 1.0 ELSE 0 END) AS no_edit_approval_rate
FROM approval_events
WHERE shown_at > now() - interval '7 days'
GROUP BY tool_name;

Read the output in pairs, not in isolation. A tool with a five-second median and a 95% fast-approval rate is a tool whose gate is decorative. A tool with a 40-second median and a 30% edit rate is one where the reviewer is genuinely doing work, and it may deserve to keep its gate or even gain a stricter one. A tool with a two-minute median and almost no rejections over months is either well tuned or badly sampled, and the planted-error rate tells you which.

The decision rule that moves tools between gates

The common mistake is treating gate placement as a property of the tool. It is a property of the tool plus the action instance. A send_customer_email tool sending an internal status update and one sending a refund confirmation are not the same risk, so the routing decision needs both the tool's risk class and the instance's context.

The rule I recommend to teams has three steps, applied in order:

  1. Reversibility first. If the action cannot be undone within your normal window (money moved, data deleted, message sent outside the organization), it never auto-approves regardless of model confidence. The orbiresearch timeout analysis draws the same line: actions that leave the system or move money "never go through on silence" see the defaults-by-reversibility pattern.
  2. Blast radius second. Count how many records, users or downstream systems one call touches. A bulk update touching 40,000 rows deserves a gate even if each individual change is reversible.
  3. Confidence last, and only as a tiebreaker within the reversible, small-blast-radius set.
Action profileExampleDefault gateTimeout default
Internal, reversible, low impactPost summary to internal dashboardAuto-approve with audit logNot applicable
Internal, reversible, high volumeTag or relabel a ticketAuto-approve, 1% sampled to queueNot applicable
External, reversible within windowDraft an email for a customerHuman queueEscalate after 30 minutes
External, hard to reverseIssue refund, pay invoiceHuman queue, second approver above thresholdEscalate after 4 working hours, never auto-approve
Value expiresBook same-day slot, act on a priceHuman queueCancel after expiry window
Based on possibly stale dataReorder stock at a recorded levelHuman queueReturn to agent to re-check facts
Blocked outrightDelete archival records, change access controlBlock; requires out-of-band approvalNot applicable

The table encodes the same four timeout defaults (approve, escalate, cancel, re-plan) that orbiresearch recommends, chosen per action type rather than globally from the same timeout guide. The important discipline is that the person who owns the queue writes the default for each action type in advance, in writing. It is a product decision, and it is the one decision teams most often leave to whichever engineer wires up the queue.

The confidence component needs a calibrated score. If your classifier's 0.9 does not mean what it says, the routing thresholds are noise. Keep the threshold conservative at first and widen it only when the override data supports it.

What actually happens when nobody answers

An approval queue without a timeout policy rots in one of two predictable ways. Either nothing happens on timeout and the queue fills with items nobody decided until reviewers learn to ignore it, or someone gets tired of the backlog and adds a global "approve anything older than 24 hours" rule. The second failure is worse, because it automatically clears the single most dangerous item in the queue: the one nobody felt comfortable approving described as rebuilding the rubber stamp with a delay.

Temporal's approval pattern gives you the mechanics for free. A workflow blocks on a condition with a timeout, an approver submits a decision via a signal, and the timeout path escalates or rejects if no signal arrives the pattern and its timeout handling are documented with code. Microsoft's agent framework exposes the same idea at the tool level: wrap a function in an approval-required wrapper and the run returns a request object that the caller resolves with a yes or no as shown in the framework's tool approval tutorial. Cloudflare's workflow approval makes the durable wait explicit, with a timeout that can run for days without keeping an agent alive via its waitForApproval primitive.

Whichever primitive you use, the review service should record the timeout outcome as a decision. An expired item that escalated is not the same as one that was cancelled, and both need to appear in the weekly rollup so you can see whether the timeout is firing on normal days. If it is, the timeout is too short or the queue is understaffed, and reviewers will start ignoring it.

Design the request so a person can actually judge it

Approval fatigue is partly a volume problem and partly a presentation problem. A request that shows the raw tool call and a wall of JSON costs the reviewer more attention than one that shows the decision they are being asked to make: what will happen, to what, how many things it touches, and what happens if nobody answers.

Two lessons from tool design transfer directly. First, single-responsibility tools make gates far easier to judge. A manage_customer tool with an action parameter forces the reviewer to work out which mode is being invoked before they can assess risk, whereas a dedicated suspend_customer tool tells them exactly what is about to happen the single-responsibility argument is made with code examples here. Second, tight schemas make invalid states impossible, so the reviewer does not need to check whether the action is well-formed, only whether it is wanted the case for constrained schemas and enums.

The request payload matters too. Return structured, actionable detail rather than a bare error code, because the reviewer and the agent both need to know what failed and how to fix it the agent-facing error design pattern. And write the approval prompt the way you would write a job posting for a new hire: what the role is, when to act independently, and when to escalate, in a few sentences the reviewer can re-read quickly the job-posting framing.

A practical test: hand the request screen to someone outside the team and ask them to state, in one sentence, what will happen if they click approve. If they cannot, the screen is the problem, not the reviewer.

Tuning in practice, with numbers

Here is a worked example, using invented but plausible numbers so you can substitute your own.

Start with a tool that sends customer emails. In week one it produces 2,400 requests, the median time to decision is 6 seconds, 88% of approvals happen in under five seconds, and 4% of approvals edit a field. The agent's underlying quality is fine; the gate is not. Apply two changes. First, auto-approve the subset that is internal, templated and addressed to a known contact, which is 70% of volume. Second, keep the gate for anything with a custom body or a new recipient, and put the recipient's full address and the first 200 characters of the body in the request screen.

Re-measure after two weeks. Volume on the gate drops to about 720 requests, the median time to decision rises to 35 seconds, fast approvals drop to 12%, and the edit rate rises to 18%. The gate is now doing work. That is the signal you are looking for: fewer items, slower decisions, more edits.

Do the same exercise on every tool with a gate, and you will usually find a familiar distribution. A small number of tools generate most of the approval volume, and within those, a small number of action types generate most of the risk. The tuning work is to pull the low-risk volume out of the queue and raise the cost of the remaining gates until the median decision time reflects actual reading.

Two guardrails on the process. Never tune a gate from the approval rate alone; a tool whose approval rate falls from 97% to 92% after a change may simply have started surfacing harder cases. And keep a standing sample of auto-approved actions that a person audits weekly, so pulling volume out of the queue does not mean pulling it out of oversight.

Where the loop should run

The classifier is only as good as the feedback loop. Each reviewer decision, and specifically each rejection or field edit, is a labeled example of what should have happened, and those labels are the cheapest training data you will ever get. Feed them back into the risk classifier on a schedule, and re-derive each tool's gate placement from the fresh override data.

This also gives you a clean way to decide when to buy tooling versus build it. If you are running agents in code, the approval primitive is free in LangGraph and the OpenAI Agents SDK, and either is a reasonable default for a team that already writes agents as graphs or tool lists per the comparison in this approval gate review. A durable workflow engine earns its cost when approvals wait hours or days or span systems as orbiresearch and Temporal both describe. What you should not do is buy a managed review queue and assume it solves the problem; the queue is the easy part, and the metrics above are what make it mean anything.

If you would rather not build the approval service and the routing logic yourself, Quanta Inference gives you one OpenAI-compatible endpoint for the classifiers and models behind each gate, priced in prepaid US dollars, so the risk-scoring call and the generation call share one key (https://quantaintelligence.ai/inference). That leaves your team's time for the part that actually needs judgment: deciding, per action type, where the gate sits and what happens when nobody answers.

FAQ

FAQ

How do I measure approval fatigue without a lot of instrumentation?

Log three fields per request: the tool name, the timestamp the request was first rendered to a reviewer, and the timestamp of the decision. From those you can compute volume, median time to decision and the share of approvals under a five-second threshold. Add a "fields edited" counter if you can, and plant a small number of bad actions to measure detection.

Is a low rejection rate good or bad?

Neither on its own. A low rejection rate is equally consistent with a well-tuned classifier and with reviewers who have stopped reading. The discriminating signals are median time to decision, the fast-approval rate and the planted-error detection rate. If time to decision is very short, treat the low rejection rate as evidence of disengagement until proven otherwise, which is the same trap the reliability literature describes in the rubber stamp analysis.

Should every risky action go to a human?

No, and that is the core of the fatigue problem. Gate anything irreversible or with a wide blast radius, and auto-approve the reversible, low-impact remainder with audit logging and periodic sampling. Gating everything produces the 93% approval pattern rather than real oversight as documented in this analysis.

What should happen when an approval times out?

It depends on reversibility, not on a global rule. Internal and reversible actions can default to approve; money movement and external messages escalate to the next person and never auto-approve; actions whose value expires should cancel; actions built on possibly stale data should go back to the agent to re-check the facts per the defaults-by-reversibility pattern. Record the timeout outcome as a decision so it shows up in your metrics.

How do I keep an approval gate meaningful once I have several of them?

Keep the number of gated actions small, put the decision in front of the reviewer in plain terms (what happens, to what, at what scale, and what happens on timeout), and make each gate slow enough that reading is the path of least resistance. Review the gate placement and timeout defaults per action type on a schedule using override data, and re-check that the classifier score you are thresholding is calibrated.

Sources

Sources

  1. Human-in-the-Loop for AI Agents: Most Approval Gates ...
  2. The HITL Rubber Stamp Problem: Why Human-in-the-Loop ...
  3. Approval Timeouts for AI Agents: Defaults by Reversibility
  4. Approval Pattern | Temporal Documentation
  5. Using function tools with human in the loop approvals
  6. Human-in-the-loop patterns · Cloudflare Agents docs
  7. AI Agent Tool Design: What Works and What Doesn't
  8. Error Handling for AI Agents: A Different Standard Than Classic Apps
  9. Designing Tools for LLM Agents: Best Practices | AI/TLDR
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