langgraph-human-in-loop

LangGraph Human-in-the-Loop

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "langgraph-human-in-loop" with this command: npx skills add yonatangross/orchestkit/yonatangross-orchestkit-langgraph-human-in-loop

LangGraph Human-in-the-Loop

Pause workflows for human intervention and approval.

Basic Interrupt

workflow = StateGraph(State) workflow.add_node("draft", generate_draft) workflow.add_node("review", human_review) workflow.add_node("publish", publish_content)

Interrupt before review

app = workflow.compile(interrupt_before=["review"])

Step 1: Generate draft (stops at review)

config = {"configurable": {"thread_id": "doc-123"}} result = app.invoke({"topic": "AI"}, config=config)

Workflow pauses here

Dynamic interrupt() Function (2026 Best Practice)

Modern approach using interrupt() within node logic:

from langgraph.types import interrupt, Command

def approval_node(state: State): """Dynamic interrupt based on conditions.""" # Only interrupt for high-risk actions if state["risk_level"] == "high": response = interrupt({ "question": "High-risk action detected. Approve?", "action": state["proposed_action"], "risk_level": state["risk_level"], "details": state["action_details"] })

    if not response.get("approved"):
        return {"status": "rejected", "action": None}

# Low risk or approved - proceed
return {"status": "approved", "action": state["proposed_action"]}

Resume After Approval

Step 2: Human reviews and updates state

state = app.get_state(config) print(f"Draft: {state.values['draft']}")

Human decision

state.values["approved"] = True state.values["feedback"] = "Looks good" app.update_state(config, state.values)

Step 3: Resume workflow

result = app.invoke(None, config=config) # Continues to publish

Command(resume=) Pattern (2026 Best Practice)

from langgraph.types import Command

config = {"configurable": {"thread_id": "workflow-123"}}

Initial invoke - stops at interrupt

result = graph.invoke(initial_state, config)

Check for interrupt

if "interrupt" in result: interrupt_info = result["interrupt"][0].value print(f"Action: {interrupt_info['action']}") print(f"Question: {interrupt_info['question']}")

# Get user decision
user_response = {"approved": True, "feedback": "Looks good"}

# Resume with Command
final = graph.invoke(Command(resume=user_response), config)

Approval Gate Node

def approval_gate(state: WorkflowState) -> WorkflowState: """Check if human approved.""" if not state.get("human_reviewed"): # Will pause here due to interrupt_before return state

if state["approved"]:
    state["next"] = "publish"
else:
    state["next"] = "revise"

return state

workflow.add_node("approval_gate", approval_gate)

Pause before this node

app = workflow.compile(interrupt_before=["approval_gate"])

Feedback Loop Pattern

import uuid

async def run_with_feedback(initial_state: dict): """Run until human approves.""" config = {"configurable": {"thread_id": str(uuid.uuid4())}}

while True:
    # Run until interrupt
    result = app.invoke(initial_state, config=config)

    # Check for interrupt
    if "__interrupt__" not in result:
        return result  # Completed without interrupt

    interrupt_info = result["__interrupt__"][0].value
    print(f"Output: {interrupt_info.get('output', 'N/A')}")
    feedback = input("Approve? (yes/no/feedback): ")

    if feedback.lower() == "yes":
        return app.invoke(Command(resume={"approved": True}), config=config)
    elif feedback.lower() == "no":
        return {"status": "rejected"}
    else:
        # Incorporate feedback and retry
        initial_state = None
        result = app.invoke(
            Command(resume={"approved": False, "feedback": feedback}),
            config=config
        )

Input Validation Loop

from langgraph.types import interrupt

def get_valid_age(state: State): """Repeatedly prompt until valid input.""" prompt = "What is your age?"

while True:
    answer = interrupt(prompt)

    # Validate
    if isinstance(answer, int) and 0 < answer < 150:
        return {"age": answer}

    # Invalid - update prompt and continue
    prompt = f"'{answer}' is not valid. Please enter a number between 1 and 150."

API Integration

from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.post("/workflows/{workflow_id}/approve") async def approve_workflow(workflow_id: str, approved: bool, feedback: str = ""): """API endpoint for human approval.""" config = {"configurable": {"thread_id": workflow_id}}

try:
    state = langgraph_app.get_state(config)
except Exception:
    raise HTTPException(404, "Workflow not found")

# Update state with human decision
state.values["approved"] = approved
state.values["feedback"] = feedback
state.values["human_reviewed"] = True
langgraph_app.update_state(config, state.values)

# Resume workflow
result = langgraph_app.invoke(None, config=config)

return {"status": "completed", "result": result}

Multiple Approval Points

Interrupt at multiple points

app = workflow.compile( interrupt_before=["first_review", "final_review"] )

First review

result = app.invoke(initial_state, config=config)

... human approves first review ...

app.update_state(config, {"first_approved": True})

Continue to second review

result = app.invoke(None, config=config)

... human approves final review ...

app.update_state(config, {"final_approved": True})

Complete workflow

result = app.invoke(None, config=config)

Key Decisions

Decision Recommendation

Interrupt point Before critical nodes

Timeout 24-48h for human review

Notification Email/Slack when paused

Fallback Auto-reject after timeout

Critical Rules

DO:

  • Place side effects AFTER interrupt calls

  • Make pre-interrupt side effects idempotent (upsert vs create)

  • Keep interrupt call order consistent across executions

  • Pass simple, JSON-serializable values to interrupt()

DON'T:

  • Wrap interrupt in bare try/except (catches the interrupt exception)

  • Conditionally skip interrupt calls (breaks determinism)

  • Pass functions or class instances to interrupt()

  • Create non-idempotent records before interrupts (duplicates on resume)

Common Mistakes

  • No timeout (workflows hang forever)

  • No notification (humans don't know to review)

  • Losing checkpoint (can't resume)

  • No reject path (only approve works)

  • Wrapping interrupt() in try/except (breaks the mechanism)

  • Non-deterministic interrupt call order (breaks resumption)

Evaluations

See references/evaluations.md for test cases.

Related Skills

  • langgraph-checkpoints

  • Persist state across human review pauses

  • langgraph-routing

  • Route based on approval/rejection decisions

  • langgraph-tools

  • Add approval gates before dangerous tool execution

  • langgraph-supervisor

  • Human approval in supervisor routing

  • langgraph-streaming

  • Stream status while waiting for human input

  • api-design-framework

  • Design review API endpoints

Capability Details

interrupt-before

Keywords: interrupt, pause, stop, before, gate Solves:

  • How do I pause a workflow for approval?

  • Add human review before a step

  • Interrupt workflow execution

resume-workflow

Keywords: resume, continue, approve, proceed, update_state Solves:

  • How do I resume after human approval?

  • Continue workflow after review

  • Update state and proceed

approval-patterns

Keywords: approval, approve, reject, decision, gate Solves:

  • How do I implement approval workflows?

  • Add approval gate to pipeline

  • Handle approve/reject decisions

feedback-integration

Keywords: feedback, comment, review, notes, human input Solves:

  • How do I collect human feedback?

  • Integrate reviewer comments

  • Capture feedback in workflow state

interactive-supervision

Keywords: supervise, monitor, interactive, control, override Solves:

  • How do I supervise agent execution?

  • Add human oversight to agents

  • Override agent decisions

state-inspection

Keywords: get_state, inspect, view, current state, debug Solves:

  • How do I inspect workflow state?

  • View current state at interrupt

  • Debug paused workflows

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

responsive-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

domain-driven-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

dashboard-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

rag-retrieval

No summary provided by upstream source.

Repository SourceNeeds Review