aiArch

Pattern · last reviewed 2026-07-13

Human-in-the-loop approval boundaries: which agent actions need a human sign-off

What this pattern is

Human-in-the-loop approval boundaries are the rule that decides, per action, whether an agent may act alone or must wait for a person. You classify every tool the agent can call by two axes — how reversible the action is and how large its blast radius — and assign each a boundary: act automatically, act and notify, require approval before acting, or forbid entirely. The point is not to review everything; it is to spend human attention only where an action is both hard to undo and wide-reaching, and to design the queue so that attention stays sharp rather than degrading into rubber-stamping.

Context and problem

An agent that can only read is safe and nearly useless. The value shows up the moment it can act — issue the refund, merge the PR, send the email, delete the stale bucket, apply the Terraform plan. Each new tool you mount widens what the agent can accomplish and, in exactly equal measure, what it can accomplish wrongly off a hallucinated argument, a prompt-injected instruction, or an ambiguous request it resolved the wrong way.

The naive responses both fail. Gate everything behind a human and you have rebuilt a form with extra steps — the agent's autonomy is gone and reviewers learn to click "approve" without reading, because the ninety-ninth low-stakes prompt this week has trained them that approval is a formality. Gate nothing and you are one bad tool-call away from a refund to the wrong account or a `DROP` against production. This is precisely OWASP's LLM06:2025 Excessive Agency: damaging actions performed in response to unexpected, ambiguous, or manipulated model output, whatever caused the model to misfire. The pattern exists to place the human at the few points where their judgment actually changes the outcome, and nowhere else.

Forces

  • Autonomy vs. safety. Every approval gate is latency and a human in the critical path; every ungated action is trust you cannot claw back once it executes.
  • Reversibility vs. blast radius. These are independent. A reversible action with enormous reach (paging every on-call engineer) and an irreversible action with tiny reach (deleting one throwaway file) are different risks and want different gates.
  • Coverage vs. fatigue. The more you route to humans, the less each review means. Attention is finite; a queue that cries wolf gets rubber-stamped, which is worse than no queue because it looks like control.
  • Speed of the loop vs. auditability. Synchronous approval blocks the agent mid-task; asynchronous approval keeps it moving but demands durable state, a record of who decided what, and a way to resume.

The pattern

Score each action the agent can take on two axes and route it to one of four boundaries. Reversibility asks: if this is wrong, can we undo it, and how cheaply? (A draft is free to undo; a sent email or a processed payment is not.) Blast radius asks: how many users, records, or systems does one call touch? The two multiply into a risk tier, and the tier picks the boundary:

  • Auto — reversible and narrow. The agent acts with no human involvement. Reads, drafts, idempotent writes to scratch state. Most calls should land here or the pattern has failed its own cost test.
  • Notify — reversible but wide, or narrow but worth a trail. The agent acts, then posts what it did to a channel a human watches. No one blocks; someone can intervene or roll back after the fact.
  • Approve — irreversible or wide enough that undo is expensive. The action is proposed, not taken; it waits in a queue until a person confirms or rejects. This is the narrow band the whole pattern is built to protect.
  • Forbid — no legitimate task needs this action from this agent. It is not a tool the agent has. Removing the capability is stronger than gating it, because a gate is code that can be bypassed and a missing tool cannot be called at all (least privilege — OWASP's first mitigation for LLM06).

The classification is a design-time decision baked into the tool layer, not something the model decides at runtime — the model is exactly the component you do not trust to grade its own actions. Confirmation and audit logging live outside the model, in the tool wrapper or the orchestration layer, so a jailbroken prompt cannot talk its way past the gate.

An agent action is scored by reversibility and blast radius, then routed to one of four approval boundaries: auto, notify, approve, or forbid. Agent proposes an action Classifier reversibility × blast radius Auto act, no human Notify act, then tell a human Approve queue; wait for sign-off Forbid tool not mounted every branch writes an audit record

Reference implementation notes

The classifier is yours to design; the confirmation and audit mechanics differ by platform. Only the parts that differ are worth writing down.

Anthropic

Claude Code ships this pattern as a first-class permission system, which makes it a usable worked example even if you are not building on it. Its permission modes set the baseline — default asks before each edit, shell command, or network call; acceptEdits auto-approves file writes but still gates Bash; plan forbids all execution; bypassPermissions approves everything (isolated sandboxes only). On top of the mode you layer allow / ask / deny rules per tool — which is exactly the auto/approve/forbid classification, expressed as configuration rather than model judgment. The property worth stealing is the layering: explicit deny rules are the hard forbid boundary, while the mode only sets the default posture — and bypassPermissions approves everything the deny rules don't catch, which is why it belongs only in isolated, throwaway sandboxes. When you build your own agent on the Claude Agent SDK, a canUseTool callback returns your boundary decision for calls the rules haven't already resolved — an auto-approved call never reaches it, so canUseTool is your ask-tier classifier, not your forbid boundary; mandatory enforcement stays in deny rules (or a PreToolUse hook that runs on every call).

# settings.json — the classification as config
{
  "permissions": {
    "allow": ["Read", "Grep", "Bash(npm test:*)"],
    "ask":   ["Bash(git push:*)", "WebFetch"],
    "deny":  ["Bash(rm -rf:*)", "Read(./.env)"]
  }
}

AWS

Amazon Bedrock Agents expose two distinct mechanisms, and the distinction matters. User confirmation is the approve boundary built in: enable it on an action-group function and the agent, instead of invoking, returns a confirmation question for the end user to confirm or deny before the function runs — enabled in the console, CLI, or SDK. Return of control is the more general escape hatch: rather than executing the action itself, the agent hands the collected parameters back to your application so you run whatever validation, queueing, or human review you want before executing. Use user confirmation for a simple inline yes/no; use return of control when the approval is asynchronous or needs to route through your own queue and identity system. Both keep the irreversible act outside the agent's autonomous loop.

Cloudflare

The approve boundary needs somewhere durable to hold a proposed action while a human is away — a synchronous prompt does not survive a page reload or a five-hour delay. On Cloudflare, a Durable Object per agent (or per pending decision) is the natural home: it holds the proposed action, the state to resume from, and the decision, with strong single-object consistency so two reviewers cannot both act on one item. Queues carry the notify-boundary side-effects — post-hoc alerts, audit fan-out — without blocking the agent, and Workflows give you durable, resumable execution for the approve-then-continue case. The agent parks the action, the human approves through a separate Worker route, and the object resumes the task from where it stopped.

Trade-offs

  • The classification is judgment you own forever. Reversibility and blast radius are not machine-derivable in general; a human sorts each tool into a tier at design time, and the mapping needs revisiting whenever tools or their downstream permissions change.
  • Approve gates add latency and a human dependency. An agent that stalls waiting for sign-off is only as fast as its slowest reviewer. If most of your value is in that band, the agent may not be the right tool at all.
  • Asynchronous approval is real infrastructure. Durable state, resumption, idempotency (the human approves once; the action must execute exactly once), and identity on the approver. This is more than a boolean.
  • Fatigue is a slow leak, not a crash. A queue that routes too much trains reviewers to approve reflexively, and you will not see it in any metric until an obviously-wrong action sails through. Guarding against it is ongoing tuning, not a one-time setting.

When not to use this

Skip approval boundaries entirely when the agent has no irreversible or wide-reaching action in its whole tool set — a read-only research or summarization agent needs auditing at most, not gating, and adding approval prompts there is pure fatigue-generation that devalues the gates that matter. Skip them, too, when the right answer is forbid rather than approve: if an action is dangerous enough that you would review every single invocation, and the agent has no legitimate autonomous use for it, remove the tool instead of gating it — a capability the agent does not have cannot be jailbroken into using, and an unremovable human bottleneck disguised as automation is worse than an honest manual process. And do not reach for a human queue where a deterministic guardrail is stronger: a hard spending cap, a row-count limit on a delete, or an allow-listed set of recipients enforces the boundary without a person and without the injection surface that a natural-language confirmation step reintroduces. Human-in-the-loop is for genuine judgment calls, not for rules a validator can encode.

As-built evidence

aiArch runs a modest version of this pattern in production. Agent-drafted editorial content does not publish itself: a draft lands in a review queue (the review_queue table), and the owner approves or rejects before it goes live. Approval is the trigger for the irreversible-ish side effects — publish-on-approve purges the page's edge cache and re-indexes AI Search (src/lib/publish.ts), both best-effort and gated on credentials so a missing token degrades to a reported skip rather than a broken approval. Outbound lifecycle email is gated the same way: every send needs owner sign-off, never an autonomous dispatch. It is a single-reviewer, owner-only queue — narrow by design — but it is the approve boundary running against real actions (cache, search index, subscriber inboxes) rather than a diagram.

Changelog

  • 2026-07-13 — Initial publication. Verified against OWASP LLM06:2025, Claude Code permission modes, and Amazon Bedrock user confirmation / return of control.
Sources and provenance

OWASP numbering and Bedrock feature names are the facts here most likely to drift — re-verify LLM06's designation and the Bedrock confirmation APIs before citing anything newer. Corrections: hello@aiarch.dev.

Learn to threat-model an agent, not just wire one.

aiArch teaches agent safety, least-privilege tool design, and human-in-the-loop gating by building — the same discipline this platform runs on its own content pipeline. The build is the curriculum.

Free sample — no signup · every claim cited · full curriculum is waitlist-only