approve), modified before running (edit), rejected with feedback (reject), or responded to directly (respond) for “ask user” style tools.
Interrupt decision types
The middleware defines four built-in ways a human can respond to an interrupt:
The available decision types for each tool depend on the policy you configure in
interrupt_on.
When multiple tool calls are paused at the same time, each action requires a separate decision.
In the default batched mode, provide decisions in the same order as the actions appear in the interrupt request. In per-call mode, each tool call has its own interrupt and takes one decision.
Use reject when the human is denying the requested action. Use respond only when the human is acting as the tool, such as answering an ask_user prompt. Do not use respond to deny side-effecting tools, because its message is treated as a successful tool result.
Configuring interrupts
To use HITL, add the middleware to the agent’smiddleware list when creating the agent.
You configure it with a mapping of tool actions to the decision types that are allowed for each action. The middleware will interrupt execution when a tool call matches an action in the mapping.
This example uses the default batched mode. To pause once per tool call, see Set the interrupt mode.
You must configure a checkpointer to persist the graph state across interrupts.
In production, use a persistent checkpointer like
AsyncPostgresSaver or MongoDBSaver. For testing or prototyping, use InMemorySaver.When invoking the agent, pass a config that includes the thread ID to associate execution with a conversation thread.
See the LangGraph interrupts documentation for details.Configuration options
Configuration options
dict
required
Mapping of tool names to approval configs. Values can be
True (interrupt with default config), False (auto-approve), or an InterruptOnConfig object.string
default:"Tool execution requires approval"
Prefix for action request descriptions
string
default:"batched"
How the agent pauses for review.
"batched" raises one interrupt per model turn, answered with an ordered list. "per_call" raises one interrupt per gated call, answered by interrupt ID. "per_call" requires langchain>=1.4.6. See Set the interrupt mode.string | None
Text added before the result of a call that a reviewer replaced with an
edit decision, so the model knows the call changed. Defaults to a note that a human reviewer replaced the call. Pass None to add nothing.InterruptOnConfig options:list[string]
List of allowed decisions:
'approve', 'edit', 'reject', or 'respond'string | callable
Static string or callable function for custom description
callable
Optional predicate that receives a ToolCallRequest and returns
True to interrupt or False to auto-approve. Use it to gate interrupts on a call’s arguments. Requires langchain>=1.3.3.Conditional interrupts
By default, every tool call listed ininterrupt_on pauses for review. To pause only some calls, add a when predicate to a tool’s InterruptOnConfig. The predicate receives a ToolCallRequest and returns True to interrupt or False to auto-approve, so you can gate on the tool’s arguments.
Conditional interrupts require
langchain>=1.3.3.when predicate returns False, the call runs without interrupting. When it returns True, or when you omit when, the call pauses as usual. Calls that evaluate to False never pause, so a reviewer only sees the actions that need a decision.
The predicate runs again when the agent resumes, so it must return the same result for the same call. If it returns False on resume, the tool runs and the reviewer’s answer is ignored, even a reject. In batched mode, if the result changes for only some of the calls, the middleware raises a ValueError.
Set the interrupt mode
The middleware pauses in one of two modes, set withinterrupt_mode:
"per_call": Each tool call that needs review raises its own interrupt as it starts. Each interrupt carries aresponse_schemaof the answers it accepts. You answer it with one decision, keyed by the interrupt ID."batched"(default): One interrupt per model turn lists every call that needs review. You answer with a list of decisions in the same order.
Pause once per tool call
Per-call interrupts require
langchain>=1.4.6.interrupt_mode="per_call" to review each tool call separately. This agent gates write_file and execute_sql, and lets read_data run without review:
Read the interrupts
When the model calls all three tools in one turn,read_data runs right away. The two gated calls each raise an interrupt:
type: "tool_approval", so a client can tell it apart from other interrupts. The tool_call_id, name, and args fields identify the call under review.
Read the response schema
Each interrupt also carries aresponse_schema: a JSON schema with one branch per decision the tool allows, matched on the decision’s type. The execute_sql schema has approve and reject branches. The write_file schema has all four. For a tool that allows one decision, response_schema is that decision’s schema alone, with no oneOf.
Each tab shows one write_file branch, an answer that matches it, and the ToolMessage the model receives. For readability, the branches below expand $ref definitions in place and omit the title and description fields.
- approve
- edit
- reject
- respond
Run the call as proposed.
Schema branch
Answer
Model receives
Resume each call by ID
Answer each interrupt with one decision, keyed by its ID:Correct an invalid answer
LangGraph checks each answer against the interrupt’sresponse_schema before it saves the answer. An invalid answer raises a ValidationError and nothing is saved, so you can send a corrected answer to the same interrupt ID. Here, the edit leaves out content:
response_schema shows the arguments, but LangGraph does not check edits against them.
Order middleware for per-call review
In per-call mode, the review happens as the tool call starts, inside the tool-call middleware chain:- List
HumanInTheLoopMiddlewarebefore tool retry or error-handling middleware, such as ToolRetryMiddleware and ToolErrorMiddleware, so it wraps them. Otherwise a retried call asks the reviewer again, and an invalid answer is retried or returned to the model as a tool error. - When the agent resumes, middleware listed before
HumanInTheLoopMiddlewareruns again up to the pause, so it must not have side effects before it calls its handler. The tool runs only after the answer. - A
whenpredicate runs as each call starts, so the ToolCallRequest it receives includes the tool.
Responding to interrupts
When you invoke the agent, it runs until it either completes or an interrupt is raised. An interrupt is triggered when a tool call matches the policy you configured ininterrupt_on. With version="v2", the result is a GraphOutput with an interrupts attribute containing the actions that require review. You can then present those actions to a reviewer and resume execution once decisions are provided.
The examples in this section and the decision types below use the default batched mode. For per-call mode, see Pause once per tool call.
Decision types
- approve
- edit
- reject
- respond
Use
approve to approve the tool call as-is and execute it without changes.Multiple decisions
When multiple actions are under review, provide a decision for each action in the same order as they appear in the interrupt:Streaming with human-in-the-loop
You can stream real-time updates while the agent runs and handles interrupts usingstream_events(). Use stream.messages to stream LLM tokens and stream.values to check agent state snapshots for interrupts.
stream.interrupts holds one interrupt per gated tool call. Resume with a dict keyed by interrupt ID:
Execution lifecycle
The middleware defines anafter_model hook that runs after the model generates a response but before any tool calls are executed:
- The agent invokes the model to generate a response.
- The middleware inspects the response for tool calls.
- If any calls require human input, the middleware builds a HITLRequest with
action_requestsandreview_configsand calls interrupt. - The agent waits for human decisions.
- Based on the
HITLResponsedecisions, the middleware executes approved or edited calls, synthesizes ToolMessage’s for rejected calls, returns human replies directly as ToolMessage’s forresponddecisions, and resumes execution.
after_model hook does nothing. The middleware instead pauses each gated call as it runs, with one interrupt per call, and runs the tool once that call’s decision arrives.
Custom HITL logic
For more specialized workflows, you can build custom HITL logic directly using the interrupt primitive and middleware abstraction. Review the execution lifecycle above to understand how to integrate interrupts into the agent’s operation.Connect these docs to your agent of choice via MCP for real-time answers.

