
Langgraph Human In The Loop
Pause LangGraph agent runs for human approval and resume safely with persisted thread state.
Install
npx skills add https://github.com/langchain-ai/langchain-skills --skill langgraph-human-in-the-loopWhat is this skill?
- Documents interrupt() plus Command(resume=...) so paused nodes surface __interrupt__ payloads and resume with caller inp
- Lists three hard requirements: checkpointer, thread_id in configurable config, and JSON-serializable interrupt payloads
- Explains nodes restart from the top on resume—code before interrupt() runs again
- Covers approval and validation workflows for production human gates
- References a 4-tier error handling strategy for LangGraph HITL flows
Adoption & trust: 7.9k installs on skills.sh; 782 GitHub stars; 3/3 security scanners passed (skills.sh audits); trending (+100% hot-view momentum).
Recommended Skills
Microsoft Foundrymicrosoft/azure-skills
Azure Aimicrosoft/azure-skills
Azure Hosted Copilot Sdkmicrosoft/azure-skills
Lark Eventlarksuite/cli
Running Claude Code Via Litellm Copilotxixu-me/skills
Setup Matt Pocock Skillsmattpocock/skills
Journey fit
Primary fit
Human-in-the-loop wiring belongs in Build when you implement agent graphs that must stop for operator input before continuing. Agent-tooling is the shelf for LangGraph primitives like interrupt, Command(resume), and checkpointers—not generic backend CRUD.
Common Questions / FAQ
Is Langgraph Human In The Loop safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.
SKILL.md
READMESKILL.md - Langgraph Human In The Loop
<overview> LangGraph's human-in-the-loop patterns let you pause graph execution, surface data to users, and resume with their input: - **`interrupt(value)`** — pauses execution, surfaces a value to the caller - **`Command(resume=value)`** — resumes execution, providing the value back to `interrupt()` - **Checkpointer** — required to save state while paused - **Thread ID** — required to identify which paused execution to resume </overview> --- ## Requirements Three things are required for interrupts to work: 1. **Checkpointer** — compile with `checkpointer=InMemorySaver()` (dev) or `PostgresSaver` (prod) 2. **Thread ID** — pass `{"configurable": {"thread_id": "..."}}` to every `invoke`/`stream` call 3. **JSON-serializable payload** — the value passed to `interrupt()` must be JSON-serializable --- ## Basic Interrupt + Resume `interrupt(value)` pauses the graph. The value surfaces in the result under `__interrupt__`. `Command(resume=value)` resumes — the resume value becomes the return value of `interrupt()`. **Critical**: when the graph resumes, the node restarts from the **beginning** — all code before `interrupt()` re-runs. <ex-basic-interrupt-resume> <python> Pause execution for human review and resume with Command. ```python from langgraph.types import interrupt, Command from langgraph.checkpoint.memory import InMemorySaver from langgraph.graph import StateGraph, START, END from typing_extensions import TypedDict class State(TypedDict): approved: bool def approval_node(state: State): # Pause and ask for approval approved = interrupt("Do you approve this action?") # When resumed, Command(resume=...) returns that value here return {"approved": approved} checkpointer = InMemorySaver() graph = ( StateGraph(State) .add_node("approval", approval_node) .add_edge(START, "approval") .add_edge("approval", END) .compile(checkpointer=checkpointer) ) config = {"configurable": {"thread_id": "thread-1"}} # Initial run — hits interrupt and pauses result = graph.invoke({"approved": False}, config) print(result["__interrupt__"]) # [Interrupt(value='Do you approve this action?')] # Resume with the human's response result = graph.invoke(Command(resume=True), config) print(result["approved"]) # True ``` </python> <typescript> Pause execution for human review and resume with Command. ```typescript import { interrupt, Command, MemorySaver, StateGraph, StateSchema, START, END } from "@langchain/langgraph"; import { z } from "zod"; const State = new StateSchema({ approved: z.boolean().default(false), }); const approvalNode = async (state: typeof State.State) => { // Pause and ask for approval const approved = interrupt("Do you approve this action?"); // When resumed, Command({ resume }) returns that value here return { approved }; }; const checkpointer = new MemorySaver(); const graph = new StateGraph(State) .addNode("approval", approvalNode) .addEdge(START, "approval") .addEdge("approval", END) .compile({ checkpointer }); const config = { configurable: { thread_id: "thread-1" } }; // Initial run — hits interrupt and pauses let result = await graph.invoke({ approved: false }, config); console.log(result.__interrupt__); // [{ value: 'Do you approve this action?', ... }] // Resume with the human's response result = await graph.invoke(new Command({ resume: true }), config); console.log(result.approved); // true ``` </typescript> </ex-basic-interrupt-resume> --- ## Approval Workflow A common pattern: interrupt to show a draft, then route based on the human's decision. <ex-approval-workflow> <python> Interrupt for human review, then route to send or end based on the decision. ```pyth