Human in the Loop for LangChain Agents: A Practical Guide
Add human approval to LangChain and LangGraph automation. A practical guide for your IT provider, with business rules, saved state and reviewer handoffs.
· 5 min read · by the Yoonet team · Updated

Educational example: the reviewer endpoint below is a non-working placeholder for a service your provider would need to implement. It is not a hitl.ph API. Use our setup kit to prepare a workflow and rehearse locally.
If your business uses a LangChain or LangGraph automation, this guide explains how a person can check an action before it goes ahead. A refund is the example, but the same question comes up with cancellations, customer-record changes and other decisions.
The code is for your IT or automation provider. As the business owner, your part is to agree which actions need approval, who can approve them and what happens if nobody answers. These are integration sketches with application-specific functions; your provider needs to adapt and test them against the versions and API contract you use.
Step 1 — Pause the action that needs approval
In this example, interrupt() pauses the refund action and supplies the information a reviewer needs. The automation can prepare the request, but the refund function only proceeds after a decision.
from langchain.tools import tool
from langgraph.types import interrupt
@tool
def issue_refund(order_id: str, amount: float) -> str:
"""Issue a refund to a customer."""
# Pause the graph here. The payload surfaces to whatever is
# driving the graph; execution stops until someone resumes it.
decision = interrupt({
"action": "issue_refund",
"order_id": order_id,
"amount": amount,
"question": "Approve this refund?",
})
if decision.get("approved"):
do_refund(order_id, amount) # your implementation
return f"Refunded {amount} on {order_id}"
return f"Refund declined: {decision.get('reason', 'no reason given')}"Write the question for the person reading it. Include the order, amount, relevant policy and reason for the request. An approver should not need to search through a technical log to understand what is being asked.
Step 2 — Save the work while it waits
A checkpointer saves the state of the workflow so it can be resumed later. The thread_id identifies the particular case. The example below shows a stored run and a later response using Command.
import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.types import Command
checkpointer = SqliteSaver(sqlite3.connect("agent.db", check_same_thread=False))
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "case-1042"}}
run = graph.invoke({"messages": [("user", "Refund order 1042, it arrived broken")]}, config)
# The graph is now parked at the interrupt. Inspect what it wants:
state = graph.get_state(config)
print(state.tasks[0].interrupts) # -> ({'action': 'issue_refund', 'amount': ...},)
# ...time passes; a human answers...
graph.invoke(Command(resume={"approved": True}), config)For the business, this means a person does not need to answer immediately for the case to remain available. Your provider still needs to choose suitable storage, access controls and a way of finding work that has waited too long.
Step 3 — Send the request to someone responsible for it
An alert only helps if someone has time and responsibility to answer it. Agree the working hours, cover and the information needed. The four human in the loop patterns explain when an approval should become a fuller handoff.
You can assign reviews to your own team or discuss a managed arrangement with hitl.ph. The following sketch shows how your provider might pass a pending decision to a reviewer through an API.
import requests
REVIEWER = "https://reviewer.example.invalid/v1"
HEADERS = {"Authorization": f"Bearer {REVIEWER_API_KEY}"}
def send_to_reviewer(interrupt_payload: dict, thread_id: str) -> str:
"""Turn a LangGraph interrupt into a task a real person will own."""
r = requests.post(f"{REVIEWER}/tasks", headers=HEADERS, json={
"type": "decision.own",
"brief": (
f"An automation wants to run {interrupt_payload['action']} "
f"with {interrupt_payload}. Approve or decline, with one line of reasoning."
),
"context": {"thread_id": thread_id},
"return": {"mode": "webhook", "url": "https://your-app.com/hooks/hitl"},
})
return r.json()["id"] # e.g. "task_8sJ2k0Lm"decision.own is the illustrative task name for an agreed decision. data.validate describes a record check, and document.review a document check. The task examples explain the work; agree a real connection and permissions before using any business data.
Step 4 — Return the answer to the right case
A webhook is a message sent back to your system. Your provider uses it to find the saved case and return the reviewer’s decision. The sketch below demonstrates that connection.
# Your webhook handler: the human answered, wake the graph up.
@app.post("/hooks/hitl")
def hitl_result(task: dict):
thread_id = task["context"]["thread_id"]
config = {"configurable": {"thread_id": thread_id}}
graph.invoke(
Command(resume={
"approved": task["result"]["decision"] == "approve",
"reason": task["result"]["reasoning"],
}),
config,
)
return {"ok": True}Test an approval, a decline, a delayed answer and a repeated message before using this with real refunds. The result should attach to the correct case, and a repeated response must not cause the action to happen twice. The FastAPI example shows a related service outline.
What to agree before it handles live work
- Approval rules. Specify the actions and limits that need review, rather than choosing an arbitrary percentage of work.
- Waiting times. Decide what happens when the reviewer is unavailable or the deadline passes.
- Authority. Check that only the right person can approve and that the approval matches the action shown.
- Records. Save the case, decision, reviewer and reason where your business can find them.
- Capacity. Agree the staffing and cover needed for your workload. Software cannot supply an absent reviewer.
Make this work in your business.
A short conversation about your business is a good place to start. Tell us what you have automated, what still needs checking and how often it happens.
You can email us directly at ai@yoonet.io.
Read next
- The Philippines' BPO Industry Is Growing Despite AI
- Cloudflare's Agent Stack: Email, Traces, and Now Wallets
- Human in the Loop Patterns for AI Agents
For your next step, explore the hitl.ph API docs, the business examples and your team options.