Safety & Governance

How to Add Human Approval Gates to AI Agent Production Actions

A production agent should not be able to turn a proposed write, deletion, or configuration change into a live action before the required reviewer has weighed in. This guide shows how to place a human approval gate in a LangGraph Python workflow, preserve its paused state, and resume it with the reviewer’s decision.

The key is to separate the workflow mechanics from the policies your team must choose. LangGraph documents how to pause and resume execution; it does not set review triggers, timeout rules, escalation paths, or an audit-record format, so those need to be designed around your own risk and operations.

Recurring editorial scenes feature cats with clearly visible mechanical or cybernetic parts investigating AI-agent audit setups: tracing an agent's actions, examining evaluation results, or inspecting safety controls. Use crisp, atmospheric digital illustration with lime highlights and restrained purple haze against dark settings; keep subjects and props legible, and do not depict fake software screenshots, brand lettering, or logos.: How to Add Human Approval Gates to AI Agent Production Actions

Quick answer

The short version: route changes that your policy requires a person to review through a LangGraph node that calls interrupt() before the production action. Include a JSON-serializable summary of the proposed action, its target, and why it was flagged so the reviewer has relevant context.

Compile the graph with a checkpointer and keep a stable thread_id for the paused run. When a reviewer responds, resume that same thread with Command(resume=...); keep irreversible production side effects after the approval gate.

Plan for silence, too. An interrupt waits indefinitely, so your application or an external scheduler needs to handle deadlines and escalation; do not treat a timeout as approval.

The documentation describes the pause-and-resume mechanism, not a ticketing or chat integration, an audit schema, or the organization-specific rules for who may approve which changes.

Step-by-step guide

Prepare the LangGraph workflow and its persistence requirements

Before adding the approval gate, make sure your LangGraph Python workflow can route a proposed production change through a review node before it reaches the action that writes, deletes, or changes configuration. This gives you a clear place to pause execution before the production side effect.

Configure a checkpointer when you compile the graph, and decide how your application will retain a stable thread_id for each paused workflow. You’ll need that same thread ID when resuming the workflow so LangGraph can find the saved state; the interrupt itself does not provide persistence or a reviewer-notification process.

Sources: Interrupts — Docs by LangChain.

Choose which production actions must pause for review

Start by listing the agent capabilities that can change production state, then group proposed operations as writes, deletions, or configuration changes. For each group, decide which actions require sign-off under your organization’s risk policy; consider impact and reversibility, and state the rule explicitly rather than relying on an undocumented default.

Apply the gate before any action your policy marks for review, and document the trigger so the team can tell why a request paused. LangGraph supplies the pause-and-resume mechanism, not a rule for choosing triggers or a universal risk threshold.

This step is about selecting actions for review; the next step is where you build the review node and its payload.

Sources: Interrupts — Docs by LangChain.

Add a review node with a structured interrupt payload

Add a review node before the production-action node. Call interrupt() with a JSON-serializable payload that makes the proposed change understandable to a reviewer; use simple values such as strings rather than objects your workflow cannot serialize.

from langgraph.types import interrupt

def review_change(state):
    decision = interrupt({
        "action": state["proposed_action"],
        "target": state["target"],
        "reason": state["review_reason"],
    })
    return {"review_decision": decision}

The graph pauses at this call, and the reviewer’s response becomes decision when the workflow resumes. Check that the payload contains the right action, target, and reason; then make downstream routing send only approved changes to the production action.

Sources: Interrupts — Docs by LangChain.

Preserve the paused workflow for later resumption

Compile the graph with a checkpointer so LangGraph can save the state when execution pauses at the review interrupt. Then assign each workflow run a stable thread_id and retain it with the pending review; you will need the same ID to resume that saved execution.

Before moving on, confirm that your compiled graph has a checkpointer and that your application can retrieve the original thread_id for a paused run. A new or inconsistent ID can prevent LangGraph from finding the saved state when the reviewer responds.

Sources: Interrupts — Docs by LangChain.

Resume the same thread after a reviewer decision

When the reviewer responds, pass their decision to Command(resume=...) and invoke the graph with the same thread_id used for the paused run. For example, if your application has validated the reviewer’s decision and saved the original ID:

from langgraph.types import Command

graph.invoke(
    Command(resume={"approved": True}),
    config={"configurable": {"thread_id": thread_id}},
)

The workflow resumes from the interrupt with that response available to downstream logic. Verify that the response is associated with the pending review and that the original thread ID is used; if the graph cannot find its paused state, check the ID and checkpointer configuration.

Sources: Interrupts — Docs by LangChain.

Keep production side effects after the gate

Keep every irreversible production call—such as a write, deletion, or configuration update—in a node reached only after the review interrupt returns approval. Do not perform it in the review node before interrupt(); when execution resumes, that node starts again from the beginning.

If anything must run before the interrupt, make it idempotent so repeating it does not cause an unintended duplicate effect. Then inspect the graph’s routing: pending or rejected decisions must not reach the production-action node, while approval does.

Verify this ordering in a non-production workflow before enabling the action in production.

Sources: Interrupts — Docs by LangChain.

Define timeout and escalation behavior outside the interrupt

Because LangGraph interrupts wait indefinitely, set a deadline in your application or an external scheduler rather than expecting the interrupt itself to time out. Choose a response window that fits your operational risk and reviewer coverage; the documentation does not prescribe a duration.

At the deadline, keep the production action blocked. Mark the request as timed out, notify a designated backup or owner, and require an explicit decision before resuming; if nobody is available, leave the workflow paused or cancel it rather than treating silence as approval.

Record the request and thread identifiers, deadline, escalation attempts, and final outcome in your own audit trail. Verify the timeout path in a non-production run: no response should reach the production-action node, and escalation or cancellation should follow your stated policy.

Sources: Interrupts — Docs by LangChain.

Confirm the gate pauses and resumes as intended

Run a non-production check with a proposed change and confirm the graph pauses at the review node. Verify that the saved state and pending review are associated with the expected thread_id.

Submit a reviewer decision using Command(resume=...) and the same thread_id. Confirm the workflow continues from the saved execution and that only an approval reaches the production-action node; pending or rejected decisions should not execute it.

Finally, inspect the graph’s action order to ensure irreversible writes, deletions, and configuration changes happen after the approval interrupt. If resumption fails, check the checkpointer and thread ID; if earlier work repeats, make pre-interrupt side effects idempotent.

Sources: Interrupts — Docs by LangChain.

Diagnose a workflow that remains paused or repeats work

If the workflow will not resume, confirm that the graph was compiled with a checkpointer and that the resume call uses the same thread_id as the paused run. Check that your application retained that ID with the pending review.

If work repeats after resumption, remember that the interrupted node starts again from its beginning. Make pre-interrupt side effects idempotent, and keep irreversible production actions after approval.

If a reviewer does not respond, the interrupt will remain paused indefinitely. Handle deadlines and escalation in your application or an external scheduler; verify in a non-production run that a timeout does not send the workflow to the production action.

Sources: Interrupts — Docs by LangChain.

Next steps

Before enabling the gate for production, check that a proposed change pauses at the review node, that the interrupted state is tied to the expected thread_id, and that a reviewer response resumes that same run. Then trace the workflow’s routing to confirm that only an approval reaches the production action.

I’d also test the no-response path separately: the interrupt itself will not time out, so your application or scheduler must enforce the deadline and fallback you choose. If the gate behaves as intended, the next step is to apply your organization’s review, audit, and escalation policies to the workflow—not to assume the interrupt defines them.