Human Approvals, Escalation & Handoff
Designing the seam between agent and person — approval granularity, fatigue, asynchronous decisions, and handing over without losing the thread.
In this illustrative scenario, six weeks after Reference Agent’s approval gate went live, someone measured it. Priya, the billing lead, had been shown 1,847 approval requests. She had approved 1,831 of them. Median time from request to decision: four seconds.
A four-second median raises a question about review depth; it does not prove that every decision was thoughtless. Without inspecting decisions, the gate could merely be creating a record that suggested oversight had occurred — which is worse than no gate at all, because the organisation now believed a human was reviewing these actions and Priya’s name was on 1,831 of them.
The gate asked too many questions at the wrong granularity.
19.1The seam is the product#
For an enterprise agent, the boundary between what the agent decides and what a person decides is not a safety feature bolted on at the end. It is the thing that determines whether the system can be deployed at all.
Approval quality requires labelled decision outcomes as well as latency, refusal rate and throughput. Priya’s 16 refusals could reflect sound proposals or missed errors from fatigue. Without inspecting decisions, the system could not distinguish them.
Design Rule
Design for the reviewer’s attention as the scarce resource it is. Every approval request spends a fixed budget of human judgement, and that budget is much smaller than it looks — a workload-dependent number of considered decisions, measured from review sessions. Spend it on the decisions that matter and automate or batch the rest, or the reviewer will spend it for you by approving everything.
19.2A taxonomy of human involvement#
“Human in the loop” covers six patterns with different costs and guarantees:
Shipped systems expose recognisable versions of these. OpenHands offers confirmation policies named AlwaysConfirm, NeverConfirm and ConfirmRisky, with pluggable security analysers assigning risk levels.1 goose names four modes — completely autonomous, manual approval, smart approval that auto-approves low-risk actions, and chat only.2 Hermes documents three tiers where the default uses an auxiliary model to assess risk and escalate or deny, over a hardline blocklist that always applies.3 Cline offers per-tool auto-approve toggles up to a blanket mode its own documentation calls dangerous.4
Documentation checked on 9 November 2026 also makes the enforcement path explicit. OpenHands requires both an analyser and a confirmation policy. Direct conversation.execute_tool() calls bypass those checks. Review every execution path before treating confirmation settings as a complete boundary.1
These systems support risk-assessed approval: a classifier identifies actions that require review. Measure its calibration. A classifier that misses risky actions can silently bypass the intended human gate.
19.3Approve the plan, not the action#
Priya’s 1,847 requests came from roughly 180 tasks. Each task issued eight to twelve individually-approved actions: read this, write that, post this correction, file that ticket. Every one arrived as a separate decision, stripped of the context that made it sensible.
Approving the plan changes the arithmetic. One decision per task instead of ten. And — the part that matters more — the decision is a better one, because a plan is reviewable in a way an isolated action is not. “Reconcile January for acme: read 14 ledgers, correct any discrepancy under $500 automatically, escalate anything above, file a ticket per correction” is something Priya can evaluate against her judgement of what should happen. write_file("/work/acme/out/corr-9.json") is not.
The structure that follows:
- The agent produces a plan, with the read-only steps already executed so the plan is grounded in evidence rather than in a guess (Chapter 6).
- The plan declares its own bounds: which actions it will take autonomously, and the limits — under $500, these three services, this tenant only.
- A human approves the plan and its bounds, once.
- The agent executes autonomously inside the bounds.
- Anything outside the bounds escalates as an exception, with the plan as context.
This can replace repeated fragmented decisions with one contextual decision plus exceptions. It works only if the plan’s scope and cumulative limits are enforceable.
Plan approval must bind an immutable plan version, action types, resources, audience, expiry, per-action limits and cumulative count/value limits. Ten credits below $500 are still a possible $4,999.90 effect. Recheck current policy and changed evidence at execution; approval of bounded discovery does not authorise arbitrary later mutations. OWASP’s transaction-authorisation guidance reinforces server-side binding of what the user saw to what executes.5
19.4What an approval request must contain#
Most approval requests are unanswerable, which is why they get approved. A reviewer shown issue_credit(account="acme-9", amount=50.0) cannot evaluate it — they lack every fact that would make it right or wrong. So they approve, because refusing something that is probably fine is socially expensive and approving is free.
Six elements make a request answerable.
What will happen, in the reviewer’s language. “Credit $50 to Acme’s account,” not a function call. The reviewer is a billing lead, not a programmer.
Why the agent believes it should happen. The evidence chain, briefly: “the January settlement shows a $50 overcharge on line 214; the tenant’s ticket #4471 disputes exactly this amount.”
What is uncertain. The most-omitted element and the most decision-relevant. “The ledger and settlement disagree by $50.00; I could not determine which is authoritative.” Uncertainty is precisely what a human is being asked to resolve, and hiding it makes the request unanswerable while looking complete.
Reversibility and blast radius. “This posts immediately and cannot be reversed without a manual journal entry.” A reviewer who does not know whether something is undoable cannot calibrate their care.
The alternative. “If you decline, I will escalate to the billing lead with the discrepancy documented.” State the fallback so the reviewer can assess both choices.
A time bound. “I will wait 30 minutes, then escalate.” Define what happens if no answer arrives.
@dataclass
class ApprovalRequest:
summary: str # plain language, reviewer's vocabulary
action: ToolCall # the exact call, for the audit record
rationale: str # the evidence chain
uncertainty: str # what the agent could not determine
reversible: bool
blast_radius: str # "one account, $50" / "3 services, ~2 min"
if_declined: str # what happens on "no"
expires_at: datetime
on_timeout: Literal["escalate", "abandon"] # never "proceed"
plan_id: str | None = None # the plan this belongs to, for contextThe one field whose type carries a design rule: on_timeout cannot be proceed. A request that executes on silence is not an approval gate, it is a delay, and it converts an unavailable reviewer into an implicit authorisation.
19.5Fatigue and automation bias#
Two human factors affect approval quality:
Alert fatigue can occur when frequent low-value notifications train reviewers to dismiss requests without reading. Priya’s four-second median is a reason to test for this pattern, not proof of it. Reviewers may adapt to a low rate of genuine problems by reducing attention.
Automation bias is over-reliance on an automated recommendation. A confident recommendation from a usually reliable system can shift review toward checking only obvious errors. Calibrate review against labelled decisions as system reliability changes.
Five countermeasures:
Reduce volume, aggressively. This is a practical first intervention when the queue exceeds considered-review capacity. Plan approval, exception-only approval, and batching. Measure request complexity and reviewer capacity before setting a rate limit.
Vary the presentation for high-stakes requests. A different colour and layout, a required typed confirmation of the amount, a mandatory pause. The point is to break the motor pattern that makes approval automatic.
Surface uncertainty prominently, not the recommendation. A request that leads with “I could not determine which figure is authoritative” invites judgement; one that leads with “recommend crediting $50” invites assent.
Measure and publish the refusal rate. A reviewer whose refusal rate is 0.9% either has a very good agent or is not reviewing. Track it, per reviewer, and treat a rate near zero as a signal to investigate the gate rather than to congratulate the agent.
Test with labelled incorrect proposals in a simulation. Use consented reviewer exercises or a shadow queue with execution disabled. Measure harmful approvals and correct refusals without letting a test proposal create a real effect.
Failure Mode
An approval gate that is never exercised is worse than no gate, because it manufactures the appearance of oversight. The organisation believes a human reviewed the action, the audit trail names them, and accountability has been transferred to someone who spent four seconds on it. If you cannot demonstrate that reviewers identify consequential errors on labelled samples, you have built a liability rather than a control.
19.6Calculate the attention budget from measured inputs#
The approval budget in Figure 19.2 becomes useful only when you turn it into an admission calculation. Do this before enabling the gate, not after the first reviewer starts clicking. The calculation and its instrumentation are one operating move: measure how many requests arrive, how long a considered decision takes, how many reviewer-minutes are genuinely available, and how much capacity you will reserve for bursts and hard cases; then keep those inputs current.
Take an illustrative Reference Agent deployment handling 180 billing tasks per working day. Each task proposes ten actions on average. Priya can protect six hours for review, but you budget only 70% of those 360 minutes. The remaining 30% absorbs handovers, investigation, breaks, and uneven arrivals. That leaves 252 reviewer-minutes of designed capacity. An ordinary plan or exception decision takes 75 seconds when the request is answerable; a high-stakes decision takes four minutes and requires dual control.
Now compare three gate designs against the same workload.
| Gate design | Reviewer-decisions per day | Arithmetic | Reviewer-minutes | Load against 252 min |
|---|---|---|---|---|
| Approve every action | 1,800 | 180 tasks × 10 actions × 1.25 min | 2,250 | 893% |
| Approve every plan | 180 | 180 plans × 1.25 min | 225 | 89% |
| Bounds + exceptions + dual control | 45 reviewer-decisions (40 requests) | 25 plans × 1.25 + 10 exceptions × 1.25 + 5 high-stakes actions × 2 reviewers × 4 | 83.75 | 33% |
The first design demands nearly nine times the protected daily attention capacity. Its interface can still show a green queue because four-second clicks make the apparent service rate high; the human control has already failed. The second design technically fits, but an 89% planned load leaves almost no room for a burst or one difficult investigation. The third design spends most attention where the action is unusual or consequential. The 140 routine tasks not represented in its calculation execute inside previously approved bounds, with sampling and outcome monitoring rather than a fresh click.
Every number here is illustrative. The method is not. Measure the median and 90th-percentile decision time from sessions where reviewers actually explain a sample of their choices; raw click latency rewards shallow review. Count dual-control decisions twice because they consume two independent attention budgets. Use the arrival distribution by hour rather than dividing a daily total by eight: ten requests arriving together create a queue even when the daily average fits.
The operating metric is not just daily utilisation. Instrument the gate as a queue:
- Arrival rate by class — plans, exceptions, and high-stakes actions per hour. A rising exception rate usually means the approved bounds no longer match the work.
- Decision service time, separated from queue time. A falling service time alongside stable request complexity is a fatigue signal, not an efficiency gain.
- Oldest pending age and 90th-percentile queue age. The median can look healthy while a few specialised requests wait all day for the only qualified approver.
- Timeout and reassignment rate. A queue that fits only because requests expire or bounce between owners does not fit.
- Refusal and post-approval reversal rate by class. Do not set one universal refusal target. Investigate changes against the risk mix and the reasons reviewers recorded.
Measure the arithmetic by request class#
A single average decision time hides the queue you most need to see. Split requests by the qualification they require: routine plan review, billing exception, security exception, and dual-control financial action. For each class (i), calculate daily attention demand as arrivals_i × reviewers_i × considered_minutes_i. Then compare it with the protected minutes of reviewers who are actually qualified for that class, not with the organisation’s total headcount.
Suppose the tiered design’s five high-stakes actions all require Priya plus one of two treasury controllers. Its 40 reviewer-minutes are not interchangeable with the 43.75 minutes of ordinary review. If both controllers attend the same quarterly close, high-stakes capacity is zero even though the overall dashboard shows 168 unused minutes. The approval owner and the backup must therefore be part of the capacity model, and absence calendars must feed routing before a request enters the queue.
Peak windows matter for the same reason. Ten ordinary requests arriving at 09:00 require 12.5 reviewer-minutes. With one qualified reviewer they cannot all receive a considered answer inside a five-minute service objective, however low the daily utilisation looks. Your options are concrete: widen pre-approved bounds for that class, move the service objective, add a qualified reviewer for the peak, or reduce the upstream batch size. A priority label does not create capacity.
Finally, calculate the counterfactual load when the risk classifier degrades. If it normally sends 10 exceptions but a conservative update sends 60, the exception demand rises from 12.5 to 75 minutes. That still fits this example’s total budget, but it changes the peak queue and may starve plan reviews. Capacity tests should replay at least the expected day, a burst day, and a classifier-failure day before the policy ships.
Make that replay a release gate for policy changes. Feed one day of classified historical actions through the proposed bounds without executing them, count which requests would reach each reviewer pool, and apply measured service-time distributions. Reject a policy that exceeds protected capacity in the expected case or has no declared degradation path in the burst case. The choice is then explicit: narrow the agent’s work, widen a bound with evidence, fund more qualified attention, or accept a longer decision window. Shipping an overloaded gate and hoping reviewers cope is not a fifth option.
Run the capacity calculation again when volume, bounds, reviewer staffing, or the risk classifier changes. A gate that fitted last quarter can become theatre without a single line of approval code changing.
19.7Asynchronous approval#
An agent that blocks on a human is an agent whose throughput is human availability. For interactive work that is acceptable; for a general work agent handling background tasks it is not.
Park and resume. The run reaches an approval point, persists its state, and stops. The approval arrives minutes or hours later and the run resumes. This is exactly what Chapter 5’s durable state was for — the approval request and its decision are events, the run is resumable, and no process sits idle holding a socket. The BLOCKED step state from Chapter 6 is the plan-level counterpart.
Work around it. Better where possible: mark the step BLOCKED and continue with other ready steps. The agent keeps making progress on the branches that do not need a decision. This requires the plan to be a graph rather than a list, which Chapter 6 argued for.
Pre-authorise with bounds. Best of all: the approval was given in advance, as bounds on a plan, so no runtime decision is needed for the common case. Only exceptions block.
Two operational requirements. Every pending approval needs an owner and an escalation path — a request sitting unanswered because its reviewer is on leave is a stalled task, and it should escalate on a timer rather than waiting indefinitely. And the approval must be revocable before execution: a reviewer who approves and then reconsiders needs a way to withdraw it before the action fires, which means the window between decision and execution must be visible to them.
19.8Make the decision-to-execution race explicit#
Revocation sounds like an interface feature until the first approval and withdrawal cross in flight. Then it becomes a state-machine problem.
Consider one Reference Agent billing run. At 14:02:11 the agent asks to issue a £4,800 credit. At 14:05:03 Priya approves after checking the settlement. The worker that will issue the credit is busy, so the action waits. At 14:05:19 a fresh ledger import shows that the apparent overcharge was a duplicate feed. Priya revokes the approval at 14:05:26. At 14:05:28 the worker executes anyway because it copied granted=True into its local job payload twenty-five seconds earlier.
The interface tells Priya the approval was revoked. The money still moves. Everyone initially blames message delay, but faster messaging would only make the race narrower. The actual defect is that the system treated an approval decision as a timeless boolean rather than as a revocable execution entitlement.
Use explicit states and one atomic claim:
REQUESTED ──grant──▶ GRANTED ──claim──▶ CLAIMED ──verified result──▶ EXECUTED
│ │ │
├──decline──▶ DECLINED └──ambiguous timeout──▶ UNKNOWN
└──expire───▶ EXPIRED
├──revoke──▶ REVOKED
└──expire──▶ EXPIREDDefinitive pre-effect rejection can become FAILED; a timeout after dispatch remains UNKNOWN until reconciliation proves its outcome.
The executor must compare-and-set GRANTED to CLAIMED in the authoritative store immediately before the side effect. A revocation competes for the same state transition. If revocation wins, the claim fails and nothing executes. If the claim wins, the interface must stop promising that revocation is still possible and show that execution has begun. There is no honest state in which the system has accepted a revocation while an old copy of the grant remains usable.
Bind the claim to more than a request id. It needs the exact action signature, tenant, actor, approved bounds, policy version, expiry, and rendered-request hash. That last value proves the executor is claiming permission for what the reviewer actually saw. A changed amount, audience, tenant, tool implementation or plan outside the approved bounds requires a new decision. Also bind the operation id and any resource-version precondition, so the approved object cannot change unnoticed before execution. The tool call still needs an idempotency key (Chapter 5), because a valid claim prevents unauthorised execution but does not prevent a retry from issuing the credit twice.
Three edge cases deserve tests rather than prose in a runbook:
| Race | Unsafe implementation | Required result |
|---|---|---|
| Revoke and claim arrive together | Both read granted=True and succeed |
Exactly one atomic transition wins |
| Grant expires while the worker is offline | Job payload carries an old boolean | Claim rejects the expired grant |
| Worker crashes after the side effect | Replacement sees CLAIMED and repeats it |
Receiver-side idempotency returns the first result, or reconciliation keeps the call uncertain |
Dual control adds another state dimension. Two green ticks are not dual control if they can come from the same authenticated principal, from a service account acting for both people, or from one person approving twice after a role change. Store grants as distinct principal-bound records and evaluate separation-of-duties rules when claiming: for example, one billing approver and one treasury approver, neither of whom submitted the request. The entitlement becomes claimable only when the required independent grants coexist and none has expired or been revoked.
Do not let the first approval wait forever for the second. Give the collection its own expiry and notify the first reviewer when it lapses. If the underlying action changes while one grant is pending, invalidate the whole collection; you cannot carry the first person’s approval onto a new amount and ask only the second person to review the change. This is why the rendered-request hash belongs in every grant rather than only on the final claim.
This mechanism narrows the meaning of approval precisely. A person authorises one immutable action for one tenant within one time window. They do not authorise whatever a worker later interprets from a mutable plan. Production systems also need authenticated decision channels, durable queues, qualified-owner routing, and a reconciliation job for claims stranded by worker failure. Those are deployment extensions; the reference Reference Agent loop below deliberately implements a smaller synchronous seam.
19.9Escalation and handoff#
Escalation is not approval. Approval asks may I do this; escalation says I cannot do this, you take it. Different design, and the distinction matters because conflating them produces escalations that arrive as questions nobody can answer.
Four triggers worth encoding explicitly rather than leaving to the model’s judgement:
Competence. The task needs something the agent cannot do — a judgement call, a relationship, an authority it does not hold.
Confidence. The agent could act but its own evidence is weak. This is the one that requires the operating contract from Chapter 8 to have actually landed: stop and state what you know when you cannot make progress.
Authority. The action exceeds the agent’s autonomy level and no approver is available within the window.
Repeated failure. Three attempts, a loop detected, a budget exhausted. Chapter 4’s terminal outcomes map onto escalation directly, and ESCALATED is a distinct outcome precisely because the task remains open.
A handoff must transfer context, not a task id. The receiving human needs, in one place:
- What was asked, verbatim
- What the agent established, with provenance
- What it tried and what happened
- Specifically why it stopped
- What it recommends, if anything, held separately from the facts
- Links to the artefacts and the full trace
This is the handoff record from Chapter 9, addressed to a person rather than to a fresh context. The overlap is not a coincidence: both are the problem of transferring understanding across a discontinuity, and a system that has built one has most of the other.
Measure handoff quality through follow-up questions: how often must the receiving person ask for more information before proceeding? Repeated questions identify missing fields in the handoff record.
11Devin's Playbook format is instructive on this point — it includes a named Forbidden Actions section alongside procedure and specifications. Encoding what must not be done, explicitly, is easier and more durable than specifying everything that should be, and it gives an escalation a clear boundary: a forbidden action is denied; an action outside that set still needs affirmative authority and scope.19.10Auditing approvals#
For a regulated deployment, the approval trail is a large part of the compliance story, and Chapter 5’s event log is where it lives.
The request/decision pair is what makes this auditable rather than merely logged:
log.append(EventType.APPROVAL_REQUESTED, tool=call.name,
arguments=call.arguments, reason=verdict.reason)
answer = self.approver(call, verdict)
log.append(EventType.APPROVAL_DECIDED, tool=call.name,
granted=answer is True,
note="" if answer is True else str(answer))Six properties a defensible trail needs. Who — a named individual, not a service account or a role. What — the exact action with its arguments. When, for both request and decision, so latency is derivable. What they were shown — the rendered request, not a reconstruction, because “the reviewer approved X” is only meaningful if you can show what X looked like to them. The outcome — including whether the action then succeeded. The context — the run id and plan id that connect this decision to everything around it.
The derived metrics worth putting on a dashboard: approval latency distribution, refusal rate per reviewer, timeout rate, and the ratio of approvals to actions. That last one is the single best early-warning signal for the Priya problem — a ratio approaching one means you are gating everything, which means you will soon be gating nothing.
19.11Read the reference approval seam honestly#
The reference implementation makes the control boundary small enough to inspect. PermissionEngine.evaluate() produces ALLOW, ASK, or DENY; the loop tightens an apparent allow when a tool mutates irreversibly; and only then can the configured approver run. The decisive block in code/reagent/loop.py is:
if verdict.decision is Decision.ASK:
self._emit(EventType.APPROVAL_REQUESTED, tool=call.name,
arguments=call.arguments, reason=verdict.reason)
answer = self.approver(call, verdict)
granted = answer is True
self._emit(EventType.APPROVAL_DECIDED, tool=call.name,
granted=granted,
note="" if granted else (
answer if isinstance(answer, str) else ""))
if not granted:
note = answer if isinstance(answer, str) else "no reason given"
self._observe(ToolResult.error(
call, f"A human declined this action: {note}"), executed=False)
continue
self.policy_ctx.session_approvals.add(call.signature())The request is logged before the callback. If the process dies while the person is deciding, the trace still records that an approval was outstanding. Logging only the answer produces decisions with no reconstructable question.
Only the singleton True grants. "yes", a non-empty note, and any truthy object do not silently become authority. A string is a refusal reason. This is a small type boundary that prevents an integration’s convenient return value from becoming an accidental grant.
A refusal becomes an observation. The model receives the human’s reason and may choose another permitted route. The denial is not an exception and does not crash the run. That behaviour also explains why the reference loop can still end COMPLETED after an action was declined: completion describes the run’s terminal path, while the approval event records the control decision.
The grant is scoped to call.signature(). An identical call in the same session need not ask twice, but changing an argument changes the signature. Approval for a £50 credit does not approve £500. The session grant also sits behind absolute path and network checks in PermissionEngine; a human answer cannot widen an egress allowlist or turn a denied path into an allowed one.
The approver defaults to deny_all. No configured human channel means no authority. approve_all exists for tests and autonomy level 3 and says so in its docstring. Treating a missing callback as approval would make a deployment failure indistinguishable from consent.
The boundaries matter just as much as the guarantees. This is a synchronous, per-call reference seam: the callback returns during the worker’s execution; session_approvals lives in process memory; and the audit event records the tool, arguments, reason, decision, and refusal note. It does not implement a durable pending-approval queue, plan-level bounds, expiry, revocation, ownership, dual control, rendered-request capture, or the atomic claim described above. The RunOutcome.ESCALATED enum names an architectural outcome, but this approval callback does not itself construct and route a human handoff.
Trace one £50 charge through that scope. The model proposes charge_card(amount=50, account="acme-9"). Policy returns ASK; the irreversible-tool refinement can tighten an allow but can never loosen a deny. The loop emits the request, and deny_all refuses unless an approver has been configured. If Priya grants, the loop records the decision and stores the exact call signature for this session. A repeated identical proposal passes through session.grant; a proposal for £51 has a different signature and asks again. Before execution, the side-effect path computes an idempotency key from the run id and call signature. Once a successful completion is durable, a model repetition or process replay returns the stored result rather than charging again. As Chapter 5 establishes, the local key does not close a crash after the remote effect but before the completion append; a production adapter needs receiver-side idempotency or authoritative reconciliation for that uncertain window.
Notice what this trace does not imply. The in-memory session grant is not a reusable organisational permission, and the idempotency key is not an approval. One answers may this exact call proceed in this live session; the other answers has this logical side effect already completed in this run. Merging those concepts creates a dangerous shortcut where previous execution is mistaken for present authority.
That is deliberate scope, not a hidden production feature. Preserve the ordering and fail-closed defaults when you replace the callback with a durable service. Extend the event schema and state machine around the seam; do not move authority into the model or let a notification system call the tool directly.
19.12Earning autonomy with evidence#
Use measured approval outcomes to decide when a capability can gain more autonomy (Chapter 15).
Promote a bounded capability only when independently reviewed outcome quality and harmful-error measurements support it, over a representative sample with no unresolved serious incidents. A high approval rate alone may measure automation bias rather than correctness. The specifics depend on your risk appetite, but the shape is fixed: a rate, a sample size, and an incident condition. “It has been fine for a while” is not a criterion; it is the absence of one.
Promotion is per capability and per bound, not global. “Autonomous for credits under $100” is a promotion. “Autonomous for credits” is a different and much larger one. Widening a bound is a promotion decision in its own right.
Make demotion automatic and cheap. Defined incidents or refusal-rate spikes should reduce autonomy promptly, without waiting for a meeting.
The approval budget
Treat human attention as a metered resource with an explicit budget, and design against the budget rather than against the actions.
- Set the budget. How many genuinely considered decisions can this reviewer make per day? Measure by request class and available review time. Write the number down.
- Count your demand. Approvals per day at current volume. If demand exceeds budget, the gate is already failing regardless of how it looks.
- Close the gap by reducing review frequency where justified. Per-action → plan approval → exception-only → notify-with-undo → post-hoc sampling. Each step down is a real reduction in guarantee; make it deliberately and record which risk you accepted.
- Spend the freed budget on the top tier. The credits over $500 get dual control and a mandatory pause, funded by no longer asking about the ones under $50.
- Instrument the gate itself. Refusal rate, decision latency, approval-to-action ratio, timeout rate. Review these monthly as a measure of the gate’s health, not the agent’s.
Definition
Approval fatigue — the degradation of review quality under high request volume, where a low base rate of genuine problems trains dismissal without reading.
Automation bias — over-reliance on an automated recommendation. Higher apparent reliability can encourage it. Measure reviewer decisions to test whether it occurs.
Bounds — the declared limits within which a plan may execute autonomously, approved once alongside the plan.
Part V is complete. The agent handles every modality it will meet and has a designed relationship with the humans around it. Part VI turns to the adversarial and the accidental: what stops it when something goes wrong, and what happens when someone is trying to make it go wrong.
Key Takeaways
- Measure approval quality with labelled correct and harmful proposals. Refusal rate, review latency and throughput help diagnose the gate but do not establish decision quality.
- Six patterns differ in what they cost the reviewer. The design work is moving each decision to the cheapest pattern that still gives the assurance you need, including per-action or dual approval where the risk requires it.
- Consider plan approval when one contextual decision can replace fragmented requests. Enforce its version, scope, cumulative limits and expiry. Retain per-action approval where risk requires it.
- An answerable request needs six things: plain-language summary, evidence, what is uncertain, reversibility and blast radius, the consequence of declining, and a time bound whose timeout never means proceed.
- Automation bias can weaken review as the agent improves. Reduce overload, surface uncertainty, inspect decision quality and test labelled errors in a simulation with execution disabled.
- Asynchronous approval needs park-and-resume from durable state, the ability to work other branches while blocked, and pre-authorisation via bounds. Every pending request needs an owner and an escalation timer.
- Escalation is not approval, and a handoff must transfer context: the ask verbatim, established facts with provenance, what was tried, why it stopped, and recommendations held separately from facts. Measure how often the receiving human has to ask a follow-up.
- Promote autonomy on a rate over a sample with an incident condition, per capability and per bound — and make demotion automatic and cheap, or it will not happen when it is needed.
- Size the gate as a queue before launch: compare classified request minutes with protected reviewer capacity, reserve slack for bursts, and track queue age and service time separately.
- An approval is a revocable execution entitlement, not a boolean. Claim it atomically against the exact action, tenant, policy version, expiry, and rendered request immediately before the side effect.
- The reference Reference Agent loop is deliberately synchronous and per-call. It preserves fail-closed ordering and an auditable request/decision pair; durable queues, plan bounds, revocation, ownership, and dual control are production extensions.
Hands-on
Measure your gate before you trust it
1. Compute your reviewer’s refusal rate and median decision latency. Review a labelled sample to determine whether fast decisions are sound or whether overload has reduced attention.
2. Count approvals per task. Compare the demand with reviewer capacity; consider plan approval — and check whether your plan even exists as inspectable state, because that is the prerequisite.
3. Take your ten most recent approval requests and ask an actual reviewer, cold, whether they could evaluate each one. Then add the missing fields, especially the uncertainty and the consequence of declining.
4. Make every request’s on_timeout explicit and confirm none of them means proceed. Then find out what currently happens when a reviewer is on leave.
5. Put the gate’s own metrics on a dashboard — refusal rate, latency, approval-to-action ratio, timeout rate — and review them monthly as a measure of the gate rather than of the agent.
6. Build a one-day attention budget from measured arrival classes and considered decision times. Reserve at least one explicit margin for bursts, then identify which requests must move to bounds, exceptions, sampling, or dual control for the budget to close.
7. Write concurrency tests for grant versus revoke, claim versus expiry, and crash after execution. Verify that one authoritative state transition wins and that the retry returns the idempotent result rather than repeating the side effect.
-
Security — OpenHands SDK. Accessed 9 November 2026. https://docs.openhands.dev/sdk/guides/security ↩↩
-
goose permissions — goose. Agentic AI Foundation. https://goose-docs.ai/docs/guides/managing-tools/goose-permissions ↩
-
Security — Hermes Agent user guide. Nous Research. Accessed 9 November 2026. https://hermes-agent.nousresearch.com/docs/user-guide/security ↩
-
Auto approve — Cline. Accessed 9 November 2026. https://docs.cline.bot/features/auto-approve ↩
-
OWASP. Transaction Authorization Cheat Sheet. Accessed 8 September 2026. https://cheatsheetseries.owasp.org/cheatsheets/Transaction_Authorization_Cheat_Sheet.html ↩