Human-in-the-Loop AI Agents: The Approval State That Survives Pause and Resume
Human-in-the-loop agent control requires more than an approval button. A defensible implementation pauses the exact tool call, persists its identity and arguments, records the reviewer decision, and revalidates policy before execution resumes. This article maps that state machine, including nested-agent approvals, stale decisions, duplicate resumes, and the boundary between an HTTP policy decision and the orchestrator that performs the approved action.

A human-in-the-loop AI agent reaches a sensitive tool call and stops before execution. The orchestrator saves the proposed call, a reviewer makes a decision, and the run resumes later. That pause may last 30 seconds or three days. During it, the user's role can change, the policy can be updated, and the target record can move into a different state. Safe approval therefore depends on four verbs: pause, persist, revalidate, resume. Treating the approval click as permanent authority turns a useful safety control into a stale permission.
TL;DR
- Pause the exact tool call before any side effect, then persist its call ID, arguments, originating identity, policy version, and expiry.
- Record approval, edited arguments, rejection, or a direct response as a separate reviewer decision.
- Revalidate identity, arguments, policy, target state, and expiry when the run resumes. An approval is never a standing grant.
- DeepInspect can escalate routed HTTP AI requests. The orchestrator owns persistence, reviewer UX, and execution after approval.
Approval is a state machine
The OpenAI Agents SDK human-in-the-loop documentation describes the core flow precisely. A sensitive tool call pauses the run. RunState serializes the paused execution, a person approves or rejects the interruption, and the run resumes from that state. Approvals raised inside a nested agent surface on the outer run, where the application can present them to a reviewer.
That mechanism gives platform teams a better design target than a generic instruction to keep a person involved. I would reject any architecture review that shows an approval button without a durable state transition behind it. The button is the visible rectangle on the screen. The control is the record that binds one reviewer decision to one proposed operation.
A practical state machine needs at least pending, approved, edited, rejected, expired, executing, and completed states. Each transition has one authorized actor and one allowed next step. A second click on an already completed approval should return the recorded outcome instead of executing the tool twice.
Pause before the side effect
The interruption must happen after the agent proposes a tool call and before the tool performs its side effect. For a payment agent, the stored proposal might name issue_refund, order ORD-18472, amount $742.18, currency USD, and the customer account. For an infrastructure agent, it might contain a production deployment target and an image digest. The reviewer needs the proposed operation, not a paraphrase generated after execution.
This is where inline enforcement and human approval meet. A policy decision can pass low-risk traffic, deny a prohibited operation, or escalate sensitive content. Escalation creates a pending state and blocks forwarding until the application supplies a valid decision.
OpenAI's SDK documentation sends malformed or unsafe-to-inspect tool arguments into manual approval rather than letting them slip past inspection. An unreadable argument object is an authorization failure. The application should preserve the original bytes or structured payload needed for review and prevent the proposed call from reaching its destination.
Persist the proposed operation
The persisted record should identify the call being approved with enough precision to prevent substitution. At minimum, store the run ID, tool-call ID, tool name, complete arguments, originating human identity, agent identity, requested destination, creation time, policy version, and expiry. Add a digest of the canonicalized arguments when the resume path can verify it.
Keep the reviewer decision separate from the agent's saved state. The decision record should capture the reviewer identity, timestamp, outcome, any edited arguments, and the policy presented during review. This separation prevents the same process that proposed the action from silently rewriting its approval evidence. It also supports the action-lineage model needed when an outer agent delegates to a nested specialist.
The database row is the concrete control here. A reviewer looking at a yellow pending card should see the exact recipient, amount, environment, or resource that will be affected. Labels such as "approve agent action" hide the object under review and create room for a changed argument to pass under an old decision.
Reviewer decisions have different semantics
The LangChain human-in-the-loop middleware documentation defines four decisions: approve the call unchanged, edit its arguments, reject it with feedback, or respond directly for tools designed to ask a user. LangChain persists graph state at the interrupt and resumes it after the decision.
Those outcomes should produce different transitions. Approval preserves the proposed arguments. An edit creates a new proposed operation and should receive a new digest, even when the same reviewer supplies the change. Rejection terminates that branch and returns explicit feedback to the agent. A direct response supplies information to an "ask user" tool without executing an external side effect.
LangChain warns against using respond to deny a side-effecting action because the framework treats the response as a successful tool result. That detail can alter the agent's next move. A rejected wire transfer blocks the action, while a successful transfer returning the text "denied" may tell the planner that the tool completed.
Resume means revalidate
An approval proves that a reviewer accepted a specific operation at a specific time. Execution still needs a fresh boundary decision. Before resuming, check that the originating identity remains active, the agent retains delegated authority, the approval is unexpired, the tool arguments match the approved version, and the current policy still permits the call. The target resource may also need an optimistic concurrency check.
Suppose the reviewer approves access to a customer file at 16:58 on Friday. Security removes the requesting employee from the support role at 17:03, and a worker resumes the run at 17:11. Replaying Friday's approval without checking the current role converts a seven-minute delay into an authorization bypass.
This is an extension of the post-authentication gap. Valid identity establishes the caller. Per-request authorization decides what that caller may do now. Resume is another request boundary, so it needs the same evaluation rather than trusting serialized state as current authority.
Nested agents and duplicate resumes
Nested agents complicate ownership. OpenAI surfaces nested approvals on the outer run, which gives the application one place to collect reviewer decisions. The persisted record still needs the agent path that produced the interruption. "Research agent requested database agent to export customer records" carries more useful lineage than "run paused."
Queue workers can retry after the same resume message arrives twice, a reviewer double-clicks, or two browser tabs submit conflicting decisions. Use an idempotency key tied to the approval ID and an atomic transition out of the approved state. Only one worker should claim execution. Later attempts should read the completed outcome.
These controls belong beside excessive-agency limits, not inside the model prompt. A prompt saying "ask first" remains probabilistic. The state machine makes execution unavailable while the record is pending and unavailable again after the one approved operation completes.
The HTTP boundary
Human approval spans components with different jobs. An HTTP policy point can inspect a routed model or agent request, bind it to supplied identity, and return an escalation decision. The orchestrator must stop the workflow, persist the state, display the review, accept the decision, and resume the run. A gateway cannot click its own approval card or preserve an application graph it never owns.
Local shell commands, STDIO tools, offline approval channels, and UI actions that bypass routed HTTP traffic sit outside DeepInspect's enforcement boundary. The application has to gate those execution paths directly. For HTTP requests sent to an LLM through the gateway, a resumed call can be evaluated again against the current identity, destination, content, and policy version.
In my view, products that claim to "provide human oversight" while owning only one half of this state machine are selling an interface label. The defensible design names the component responsible for every transition and preserves evidence across the pause.
DeepInspect
DeepInspect can act at the HTTP AI request boundary when an authenticated user or agent sends a model request through the gateway. Policy can pass, block, redact, or escalate that routed request, and the decision produces an identity-bound audit record. An escalated response gives the orchestrator a deterministic reason to pause.
The orchestrator remains responsible for durable RunState or graph persistence, the reviewer screen, expiry, idempotent execution, and tool-side effects. When it submits the resumed LLM request through DeepInspect, the gateway evaluates the request again under current policy. That second evaluation keeps a three-day-old approval from becoming unrestricted authority.
Book a technical deep dive at deepinspect.ai.