← Back to DevBytes

Human-in-the-Loop Agents with LangGraph Interrupts

What Are Human-in-the-Loop Agents?

Human-in-the-loop (HITL) agents are AI systems that can pause their execution to request and incorporate human judgment, approval, or additional input. This pattern is essential when building reliable and safe autonomous agents that operate in high-stakes environments. LangGraph, a stateful orchestration framework from the LangChain ecosystem, provides first-class support for HITL workflows through its interrupts mechanism. An interrupt allows a graph node to suspend execution, persist the agent’s state, and wait for external input before resuming.

Unlike traditional “fire-and-forget” agent loops, a LangGraph graph with interrupts can stop at predefined checkpoints, expose the current state to a human operator, and then continue from exactly where it left off. This makes it ideal for tasks like approval workflows, form filling, debugging, and any scenario where the agent must align with human preferences or policies.

Why Human-in-the-Loop Matters

How LangGraph Implements Interrupts

LangGraph graphs are built from nodes (Python functions) connected by edges. A special function interrupt() can be called inside any node. When invoked, the graph halts, the current state is saved via a checkpointer, and control returns to the calling code. The developer can then inspect the state, collect human input, and resume execution by passing a Command object with the required data.

Key components:

Setting Up Your Environment

Install the required packages. You need langgraph, langchain-core, and optionally a model provider like langchain-openai if your agent uses an LLM.

pip install langgraph langchain-core langchain-openai

We’ll use an in-memory checkpointer for simplicity, but you can swap in SqliteSaver or PostgresSaver for production persistence.

Defining a State Graph with Interrupts

Let’s build a simple travel approval agent. The agent collects a travel request from the user, then pauses for a manager’s approval before booking the trip. The state holds the request details and an approval flag.

1. Define the State Schema

We use a TypedDict or Pydantic model to define the shape of the state that flows through nodes.

from typing import TypedDict, Optional
from langgraph.graph import StateGraph

class TravelState(TypedDict):
    destination: str
    budget: float
    approved: Optional[bool]  # None until human decides
    booking_status: str       # will be set after approval

2. Create Node Functions

We need nodes for: collecting user input, requesting approval (the interrupt point), and booking the trip.

def collect_request(state: TravelState, user_input: str = None):
    """Simulate receiving user request. In real app, this might parse a message."""
    # For demo, we set destination and budget directly
    return {"destination": "Paris", "budget": 2000}

def human_approval(state: TravelState):
    """Interrupt to wait for manager approval."""
    from langgraph.errors import interrupt
    # Show what needs approval
    approval_message = f"Approve travel to {state['destination']} with budget {state['budget']}?"
    # Pause execution here – control returns to the caller
    decision = interrupt(approval_message)
    # Once resumed, decision contains the human's input (True/False)
    return {"approved": decision}

def book_trip(state: TravelState):
    if state.get("approved"):
        # Simulate booking
        return {"booking_status": "Booked successfully"}
    else:
        return {"booking_status": "Booking cancelled"}

3. Assemble the Graph

Add nodes and edges. The human_approval node is where the interrupt occurs. After it resumes, we conditionally branch based on the approval decision.

builder = StateGraph(TravelState)

builder.add_node("collect_request", collect_request)
builder.add_node("human_approval", human_approval)
builder.add_node("book_trip", book_trip)

# Flow: collect → approval → book
builder.add_edge("collect_request", "human_approval")
builder.add_edge("human_approval", "book_trip")

# Set entry point
builder.set_entry_point("collect_request")

# Compile with a checkpointer (required for interrupts)
from langgraph.checkpoint.memory import MemorySaver
graph = builder.compile(checkpointer=MemorySaver())

Handling Interrupts in the Invocation Loop

When you invoke the compiled graph, execution will proceed until the interrupt() call. LangGraph then raises an Interrupt exception (or returns an interrupt event when streaming). Your outer loop must catch that exception, present the interrupt message to the human, collect their response, and resume with a Command.

Basic Invocation with Exception Handling

config = {"configurable": {"thread_id": "travel-001"}}  # thread_id ties to state persistence

# First invocation – runs until interrupt
try:
    result = graph.invoke({"destination": "", "budget": 0, "approved": None, "booking_status": ""}, config)
    # If no interrupt, result would be final state
    print("Final result:", result)
except Exception as e:
    # Interrupts are wrapped; check for Interrupt type
    from langgraph.errors import Interrupt
    if isinstance(e.__cause__, Interrupt):
        interrupt_event = e.__cause__
        print("Agent paused:", interrupt_event.args[0])  # The approval message
        # Now collect human input...

In practice, you’ll use the exception handling to display the message and then resume.

Resuming with Human Input Using Command

To resume, you create a Command object with the resume argument set to the value that interrupt() should return. You pass this command back to graph.invoke() using the same config (same thread_id).

from langgraph.types import Command

# After catching the interrupt, suppose we got human approval as True
human_decision = True  # This could come from a UI prompt

# Create resume command
resume_cmd = Command(resume=human_decision)

# Resume the graph from where it stopped
final_state = graph.invoke(resume_cmd, config)
print("Final state after resume:", final_state)
# Output will show approved=True and booking_status='Booked successfully'

Full Interactive Loop Example

This snippet demonstrates a complete pattern: invoke, catch interrupt, ask user via console, resume.

import sys
from langgraph.errors import Interrupt
from langgraph.types import Command

config = {"configurable": {"thread_id": "travel-002"}}

# Initial input (empty state)
initial_state = {"destination": "", "budget": 0, "approved": None, "booking_status": ""}

while True:
    try:
        result = graph.invoke(initial_state, config)
        print("Flow completed without interrupt:", result)
        break  # done
    except Exception as e:
        if isinstance(e.__cause__, Interrupt):
            interrupt_event = e.__cause__
            message = interrupt_event.args[0]
            print("⏸️  Agent paused:", message)
            # Get human input from terminal (simulate UI)
            user_resp = input("Your response (True/False): ").strip()
            if user_resp.lower() == "true":
                decision = True
            else:
                decision = False
            # Resume
            resume_cmd = Command(resume=decision)
            # Replace initial_state with the command for next invoke
            initial_state = resume_cmd
            # Loop continues, now graph.invoke(Command(...), config) resumes
        else:
            raise

The loop is necessary because after resume, the graph might hit another interrupt (if there are multiple). For a single interrupt, one catch-and-resume suffices.

Best Practices

Conclusion

Human-in-the-loop agents built with LangGraph interrupts give you precise control over when and how humans intervene in an autonomous workflow. By combining interrupt(), persistent checkpoints, and the Command resume pattern, you can create agents that are both powerful and safe. Whether you’re building a financial approval dashboard, a medical triage assistant, or an interactive debugger, LangGraph’s interrupt mechanism turns a monolithic agent into a collaborative, auditable system. Start with the simple patterns shown here, adopt the best practices, and you’ll be well on your way to robust human-AI partnerships.

— Ad —

Google AdSense will appear here after approval

← Back to all articles