Human-in-the-loop is a state machine, not a button

An Approve button is not a human-in-the-loop strategy. Approval is a state — with timeouts, escalation paths, rollback, and audit trails. Agents that pause for a click without a state machine are performing safety theater.

Engineering6 min read
AI agentsHuman-in-the-loopWorkflowState machinesProduction AI
Share

The agent drafted a refund for $4,200. A modal appeared: Approve or Reject. The operator was in a meeting. Twenty minutes later, the customer had already called support twice. The approval request sat in a queue with no timeout, no escalation, no audit of who was notified, and no safe default when nobody answered. Human-in-the-loop was implemented as a button. Production needed a state machine.

Human-in-the-loop (HITL) exists because some agent actions carry risk that exceeds autonomous execution thresholds — financial transactions, irreversible data changes, customer-facing communications, privileged operations. The pattern is sound. The implementation usually is not: a UI interrupt without defined states, transitions, timeouts, or failure modes. That is not governance. It is a pause that sometimes never ends.

A button models a moment; a state machine models a process

ApproachWhat it handlesWhat it misses
Approve/Reject buttonSingle decision pointTimeout, escalation, partial approval, rollback
Email notificationAlerting a humanStructured response, audit, idempotency
Slack messageFast visibilityState persistence, concurrent reviewers
State machineFull lifecycleRequires upfront design

A HITL state machine treats each gated action as a workflow instance with explicit states and transitions:

pending_review
├─(approve)→ approved → executing → completed
├─(reject)→ rejected → cancelled
├─(timeout)→ escalated → pending_review (new assignee)
├─(timeout, no escalation target)→ auto_rejected | auto_approved (policy)
└─(execute fails)→ failed → rollback_pending → rolled_back | manual_intervention

Every transition logs: actor (human or system), timestamp, reason, prior state, payload hash. Audit is not a separate feature — it is the transition table.

An approval queue without expiry is a denial-of-service attack the product built against itself.

States every HITL workflow needs

pending_review. Action proposed, execution blocked. Includes: proposed payload, risk score, requester context, assigned reviewer pool, expires_at, escalation level.

approved / rejected. Terminal human decisions. Rejection should capture reason code — not free text alone — for model and policy improvement.

escalated. Timeout or overload triggered reassignment. Records who was skipped and why. Escalation paths must be finite — infinite escalation is another form of stuck.

executing. Human approved; side effects in progress. Prevents double-execution if reviewer double-clicks or network retries fire duplicate approvals.

completed / failed / rolled_back. Post-execution truth. Failed execution with partial side effects needs rollback state — not "try again" without reconciliation.

auto_resolved. Policy-driven default when humans do not respond. Must be explicit and rare: auto-reject high-risk actions, auto-approve low-risk within bounds. Silent auto-approve on high-risk actions is how incidents become headlines.

Timeouts and escalation are not optional polish

Without expires_at, pending reviews accumulate. Agent workflows block. Customers wait. Operators return to a backlog of stale decisions with outdated context.

Design timeouts per risk tier:

Risk tierExample actionReview timeoutEscalationDefault on exhaustion
LowDraft email, internal note4 hoursTeam channelAuto-approve or auto-cancel
MediumRefund under threshold30 minutesOn-call rotationAuto-reject
HighBulk delete, wire transfer15 minutesNamed approver + backupAuto-reject, alert
CriticalProduction config change5 minutesPagerAuto-reject, block action

Escalation must change assignee and notify — not just re-send the same notification to someone already ignoring it. After N escalation levels, hit a defined terminal state. "Still pending" is not a terminal state.

The AI agent runbooks for production pattern covers operational response when agents fail — HITL is the prevention layer for actions that should not fail autonomously in the first place.

Idempotency and concurrency prevent double execution

Two operators open the same approval. Both click Approve. Without idempotency, the refund runs twice.

Requirements:

  • Approval token bound to single workflow instance ID.
  • Transition from pending_review to approved is atomic — first wins, second gets "already resolved."
  • Execution step idempotent against external systems — payment API with idempotency key, database upsert with dedup key.
  • Optimistic locking on state version field.

Concurrent review without locking is a race condition dressed as collaboration.

Rollback and trust calibration belong in the same design

Human approval does not guarantee correct execution. Approved actions can fail mid-flight. Partial failures need rollback transitions and human notification — not silent retry loops.

AI UI trust calibration addresses how much autonomy users grant agents in the interface. HITL state machines implement that calibration in backend workflow: which actions require human states, which proceed autonomously, and what happens at boundaries. Frontend trust settings and backend state machines must agree — a UI that shows "auto" while the backend queues every action creates confusion in both directions.

How should teams implement human-in-the-loop?

These design choices separate production governance from demo interruptions.

Is human-in-the-loop the same as approval workflows?

Approval workflows are one instance of HITL. HITL also includes: human edit before send, human selection among agent proposals, human override of agent classification. Each variant needs states — not every variant needs Approve/Reject binary.

What should happen when nobody responds?

Never leave actions in pending_review indefinitely. Explicit policy: auto-reject by default for irreversible or high-value actions; auto-approve only for low-risk actions below defined thresholds with full audit. Document the policy where operators can see it before they miss a timeout.

How does HITL interact with agent context budgets?

Pending reviews hold agent state — tool results, proposed actions, conversation context. Long pending periods inflate context window budgets when the agent resumes. Prefer summarizing pending state on resume rather than replaying full pre-approval transcript.

A common argument runs the other way

The opposing view holds that HITL defeats the purpose of agents — that autonomous systems should be trusted or not deployed, and approval gates create bottlenecks worse than the risk they prevent.

Bottlenecks come from buttons without state machines, not from HITL itself. Well-designed HITL auto-resolves low-risk paths instantly, routes medium-risk to async review, and blocks only genuinely high-risk actions. That is faster than recovering from autonomous mistakes in production.

Full autonomy without HITL is valid for read-only or easily reversible domains. The state machine still exists — it just has no pending_review transitions.

Key takeaways

  • Human-in-the-loop is a state machine: pending, approved, rejected, escalated, executing, completed, failed, rolled back.
  • Approve/Reject buttons without timeouts create indefinite blocks and stale decisions.
  • Escalation paths must be finite with explicit terminal defaults.
  • Idempotent transitions prevent double execution from concurrent approvers.
  • Audit every state transition — actor, timestamp, reason, payload hash.
  • Align backend HITL states with frontend trust calibration settings.

Conclusion

Safety theater puts a human in the screenshot. Production HITL puts humans in a workflow with defined endings — what happens when they approve, reject, ignore, or disconnect. The state machine is the difference between governance and a modal that never closes.

The audit for existing agent products: list every action that waits for human input. For each, document current state, timeout, escalation, and default when nobody responds. Empty cells are the implementation roadmap.

Related articles

Command Palette

Search for a command to run...