Module 9: Human-in-the-Loop

Introduction: Agents with Human Oversight

Overview

Your AI Research Assistant is persistent. It has checkpointing with MemorySaver, crash recovery, time-travel debugging, long-term memory with Store, and multi-user support with thread_id. You built it in Module 8 and it's a system that remembers.

But it remembers everything and acts on everything without asking.

Picture this scenario: you tell your agent "research the best AI tools for healthcare." The agent breaks down the query, searches the web, and decides it needs deeper data. So it:

  1. Calls an academic papers API that charges $0.10 per query — 50 times
  2. Finds a relevant result and decides to email it to the team
  3. To "clean up" the temporary data, runs a DELETE on the partial results table

Nobody asked for step 2 or step 3. And step 1 cost $5 when $0.50 would have been enough. Every one of those actions was technically correct according to the agent's logic — but operationally unacceptable without oversight.

An agent's autonomy is a privilege, not a right. You decide how much freedom to give it.

That's Human-in-the-Loop (HITL): the ability to pause an agent's execution, show the human what it plans to do, and wait for a decision before continuing. It isn't a safety patch — it's a fundamental design decision that separates prototypes from production systems.


Where are we in the guide?

This is Module 9 of the guide LangChain & LangGraph: From Chains to Agents. It's the second module of Block 3 (Advanced LangGraph).

Block 1: LangChain Core (Modules 1-4)           ✅ Done
Block 2: LangGraph Fundamentals (Modules 5-7)   ✅ Done
Block 3: Advanced LangGraph (Modules 8-10)      ← YOU ARE HERE (Module 9)
Block 4: Production (Modules 11-12)
Your progress:

Block 1 — LangChain Core                    ✅ Done
    │
    │  Module 1: Models and Providers        ✅
    │  Module 2: Tools and Tool Calling      ✅
    │  Module 3: Agents (create_agent)       ✅
    │  Module 4: Middleware and Customization ✅
    │
    ▼
Block 2 — LangGraph Fundamentals            ✅ Done
    │
    │  Module 5: Introduction to LangGraph   ✅
    │  Module 6: Functional API              ✅
    │  Module 7: Advanced Flows              ✅
    │
    ▼
Block 3 — Advanced LangGraph
    │
    │  Module 8: Memory and Persistence      ✅ Done
    │  Module 9: Human-in-the-Loop           ← YOU ARE HERE
    │  Module 10: Multi-Agent Systems        🔒 Next
    │
    ▼
Block 4 — Production                        🔒
    │
    │  Module 11: Deep Agents                🔒
    │  Module 12: LangSmith and Production   🔒

Block 3 turns your building tools into enterprise capabilities. Module 8 gave you persistence (checkpointing, memory, multi-user). This module adds human oversight. Module 10 will scale up to multiple coordinated agents.


The bridge from Module 8

What you already have

Your Research Agent v3 is a persistent system:

  • ✅ Checkpointing with MemorySaver: state is saved after every node
  • ✅ Crash recovery: if the process gets interrupted, it resumes from the last checkpoint
  • ✅ Time-travel debugging: you can navigate the state history and create forks
  • ✅ Long-term memory: it remembers user preferences across sessions
  • ✅ Multi-user: each thread_id has its own isolated context

The missing question

Your agent remembers and persists. But should it act on everything autonomously?

Your agent receives: "Research AI trends in finance"

With persistence (M8):
  ✅ Saves progress step by step
  ✅ Can resume if interrupted
  ✅ Remembers previous research

Without oversight (so far):
  ❌ Calls paid APIs without asking
  ❌ Decides to send emails without approval
  ❌ Runs destructive queries "to optimize"
  ❌ Follows a wrong direction with no way for you to correct it

Module 8's checkpointing is the technical prerequisite for HITL. For an agent to pause and wait for human approval, it needs to save its state in a checkpoint, wait indefinitely, and then resume exactly where it paused. Without persistence, the agent can't "remember" where it was when you paused it.


Why not everything should be autonomous

Example 1: The expensive action

Agent: "To research this thoroughly, I'm going to query these APIs:"
  - Google Scholar API: $0.10/query × 50 queries = $5.00
  - Patent API: $0.25/query × 20 queries = $5.00
  - News API Premium: $0.05/query × 100 queries = $5.00
  
  Estimated total: $15.00

Without HITL: The agent runs everything. You get the bill.
With HITL: "Proceed with $15 in API calls? [yes/no/reduce queries]"
  → User: "Cut it down to 10 queries per source"
  → Total: $4.00 — same useful result, 73% less cost

Example 2: The irreversible action

Agent: "I found duplicate data in the results table."
  → Autonomous decision: DELETE FROM research_results WHERE is_duplicate = true
  
Without HITL: 200 records deleted. Some weren't actually duplicates.
With HITL: "I found 200 possible duplicates. Delete them? [yes/no/review list]"
  → User: "Show me the first 10"
  → User reviews: "These 3 aren't duplicates. Delete the other 197."

Example 3: The action with external impact

Agent: "Research complete. I'm going to send the report to the team."
  → Emails 15 people with partially incorrect information
  
Without HITL: The team gets bad data. You lose credibility.
With HITL: "Report ready. Send it to the team? [yes/no/edit first]"
  → User reviews: "The Q3 figure is wrong. I'll fix it and then send."

The pattern is clear: expensive, irreversible, or high-impact actions need oversight. Cheap, reversible, low-impact actions can be autonomous.


The autonomy spectrum

Agents aren't a binary of "autonomous" or "supervised." They live on a spectrum:

Fully                  Mostly                  Selective            Fully
autonomous             autonomous              oversight            supervised
    │                      │                        │                     │
    ▼                      ▼                        ▼                     ▼
No interrupts.         Only interrupts          Interrupts before     Interrupts at
Decides and acts       for destructive          expensive, external,  EVERY step.
without asking.        or high-cost             or permanent-data     Asks permission
                       actions.                 actions.              for everything.
                                                
    
    ⚠️ Dangerous          ← Most production            ⚠️ Useless
    in production           agents live here             (why even have
                                                          an agent?)

Too many interrupts make the agent useless — if it asks permission for every web search, the user would have been faster doing it by hand. Zero interrupts make it dangerous — the agent acts unsupervised on things that can have real consequences.

The sweet spot: interrupt on expensive, irreversible, or high-impact actions. Everything else, autonomous.

Criteria for deciding what to interrupt

CriterionAutonomousNeeds approval
CostFree or penniesMore than $1 per operation
ReversibilityEasily reversibleHard or impossible to undo
External impactOnly affects the agentAffects people, systems, or data
Confidence in the dataVerified dataUncertain or partial data
FrequencyRoutine operationFirst time or unusual case

HITL patterns: it isn't just approve or reject

HITL isn't a "yes/no" checkbox. The user has several ways to step in:

1. Approval Gate — ask permission before acting

The agent plans an action, shows it to the user, and waits for approval before executing.

Agent: "I'm going to call the Google Scholar API (cost: $0.50). Proceed?"
Options: [Yes] [No] [Reduce scope]

2. Review & Edit — review and correct before continuing

The agent produces a partial result and the user reviews it before the agent moves on.

Agent: "I found these 5 relevant papers:"
  1. "RAG for Healthcare" (2025) — relevance: high
  2. "Vector Search Optimization" (2024) — relevance: medium
  ...
Options: [Continue with all] [Drop #2 and #4] [Add a criterion]

3. Guided Execution — redirect the research

The user watches the progress and changes the agent's direction midway.

Agent: "Processed 3 of 5 sources. Findings so far center on RAG."
User: "Focus more on fine-tuning, ignore the RAG results."
Agent: Adjusts the strategy → continues with the new focus

4. State Edit — fix data directly

The user edits the agent's internal state to correct wrong information.

Agent: "According to my data, TechCorp's CEO is John Smith."
User: "Wrong. The current CEO is Jane Doe (changed in January 2026)."
Agent: Updates its state → continues with the correct information

These four patterns cover most HITL scenarios. In this module's technical capsules you'll implement them one by one.


The prerequisite: persistence

All of HITL's mechanics depend on a concept you already know: checkpointing.

HITL flow:

1. The graph runs nodes normally
2. A node calls interrupt("message for the human")
3. The graph PAUSES — state is saved in a checkpoint
4. The human sees the message and makes a decision
   (this can take seconds, minutes, or days)
5. The human sends their answer with Command(resume=value)
6. The graph RESUMES from the checkpoint — the node receives the answer
7. Execution continues normally

Step 3 is the key: without a checkpointer, state is lost when the graph pauses. The agent can't wait for the human if it has nowhere to save its progress. That's why Module 8 comes before this one — you need working persistence for HITL to make any sense.

from langgraph.checkpoint.memory import MemorySaver

checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)

If you already have this line in your code (and you do, since M8), you're ready for HITL.


Module map

#CapsuleWhat you'll learnType
01Introduction (this one)Why agents need oversight, the autonomy spectrum, HITL patternsIntro
02Interrupts: pausing executioninterrupt(), Command(resume=), the pause/resume flow, the full UXTechnical
03Approval gates: validate before actingApproval before expensive actions, conditional routing after approvalTechnical
04Review & edit: reviewing and fixing stateupdate_state(), editing data mid-execution, correcting the agentTechnical
05Feedback loops: redirecting the agentFeedback during execution, changing direction, guided executionTechnical
06UX design for HITLWhat to interrupt, what not to, how not to frustrate the user, trust levelsDesign
07Interrupt rules and anti-patternsIdempotency, node re-execution, try/except, side effectsTechnical
08Project: Research Agent with oversightResearch Agent v4: approval gates + review + editable state + feedbackProject

Learning flow

You start with interrupts (capsule 02) — the fundamental mechanism for pausing and resuming a graph. It's the foundation for everything else. Then you implement approval gates (capsule 03) — the most common pattern: asking permission before expensive or irreversible actions. With that down, you learn review & edit (capsule 04) — the user reviews partial results and corrects the agent's state. Then you add feedback loops (capsule 05) — the user redirects the research midway. Capsule 06 (UX design) teaches you to decide what to interrupt and when — the most important design decision in HITL. Capsule 07 (rules and anti-patterns) covers the technical traps you need to avoid. Finally, you pull it all together in Research Agent v4 (capsule 08).

The progression is: mechanism → approval → review → feedback → design → rules → project.


Connection to the project

Research Agent v4: the supervised agent

Your Research Agent takes a big step forward in this module:

v1 (Module 6): Working but fragile
    ↓
v2 (Module 7): Robust (retry, branching, error handling)
    ↓
v3 (Module 8): Persistent (checkpointing, memory, multi-user)
    ↓
v4 (This module): Supervised
    │
    │  + Approval gate before paid APIs
    │  + Review of the research plan before executing
    │  + Feedback to redirect the research midway
    │  + State edit to fix data before the final report
    │
    ▼
v5 (Module 10): + Multi-agent (coordinated specialists)

After this module, your Research Agent isn't "fire and forget." It's a supervised agent that:

  1. Presents its search plan and waits for approval
  2. Asks permission before calling APIs that cost money
  3. Lets you fix incorrect data before generating the report
  4. Accepts redirection if the research is going the wrong way

The proof of success: you kick off a research run, the agent tells you "I plan to search 3 sources (estimated cost: $0.30), proceed?", you answer "yes, but only 2 sources", and the agent adjusts its plan and continues.


Connection to Module 10: Multi-Agent Systems

With a single agent, oversight is straightforward: you approve or reject. But what happens when you have 5 agents?

Researcher Agent: searches for information (low risk → autonomous)
Analyst Agent: processes data (low risk → autonomous)
Writer Agent: generates reports (medium risk → review before publishing)
Communicator Agent: sends emails (high risk → approval required)
DBA Agent: modifies the database (high risk → approval required)

Who approves what? Does the human approve each agent individually? Can a supervisor agent approve the others? How do you keep the human from drowning in 20 simultaneous approvals?

Those questions belong to Module 10. What you need to know now: the HITL patterns you learn in this module (approval gates, review & edit, feedback) are the same ones you'll use in multi-agent — just applied at the system level instead of at the individual node level.


What this module does NOT cover

  • Multi-agent supervision — How to coordinate approvals across multiple agents is covered in Module 10. Here we work with a single supervised agent.
  • UI/Frontend for HITL — We simulate the human interaction in the terminal. Building a web interface with approval buttons is a deployment topic (Module 12).
  • Deploying agents with HITL — How to deploy a supervised agent with webhooks, APIs, or LangGraph Cloud belongs to Module 12.
  • Security and authentication — Who can approve what, user roles, and permissions are production topics, not HITL topics.
  • Interrupts in advanced streaming — We cover the basic interrupt/resume flow. Streaming with asynchronous HITL is an advanced deployment pattern.

Technical setup

Prerequisites

  • Module 8 completed — you have a Research Agent v3 with checkpointing, long-term memory, and multi-user support
  • Python 3.11+ installed
  • ✅ At least one API key from a provider (OpenAI recommended)
  • A working checkpointer — you've been using MemorySaver since M8

Installation

You don't need any new packages. Everything you need was installed back in Module 8:

pip install langgraph langchain-openai python-dotenv

Check that the HITL imports work:

from langgraph.types import interrupt, Command
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, MessagesState, START, END

print("interrupt available")
print("Command available")
print("MemorySaver available")
# Expected output:
# interrupt available
# Command available
# MemorySaver available

Environment variables

Your .env from Module 8 still works:

# .env
OPENAI_API_KEY=sk-...

Quick check: a basic interrupt

Run this script to verify the interrupt mechanism works:

from langgraph.types import interrupt, Command
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, START, END
from typing import TypedDict


class SimpleState(TypedDict):
    task: str
    status: str
    human_decision: str


def plan_node(state: SimpleState) -> dict:
    return {"status": "plan_ready"}


def approval_node(state: SimpleState) -> dict:
    decision = interrupt(f"Do you approve the task '{state['task']}'? [yes/no]")
    return {"human_decision": decision, "status": "decision_received"}


def execute_node(state: SimpleState) -> dict:
    if state["human_decision"] == "yes":
        return {"status": "completed"}
    return {"status": "cancelled"}


builder = StateGraph(SimpleState)
builder.add_node("plan", plan_node)
builder.add_node("approval", approval_node)
builder.add_node("execute", execute_node)
builder.add_edge(START, "plan")
builder.add_edge("plan", "approval")
builder.add_edge("approval", "execute")
builder.add_edge("execute", END)

checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)

config = {"configurable": {"thread_id": "verify_hitl_001"}}

result = graph.invoke(
    {"task": "Research RAG", "status": "", "human_decision": ""},
    config
)
print(f"Status: {result['status']}")
print(f"Interrupt: {result.get('__interrupt__', 'none')}")

result = graph.invoke(Command(resume="yes"), config)
print(f"Final status: {result['status']}")
print(f"Human decision: {result['human_decision']}")
# Expected output:
# Status: plan_ready
# Interrupt: [Interrupt(value="Do you approve the task 'Research RAG'? [yes/no]", ...)]
# Final status: completed
# Human decision: yes

If you see "Final status: completed" and "Human decision: yes", the HITL mechanism is working correctly. The graph paused at approval_node, waited for your decision, and continued.


Signs of success

By the end of this module, you'll know it worked if:

  • ✅ You can pause a graph with interrupt() and resume it with Command(resume=) — the basic flow works
  • ✅ You implement approval gates that ask permission before expensive or irreversible actions
  • ✅ The user can review partial results and edit the agent's state before continuing
  • ✅ The user can redirect the research midway with feedback
  • ✅ You know how to decide what to interrupt and what to leave autonomous without paralyzing the agent with too many pauses
  • ✅ Your Research Agent v4 presents a plan, asks for approval, and accepts corrections

Self-assessment test

If you can answer these questions, you're on the right track:

  1. Why does an interrupt require a configured checkpointer?
  2. What's the difference between an approval gate and a review & edit?
  3. What happens when the node containing interrupt() resumes — does it run from the top of the node, or from where it paused?
  4. If your agent interrupts 15 times during a 5-minute research run, is that good design? Why?
  5. How do you decide whether an action should be autonomous or needs approval?

Summary

  • Your Research Agent v3 is persistent, but it acts on everything without asking. Expensive, irreversible, or high-impact actions need human oversight. HITL isn't a safety patch — it's a design decision
  • The autonomy spectrum runs from fully autonomous (dangerous) to fully supervised (useless). The sweet spot: interrupt on expensive, irreversible, or high-impact actions. Everything else, autonomous
  • HITL isn't just approve/reject. The user can: approve, reject, approve with modifications, ask for more info, redirect the task, or edit state directly. Four patterns: approval gate, review & edit, guided execution, state edit
  • Persistence is a technical prerequisite. interrupt() pauses the graph and saves state in a checkpoint. Without a checkpointer, state is lost during the pause and the agent can't resume. All of LangGraph's HITL depends on Module 8's checkpointing
  • Research Agent v4 adds: approval before paid APIs, review of the research plan, feedback to redirect, and state edit to fix data
  • This module sets up multi-agent (M10). When you have 5 agents, the question changes: who approves what? The patterns are the same, but applied at the system level

Additional resources

  1. LangGraph — Human-in-the-Loop — Official HITL documentation in LangGraph: interrupts, approval patterns, state editing
  2. LangGraph — Interrupts — Complete reference for interrupt() and Command(resume=), rules, and anti-patterns
  3. How to add human-in-the-loop — Practical step-by-step guide to implementing HITL
  4. How to edit graph state — How to let the human edit the agent's state during a pause
  5. How to review tool calls — Pattern for reviewing and approving tool calls before they run
  6. LangGraph — Persistence (prerequisite) — Refresher on the checkpointing system that makes HITL possible

Module 9 — LangChain & LangGraph: From Chains to Agents

Next capsule: Interrupts: Pausing Execution — you'll learn how interrupt() pauses your graph, how Command(resume=) resumes it, and how to simulate the full experience of a human interacting with an agent.