PromptABCD
FeaturesLearnHow it worksUse casesFAQGuideBlogContext Blocks
Sign inGet started free
Sign inSign up
PromptABCD

A calm home for your best AI prompts. Save them once, find them in seconds, reuse them forever.

Product

  • Features
  • Chrome Extension
  • Free Courses
  • How it works
  • Use cases
  • Blog
  • Context Blocks
  • Export Anywhere
  • FAQ

Resources

  • User guide
  • Learn prompting
  • Sign in
  • Get started free

© 2026 PromptABCD. All rights reserved.

Privacy PolicyTerms and Conditions
Home/Blog/AI Harness/Approval Gates: Requiring Human Sign-Off in the Harness
AI Harness

Approval Gates: Requiring Human Sign-Off in the Harness

A blocking approval step froze workers for hours; a blanket one became a rubber stamp. Here's how one team built an agent approval gate that's async, durable, and specific.

September 8, 2026·8 min read
ShareShare
⚡Featured Prompt— copy and use right now
def run_with_approval(action, context):
    if needs_approval(action):
        print(f"APPROVE? {action}")
        answer = input()          # blocks the entire process, indefinitely
        if answer != "yes":
            raise Denied(action)
    return execute(action)

In a 2024 survey of teams running agents in production, the ones that had shipped a human-in-the-loop step reported roughly 60% fewer serious incidents than the ones running fully autonomous — and the single most common form that step took was an agent approval gate: a hard pause before a risky action that waits for a human to say yes. What's surprising isn't that approval gates help. It's how many teams build them wrong on the first try, in a way that either blocks a worker for an hour or trains their reviewers to approve on autopilot. This is the story of one team that hit both failure modes and how they fixed them.

An approval gate is the mechanism behind the "ask a human" decision. A permission system decides that something needs approval; the gate is how you actually pause, surface the right context, wait, and resume. Getting the decision right and the mechanism wrong still leaves you exposed.

The Problem This Team Faced

The team ran an agent that managed cloud infrastructure — scaling services, rotating credentials, occasionally tearing down environments. Some of those actions were irreversible, so they knew from day one they needed a human to approve the dangerous ones. The question was how.

Their first version blocked. When the agent hit a risky action, the harness called out to a human and simply waited — the whole run frozen, a worker held open, the code parked on a blocking call until someone in another timezone woke up and clicked approve. A run that should have taken thirty seconds sometimes took six hours, and during those six hours a worker process sat doing nothing but holding memory.

⚠️ Common mistake: Implementing an agent approval gate as a synchronous block. Human approval happens on human time — minutes to hours — and holding a live process open for that entire window doesn't scale past a handful of concurrent runs. The first sign of trouble is your worker pool exhausting itself with runs that are all just waiting.

The Wrong Approach

Here's the blocking version, simplified. It works in a demo with one reviewer sitting next to you and falls apart the moment approvals take real time.

hljs python
[object Object], ,[object Object],(,[object Object],):
    ,[object Object], needs_approval(action):
        ,[object Object],(,[object Object],)
        answer = ,[object Object],()          ,[object Object],
        ,[object Object], answer != ,[object Object],:
            ,[object Object], Denied(action)
    ,[object Object], execute(action)

What this does: Pauses on a blocking

input()
until a human types an answer, then either executes or raises. Every second of that wait, the process is pinned. Ten concurrent runs waiting on approval means ten frozen workers. The logic is fine; the architecture guarantees it won't survive contact with real reviewer latency.

Their second mistake compounded the first. To avoid the blocking problem, they made approval a single blanket "approve this run" button that a reviewer clicked once to unblock everything the agent wanted to do for the rest of the session. Reviewers, facing dozens of these a day, learned to click it without reading. The gate existed, but it had become a rubber stamp — which is arguably worse than no gate, because it manufactures a false sense of oversight.

The Correct Approach

The fix was to make the gate asynchronous and durable. When the agent hits a risky action, the harness doesn't wait — it persists the run's state, records a pending approval, notifies a human out of band, and releases the worker. When the human approves later, a separate path loads the state and resumes the run from exactly where it paused.

hljs python
[object Object], ,[object Object],(,[object Object],):
    action = state.pending_action
    ,[object Object], needs_approval(action):
        approval_id = create_approval_request(
            run_id=state.run_id,
            action=action,
            preview=render_preview(action),   ,[object Object],
        )
        save_state(state.run_id, state)       ,[object Object],
        notify_reviewers(approval_id)
        ,[object Object], Paused(approval_id)             ,[object Object],
    ,[object Object], execute(action)

What this does: Writes a pending approval with a human-readable preview of the action, checkpoints the entire run to durable storage, notifies reviewers, and returns

Paused
— which lets the worker pick up other work. Nothing blocks. The run lives as a row in a database, not as a frozen process, so it can wait hours or days at zero cost.

The resume path is a separate entry point triggered by the approval, not a thread that was waiting.

hljs python
[object Object], ,[object Object],(,[object Object],):
    req = load_approval(approval_id)
    state = load_state(req.run_id)
    record_decision(approval_id, decision, reviewer)   ,[object Object],
    ,[object Object], decision == ,[object Object],:
        execute(req.action)
        resume_run(state)                              ,[object Object],
    ,[object Object],:
        fail_run(state, reason=,[object Object],)

What this does: When a decision arrives, it loads the checkpointed state, records who decided and how, then either executes the approved action and resumes the run or fails it cleanly. Because the state was persisted, the run picks up right where it stopped — the agent never knew it was asleep for three hours.

The team also fixed the rubber-stamp problem by making each approval specific and previewed. Instead of "approve this run," a reviewer sees exactly which action, against which resource, with what effect — "tear down

staging-eu
(14 running instances)" — and approves that one action. The specificity is what keeps the reviewer's attention engaged.

⚡ Pro tip: Always show a preview of the concrete effect, not just the action name. "Delete environment" invites a reflexive yes. "Delete environment

prod-backup
— this removes 3 volumes and cannot be undone" makes the reviewer actually look. The preview is the difference between oversight and theater, and it costs you one render function.

Results and What Changed

Two numbers moved. Worker utilization stopped collapsing under waiting runs, because runs waiting on approval no longer occupied workers at all — the team went from a hard ceiling of a few dozen concurrent runs to effectively unbounded, since a paused run is just a database row. And the reviewers started catching things again: within the first week of switching to specific, previewed approvals, a reviewer rejected an action that would have deleted a production database, something the blanket rubber-stamp version would have waved straight through.

⚡ Pro tip: Put a timeout on pending approvals with a safe default — usually deny. An approval that sits unanswered for 24 hours shouldn't hang forever; it should expire and fail the run so the agent doesn't leave an irreversible action perpetually one click away from firing. A stale approval is a loaded gun on the table.

How to Apply This to Your Situation

Start by separating the two concerns the team initially conflated. Deciding whether an action needs approval is your permission logic. Deciding how to pause and resume is your gate mechanism. Build the gate to be asynchronous from the start, even if your first reviewer is sitting right there — synchronous approval is a trap you'll have to unwind later, and unwinding it means rebuilding your state persistence under pressure.

For the mechanism, you need three things: a way to durably checkpoint a run's full state, a store of pending approval requests, and a resume entry point that fires on decision. If your harness already persists state for crash recovery — and it should — you're most of the way there. The approval gate is largely the same machinery pointed at a different trigger.

For the human side, make approvals specific, previewed, and attributable. Every decision should record who made it and when, because that record is both your audit trail and your defense when someone asks "who approved that?" Specificity keeps reviewers engaged; attribution keeps the process accountable.

Three teams I've watched apply this landed in different places, all valid. A fintech routes high-value transfers through a Slack approval with a preview of amount and recipient, expiring in an hour. A devops team gates production changes behind a web dashboard where the on-call approves with a one-line reason. A healthcare startup requires two independent approvals for any action touching patient records — the same async mechanism, just waiting on two decisions instead of one.

Designing Around Approval Fatigue

Even a well-built agent approval gate fails if reviewers stop reading, so the harder design problem isn't the mechanism — it's keeping human attention genuinely engaged over hundreds of requests. The team learned that volume is the enemy. A reviewer who sees three approvals a day reads each one; a reviewer who sees eighty stops reading by lunch. So they worked to lower the volume of gated actions without lowering the safety.

The first move was to raise the bar for what triggers a gate at all. Many actions the model wanted approved were reversible — creating a draft, tagging a resource — and reversible actions don't need a human; they need a good undo. Reserving the gate for irreversible or high-cost actions cut approval volume by more than half and made every remaining approval matter.

The second was to batch related approvals into one reviewable unit with a combined preview, so a reviewer approves "this coherent plan" once with full context rather than clicking yes to fifteen fragments they can't connect. Fewer, richer decisions beat many thin ones.

⚡ Pro tip: Track your approval rate — the fraction of requests reviewers approve. If it's above about 95%, your gate is probably firing on things that don't need it, and that noise is training reviewers to auto-approve the ones that do. A gate that almost always says yes isn't catching much; tighten what triggers it until the approvals left are ones a human genuinely needs to weigh.

Next Steps

The natural companion to an approval gate is a complete audit log, because the moment you have humans approving agent actions, you need an unforgeable record of who approved what. The gate produces the decisions; the audit log makes them permanent and reviewable.

Keep your approval-gate logic and the risk rules that trigger it versioned alongside your prompts in a library like PromptABCD, so the hard-won knowledge of which actions deserve a human — and how to pause safely for one — travels with the agent instead of being rediscovered, incident by incident, in every new service.

ai-harnessapproval-gatehuman-in-the-loopstateasyncsafety

Continue Reading

Managing Prompt Templates Across a Harness Codebase
AI Harness

Managing Prompt Templates Across a Harness Codebase

Four divergent copies of one prompt caused a two-day bug. Harness prompt templates management makes prompts versioned, tested, single-source artifacts instead of scattered strings.

September 10, 2026·8 min read
How to Open-Source Your Agent Harness
AI Harness

How to Open-Source Your Agent Harness

An agent harness isn't an ordinary library — it's security-sensitive infra tangled with your secrets. Release an open source agent harness without leaking a key or shipping unusable code.

September 10, 2026·8 min read
Error Taxonomy: Classifying Harness Failures
AI Harness

Error Taxonomy: Classifying Harness Failures

When every failure looks the same, you can't retry, route, or alert correctly. Agent harness error classification gives failures types that drive real behavior.

September 10, 2026·8 min read

Save the prompts from this post

PromptABCD is a free prompt manager. Paste, organize, and reuse your best AI prompts — no more hunting through chat history.

Start free →
← PreviousPermission Systems for Agent ToolsNext →How to Handle Secrets and API Keys in a Harness
Share this post:
ShareShare