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:
- Calls an academic papers API that charges $0.10 per query — 50 times
- Finds a relevant result and decides to email it to the team
- To "clean up" the temporary data, runs a
DELETEon 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
| Criterion | Autonomous | Needs approval |
|---|---|---|
| Cost | Free or pennies | More than $1 per operation |
| Reversibility | Easily reversible | Hard or impossible to undo |
| External impact | Only affects the agent | Affects people, systems, or data |
| Confidence in the data | Verified data | Uncertain or partial data |
| Frequency | Routine operation | First 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
| # | Capsule | What you'll learn | Type |
|---|---|---|---|
| 01 | Introduction (this one) | Why agents need oversight, the autonomy spectrum, HITL patterns | Intro |
| 02 | Interrupts: pausing execution | interrupt(), Command(resume=), the pause/resume flow, the full UX | Technical |
| 03 | Approval gates: validate before acting | Approval before expensive actions, conditional routing after approval | Technical |
| 04 | Review & edit: reviewing and fixing state | update_state(), editing data mid-execution, correcting the agent | Technical |
| 05 | Feedback loops: redirecting the agent | Feedback during execution, changing direction, guided execution | Technical |
| 06 | UX design for HITL | What to interrupt, what not to, how not to frustrate the user, trust levels | Design |
| 07 | Interrupt rules and anti-patterns | Idempotency, node re-execution, try/except, side effects | Technical |
| 08 | Project: Research Agent with oversight | Research Agent v4: approval gates + review + editable state + feedback | Project |
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:
- Presents its search plan and waits for approval
- Asks permission before calling APIs that cost money
- Lets you fix incorrect data before generating the report
- 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 withCommand(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:
- Why does an interrupt require a configured checkpointer?
- What's the difference between an approval gate and a review & edit?
- What happens when the node containing
interrupt()resumes — does it run from the top of the node, or from where it paused? - If your agent interrupts 15 times during a 5-minute research run, is that good design? Why?
- 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
- LangGraph — Human-in-the-Loop — Official HITL documentation in LangGraph: interrupts, approval patterns, state editing
- LangGraph — Interrupts — Complete reference for
interrupt()andCommand(resume=), rules, and anti-patterns - How to add human-in-the-loop — Practical step-by-step guide to implementing HITL
- How to edit graph state — How to let the human edit the agent's state during a pause
- How to review tool calls — Pattern for reviewing and approving tool calls before they run
- 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.