This lesson on Human-in-the-Loop Breakpoints and Interrupts is hands-on and example-driven. You will implement human-in-the-loop approval and parameter modification workflows in LangGraph using interrupt() and Command(resume=...). You will configure state checkpointers and thread identifiers to reliably pause agents before sensitive tool actions and resume them with user-validated payloads.
What You'll Be Able To Do
- Define custom tools that trigger the
interrupt()function to halt execution before sensitive actions. - Implement conditional branching logic inside tool definitions to handle approval, edit, and error payloads.
- Initialize a ReAct agent using
create_react_agentwith an active checkpointer and thread-scoped memory. - Resume paused agent workflows by streaming a
Command(resume=...)object targeting a specificthread_id. - Pass modified arguments from external clients through the resume command to alter tool parameters dynamically.
Detailed Concept Walkthrough
1. Triggering Tool Interrupts and Defining Feedback Schemas
The interrupt() function halts LangGraph execution inside a tool and yields a prompt payload to the caller, waiting for an external response. It turns deterministic tool invocations into interactive human checkpoints.
- Mechanism: Calling
interrupt("prompt payload")inside a tool immediately suspends graph execution and surfaces the argument to the caller. When the graph is resumed,interrupt()returns whatever payload was supplied toCommand(resume=...). - Execution Flow: The tool evaluates the structure of the resumed payload, branching execution based on user intent such as accepting the proposed parameters, applying user edits, or aborting.
- Best Practice: Always validate the structure of the returned response and raise a descriptive
ValueErrorfor unsupported response types or missing arguments.
from langchain_core.tools import tool
from langgraph.types import interrupt
@tool
def book_hotel(hotel_name: str) -> str:
"""Book a hotel reservation with human approval."""
# Pause execution and request confirmation
response = interrupt(f"Approve booking for {hotel_name}?")
if response.get("type") == "accept":
return f"Successfully booked {hotel_name}."
elif response.get("type") == "edit":
hotel_name = response["args"]["hotel_name"]
return f"Successfully booked updated hotel: {hotel_name}."
else:
raise ValueError(f"Unsupported response type: {response.get('type')}")
Key Takeaway: The
interrupt()function suspends the node and dynamically evaluates to the payload supplied during resumption.
2. Checkpointer Persistence and Thread Scoping
Interrupts require thread-level state persistence to store graph snapshots while execution is paused. Without a checkpointer, the graph cannot preserve the call stack or resume interrupted nodes.
- Under the Hood: A checkpointer serializes graph state at each superstep into durable or in-memory storage keyed by
thread_id. When an interrupt occurs, the paused tool invocation and conversational history remain persisted in the checkpoint. - Syntax Rule: Every invocation targeting an agent with interrupts must supply a configuration dictionary containing a valid
thread_idvia{"configurable": {"thread_id": "..."}}. - System Behavior: Multiple user sessions run concurrently and independently by isolating their pause and resume states within unique thread identifiers.
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
checkpointer = MemorySaver()
model = ChatOpenAI(model="gpt-4o")
# Checkpointer is mandatory for interrupt support
app = create_react_agent(
model=model,
tools=[book_hotel],
checkpointer=checkpointer
)
config = {"configurable": {"thread_id": "1"}}
Key Takeaway: An active checkpointer and a consistent
thread_idare strict prerequisites for persisting and restoring interrupted states.
3. Resuming Suspended Workflows with Command
The Command primitive delivers external human decisions back into a suspended graph. It routes user responses directly into the waiting interrupt() expression.
- Mechanism: Invoking
app.stream(Command(resume=payload), config=config)matches the paused state viathread_idand substitutes theCommandpayload as the return value ofinterrupt(). - Under the Hood: LangGraph loads the latest checkpoint associated with the
thread_id, unblocks the suspended node, and resumes execution without re-running preceding nodes. - Best Practice: In distributed or REST API architectures, client applications must pass back the persistent thread token alongside the user's approval or modification payload.
from langgraph.types import Command
# 1. Trigger initial run until it pauses at the interrupt
initial_input = {"messages": [("user", "Book ABC Hotel")]}
for event in app.stream(initial_input, config=config):
print(event)
# 2. Resume execution with an approval
resume_command = Command(resume={"type": "accept"})
for event in app.stream(resume_command, config=config):
print(event)
# 3. Alternatively, resume with edited arguments
edit_command = Command(resume={"type": "edit", "args": {"hotel_name": "XYZ Hotel"}})
# app.stream(edit_command, config=config)
Key Takeaway:
Command(resume=...)injects external human input directly into the paused node identified by the matchingthread_id.
Topics Covered in Human-in-the-Loop Breakpoints and Interrupts
- Tool Definition with Interrupt (0:00 - 2:44) — Import the interrupt function and create a hotel booking tool that prompts the user for action confirmation.
- Conditional State Branching (2:44 - 5:03) — Add conditional logic to process acceptance, parameter modifications, or invalid responses from the interrupt payload.
- Agent and Checkpointer Setup (5:03 - 6:21) — Instantiate an in-memory checkpointer and configure a ReAct agent using GPT-4o with tool binding.
- Triggering Graph Interruption (6:21 - 8:44) — Execute the agent workflow under a designated thread ID to observe execution pausing at the tool interrupt.
- Resuming with Command (8:44 - 10:48) — Resume the paused agent using a Command object carrying an accept payload to complete the reservation.
- Mid-Flight Argument Modification (10:48 - 12:54) — Pass edited arguments through the resume command to alter hotel booking parameters before tool execution finishes.
LangChain & LangGraph for AI Agents Cheat Sheet
-
interrupt(value)— Pauses tool execution and exposes payload to callerresponse = interrupt(f"Approve {hotel_name}?") -
Command(resume=...)— Passes external data to resume a paused interruptCommand(resume={"type": "accept"}) -
MemorySaver()— Creates an in-memory checkpointer for thread persistencecheckpointer = MemorySaver() -
create_react_agent(..., checkpointer=...)— Initializes a ReAct agent configured with checkpointeragent = create_react_agent(model, tools, checkpointer=checkpointer) -
{"configurable": {"thread_id": "..."}}— Defines thread context for execution trackingconfig = {"configurable": {"thread_id": "1"}}
Comparison Table
| Action Type | Resume Payload Pattern | Tool Execution Effect |
|---|---|---|
| Approval | {"type": "accept"} | Executes with original model-generated arguments |
| Edit | {"type": "edit", "args": {...}} | Overrides arguments before finalizing operation |
| Invalid / Unknown | {"type": "reject"} or unrecognized | Raises ValueError terminating tool node execution |
Common Pitfalls
- Mistake: Resuming execution without passing the matching thread configuration. Avoid: Always include the identical thread_id config dictionary in the resume call.
- Mistake: Calling interrupt in an agent graph without binding a checkpointer. Avoid: Pass a valid checkpointer like MemorySaver when initializing the agent.
- Mistake: Failing to validate response types returned by the interrupt. Avoid: Implement conditional branches with explicit fallback error handling for unexpected payloads.
FAQs
- Why does interrupt() throw an error if no checkpointer is provided? LangGraph requires state persistence to serialize and pause the execution frame. Without a checkpointer, the graph cannot store the paused state to resume later.
- How does LangGraph map the resume payload to the specific paused tool?
LangGraph uses the
thread_idin the config dictionary to retrieve the checkpoint where the interrupt was triggered and passes the payload directly to the paused call. - Can I use interrupts across an asynchronous REST API?
Yes. Persist the
thread_idin your session or database, pause the request, and invokeCommand(resume=...)on thatthread_idupon receiving the API callback.