Human-in-the-Loop Patterns: When to Ask for Permission


Your AI deployment agent pushes code changes automatically. It works perfectly for 99 deploys. Deploy #100 pushes a database migration that drops a production table. No human reviewed it because the agent had full autonomy. Recovery takes 4 hours. You add a rule: “require human approval for any database migration.” Now the agent handles routine deploys autonomously but pauses for review when it detects schema changes. You get the speed of automation with the safety of human judgment for risky operations.

This is human-in-the-loop (HITL) - not a binary choice between full automation and manual work, but a spectrum where the agent decides autonomy level based on risk assessment.

The approval spectrum

graph LR
  subgraph spectrum["Autonomy Spectrum"]
      FULL["Full Autonomy
Agent decides and acts
No human involved"]
      NOTIFY["Notify
Agent acts, then informs
Human can review after"]
      APPROVE["Approve/Reject
Agent proposes, waits
Human must confirm"]
      SUGGEST["Suggest Only
Agent recommends
Human must execute"]
  end

  style FULL fill:#FCEBEB,stroke:#A32D2D,color:#791F1F
  style NOTIFY fill:#FAEEDA,stroke:#854F0B,color:#633806
  style APPROVE fill:#E1F5EE,stroke:#0F6E56,color:#085041
  style SUGGEST fill:#EEEDFE,stroke:#534AB7,color:#3C3489

When to require human approval

High-risk actions: Irreversible operations, financial transactions, external communications, production changes, data deletion.

Low-confidence decisions: When the agent’s reasoning indicates uncertainty, conflicting information, or ambiguous user intent.

Policy boundaries: Actions that cross compliance, legal, or organizational boundaries regardless of confidence.

Novel situations: First-time operations the agent has never performed, edge cases not covered by training.

Implementation pattern

class HumanApprovalGate:
    ALWAYS_APPROVE = ["delete_data", "send_payment", "deploy_production", "send_external_email"]
    CONFIDENCE_THRESHOLD = 0.7
    
    async def check(self, action, confidence, context):
        if action.name in self.ALWAYS_APPROVE:
            return await self.request_approval(action, reason="high_risk_action")
        
        if confidence < self.CONFIDENCE_THRESHOLD:
            return await self.request_approval(action, reason="low_confidence")
        
        if action.affects_external_systems:
            return await self.request_approval(action, reason="external_impact")
        
        return ApprovalResult(approved=True, auto=True)
    
    async def request_approval(self, action, reason):
        # Send to human review queue
        ticket = await review_queue.submit(
            action=action,
            reason=reason,
            context=action.context,
            timeout=timedelta(minutes=30)
        )
        return await ticket.wait_for_decision()

Key design principles

Make approval fast: Show the human exactly what the agent wants to do, why, and what the alternatives are. A one-click approve/reject with context is faster than asking them to evaluate from scratch.

Batch approvals when possible: If the agent needs approval for 5 similar actions, present them as a batch rather than 5 interruptions.

Learn from approvals: Track which actions get approved vs rejected. If a category is always approved, consider automating it. If always rejected, update the agent’s behavior.

Timeout gracefully: If the human does not respond within the timeout, either escalate, retry, or abort with explanation - never block indefinitely.

Preserve agent state: When paused for approval, save the agent’s full state so it can resume exactly where it left off without re-reasoning.

FAQ

Q: How do I prevent human-in-the-loop from becoming a bottleneck?

Route only genuinely risky decisions to humans. Track approval rates - if 95% of requests are approved, your threshold is too conservative. Implement tiered review: junior reviewers handle routine approvals, senior reviewers handle complex ones. Allow bulk approval for batched similar requests.

Q: Should the agent explain its reasoning when requesting approval?

Always. Humans approve faster and more accurately when they understand the agent’s reasoning: “I want to issue a $150 refund because the customer’s order was 3 days late and our SLA promises 2-day delivery. Policy 4.2 allows refunds up to $200 for SLA violations.” This takes 5 seconds to review vs 2 minutes to evaluate from scratch.

Interview questions

Q: Design the HITL system for an AI agent that manages cloud infrastructure (scaling, deploying, incident response). Which actions need approval and which can be autonomous?

Autonomous (low risk, reversible): auto-scaling within pre-defined bounds, restarting unhealthy pods, rotating logs. Notify (moderate risk): scaling beyond normal bounds, deploying to staging, updating non-critical configs. Approve required (high risk): deploying to production, modifying database schemas, changing security groups, creating/deleting infrastructure resources. Suggest only (critical risk): modifying IAM policies, touching encryption keys, anything affecting billing accounts. For incident response: allow autonomous remediation for known patterns (restart, scale-up) but require approval for novel remediations the agent has not executed before. Implement a “confidence-degraded” mode where approval thresholds lower during active incidents to balance speed vs safety.