Install any skill in seconds. Free to start, no credit card required.
Get Started Free →LangGraph human-in-the-loop patterns. Use when implementing approval workflows, manual review gates, user feedback integration, or interactive agent supervision.
.claude/skills/majiayu000-langgraph-human-in-loop/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | -38% | 0% |
| case-05 | ✗→✓ | ▲ Improved | -34% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 35% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 36% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 1% | 0% |
Pause workflows for human intervention and approval.
pythonworkflow = 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
python# 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
pythondef 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"])
pythonimport uuid_utils # pip install uuid-utils (UUID v7 for Python < 3.14) async def run_with_feedback(initial_state: dict): """Run until human approves.""" config = {"configurable": {"thread_id": str(uuid_utils.uuid7())}} while True: # Run until interrupt result = app.invoke(initial_state, config=config) # Get current state state = app.get_state(config) # Present to human print(f"Output: {state.values['output']}") feedback = input("Approve? (yes/no/feedback): ") if feedback.lower() == "yes": state.values["approved"] = True app.update_state(config, state.values) return app.invoke(None, config=config) elif feedback.lower() == "no": return {"status": "rejected"} else: # Incorporate feedback and retry state.values["feedback"] = feedback state.values["retry_count"] = state.values.get("retry_count", 0) + 1 app.update_state(config, state.values) initial_state = None # Resume from checkpoint
pythonfrom 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}
python# 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)
| Decision | Recommendation | |----------|----------------| | Interrupt point | Before critical nodes | | Timeout | 24-48h for human review | | Notification | Email/Slack when paused | | Fallback | Auto-reject after timeout |
langgraph-checkpoints - State persistencelanggraph-routing - Routing after approvalapi-design-framework - Review API designKeywords: interrupt, pause, stop, before, gate Solves:
Keywords: resume, continue, approve, proceed, update_state Solves:
Keywords: approval, approve, reject, decision, gate Solves:
Keywords: feedback, comment, review, notes, human input Solves:
Keywords: supervise, monitor, interactive, control, override Solves:
Keywords: get_state, inspect, view, current state, debug Solves:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 41,784 | 28,229 | -32% | 1 | 1 | 0% | 8,261 | 5,125 | -38% | 0 | 0 | — |
case-02 | pass→pass | 15,409 | 12,087 | -22% | 1 | 1 | 0% | 2,399 | 2,960 | +23% | 0 | 0 | — |
case-03 | pass→pass | 14,163 | 3,598 | -75% | 1 | 1 | 0% | 1,491 | 2,152 | +44% | 0 | 0 | — |
case-04 | pass→pass | 11,969 | 7,751 | -35% | 1 | 1 | 0% | 2,332 | 2,791 | +20% | 0 | 0 | — |
case-05 | fail→pass | 17,000 | 5,285 | -69% | 1 | 1 | 0% | 3,286 | 2,156 | -34% | 0 | 0 | — |
case-06 | fail→fail | 16,290 | 12,821 | -21% | 1 | 1 | 0% | 2,455 | 3,467 | +41% | 0 | 0 | — |
case-07 | fail→pass | 15,015 | 11,526 | -23% | 1 | 1 | 0% | 2,951 | 3,985 | +35% | 0 | 0 | — |
case-08 | fail→pass | 22,281 | 19,579 | -12% | 1 | 1 | 0% | 3,478 | 4,724 | +36% | 0 | 0 | — |
case-09 | fail→pass | 11,702 | 4,580 | -61% | 1 | 1 | 0% | 2,254 | 2,267 | +1% | 0 | 0 | — |
case-10 | pass→fail | 27,957 | 13,821 | -51% | 1 | 1 | 0% | 2,147 | 4,342 | +102% | 0 | 0 | — |
case-11 | fail→fail | 18,691 | 16,009 | -14% | 1 | 1 | 0% | 3,705 | 4,593 | +24% | 0 | 0 | — |
case-12 | pass→pass | 12,847 | 10,404 | -19% | 1 | 1 | 0% | 2,610 | 3,628 | +39% | 0 | 0 | — |
case-13 | fail→pass | 16,041 | 9,739 | -39% | 1 | 1 | 0% | 2,599 | 3,225 | +24% | 0 | 0 | — |
case-14 | pass→pass | 6,639 | 6,165 | -7% | 1 | 1 | 0% | 980 | 2,061 | +110% | 0 | 0 | — |
case-15 | pass→pass | 13,487 | 11,225 | -17% | 1 | 1 | 0% | 2,370 | 3,421 | +44% | 0 | 0 | — |
case-16 | pass→pass | 9,790 | 4,163 | -57% | 1 | 1 | 0% | 1,906 | 2,259 | +19% | 0 | 0 | — |
case-17 | pass→pass | 10,424 | 22,401 | +115% | 1 | 1 | 0% | 1,986 | 4,954 | +149% | 0 | 0 | — |
case-18 | pass→pass | 17,284 | 9,514 | -45% | 1 | 1 | 0% | 3,540 | 3,466 | -2% | 0 | 0 | — |
case-19 | fail→pass | 17,219 | 17,798 | +3% | 1 | 1 | 0% | 2,770 | 4,504 | +63% | 0 | 0 | — |
case-20 | pass→pass | 16,528 | 13,100 | -21% | 1 | 1 | 0% | 3,154 | 4,019 | +27% | 0 | 0 | — |
case-21 | pass→pass | 11,599 | 10,787 | -7% | 1 | 1 | 0% | 2,241 | 3,608 | +61% | 0 | 0 | — |
case-22 | pass→pass | 8,493 | 7,398 | -13% | 1 | 1 | 0% | 1,657 | 2,963 | +79% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.