Skip to main content
Human-in-the-loop (HITL) pauses an agent before a tool call runs so a person can review it. The reviewer approves the call, edits its arguments, rejects it with feedback, or answers in place of the tool. Use it for actions that are hard to undo or that need sign-off, such as deleting records, sending email, or writing files. You add review with HumanInTheLoopMiddleware in LangChain, or with the interrupt_on parameter in Deep Agents. The middleware pauses in one of two modes:
  • Per-call: One pause for each tool call that needs review.
  • Batched: One pause per model turn, covering every tool call in that turn. Batched mode is the current default.
Per-call mode requires langchain>=1.4.6. Deep Agents uses batched mode.

Follow an approval from pause to resume

Every approval follows the same steps in either mode:
  1. The model proposes one or more tool calls.
  2. The middleware checks each call against your interrupt_on policy. Calls that need review pause the agent with an interrupt.
  3. The checkpointer saves the agent’s state, so the run can stop and resume later, even in another process.
  4. Your application shows the pending calls to a reviewer and collects a decision for each one.
  5. You resume the agent with a Command(resume=...). The middleware applies each decision, and the agent continues.
A decision is one of four types: The examples on this page use an agent with three tools. write_file allows every decision type. execute_sql allows only approve and reject. read_data runs without review. In one turn, the model calls all three.

Review each tool call separately

In per-call mode, each tool call that needs review pauses on its own as it starts: Each interrupt value describes one call and has type: "tool_approval". The execute_sql interrupt looks like this:
Each interrupt also carries a response_schema, a JSON schema of the answers that call accepts. For execute_sql, the schema allows approve and reject. For write_file, it also allows respond and edit. The edit branch includes write_file’s own argument schema. Clients can render a form from the schema instead of hardcoding the decision types. The LangChain guide shows the schema for each decision type. You answer each interrupt with one decision, keyed by the interrupt’s ID:
LangGraph rejects an invalid answer before saving it, so the reviewer can correct it and answer again. Interrupts you do not answer stay pending, which lets a reviewer answer them one at a time.

Review a whole turn at once

In batched mode, the middleware pauses once after the model responds, before any tool runs. read_data waits for the review too, even though it is not gated. One interrupt lists every gated call in action_requests, and each call’s allowed decisions in a separate review_configs list:
You answer with one list of decisions, in the same order as action_requests:
The interrupt has no response_schema. The middleware checks each decision only when it applies it, after LangGraph has saved the answer.

Compare batched-mode problems

Batched mode matches answers to calls by position, and checks each answer only after saving it. Each row shows a problem the example agent hits in batched mode, and how per-call mode avoids it:

Choose an interrupt mode

Use per-call mode for new agents. LangChain plans to make it the default in the next major release. Keep batched mode while your approval UI reads the batched format, such as one built with the useStream approval pattern. Per-call mode also changes how middleware order and multi-answer resumes behave. Before you switch, read Order middleware for per-call review and Resume each call by ID.

See also