CTCaliberTek
Course: LangGraph: Step by Step (9 Modules) · Lesson 6 of 9Log in to track your progress and earn a certificate when you finish the course.
AI & Machine Learning

LangGraph Module 6: Human-in-the-Loop with Interrupts

Published October 11, 2026·Updated October 29, 2026·9 min read
Like SaveLog in to like, save, and track progress.

Introduction

Some actions should not happen without a person's say-so: sending an email, issuing a refund, deleting data. LangGraph's interrupt function pauses the graph, saves its state, and waits. You resume it later with the person's answer. This module builds an approval step.

Prerequisites

  • Module 5 completed
  • Understanding of checkpointers and thread IDs

Step-by-Step Guide

Step 1: Know the requirements

Interrupts need a checkpointer, because the graph's state must be saved while it waits, and a thread_id so you can resume the right run. Compile with a checkpointer and always pass the same thread_id when resuming.

Step 2: Pause with interrupt

Call interrupt inside a node with any JSON-serializable value, such as a question and the details of the pending action. Execution stops there. When resumed, the value you resume with becomes the return value of interrupt.

from typing import Literal
from typing_extensions import TypedDict
from langgraph.types import interrupt, Command

class State(TypedDict):
    action: str
    status: str

def propose(state: State) -> dict:
    return {"action": "Delete 3 old log files"}

def approval(state: State) -> Command[Literal["execute", "cancel"]]:
    decision = interrupt(
        {"question": "Approve this action?", "action": state["action"]}
    )
    return Command(goto="execute" if decision else "cancel")

def execute(state: State) -> dict:
    return {"status": "executed"}

def cancel(state: State) -> dict:
    return {"status": "cancelled"}

Step 3: Build and run until it pauses

Wire the graph and invoke it. When an interrupt fires, the result contains an __interrupt__ entry with the value you passed, which your application shows to the user.

from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver

builder = StateGraph(State)
builder.add_node("propose", propose)
builder.add_node("approval", approval)
builder.add_node("execute", execute)
builder.add_node("cancel", cancel)
builder.add_edge(START, "propose")
builder.add_edge("propose", "approval")
builder.add_edge("execute", END)
builder.add_edge("cancel", END)

graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "approval-1"}}

result = graph.invoke({"action": "", "status": ""}, config)
print(result["__interrupt__"][0].value)

Step 4: Resume with the decision

Send a Command with resume set to the person's answer, using the same thread_id. The graph continues from the interrupt, and the answer becomes the interrupt call's return value. Command(resume=...) is the only Command form meant to be used as input to invoke.

result = graph.invoke(Command(resume=True), config)
print(result["status"])   # executed

Step 5: Follow the rules for safe interrupts

When a graph resumes, the node that called interrupt starts again from its beginning, so code before the interrupt runs twice. Make side effects before an interrupt idempotent or put them after it. Do not wrap interrupt in a bare try and except, because it works by raising an exception, and do not conditionally skip or reorder interrupt calls inside a node. Prefer a conditional edge over a while loop to ask again.

FAQs

How is this different from the human approval middleware in LangChain?
That middleware is a ready-made way to gate specific tool calls and is built on this same mechanism. Using interrupt directly lets you place approval or input requests anywhere in a custom graph.
Can the pause last for days?
Yes, if you use a durable checkpointer such as SQLite or Postgres. The saved state waits until you resume with the same thread_id.

Summary

Use a checkpointer, call interrupt to pause with a payload, read __interrupt__ to show it, and resume with Command(resume=...) on the same thread. Remember that the node restarts on resume.

Related tutorials

Comments

Log in or create a free account to join the discussion.

  • No comments yet. Be the first to comment.