<overview>
Middleware patterns for production LangChain agents:
- HumanInTheLoopMiddleware / humanInTheLoopMiddleware: Pause before dangerous tool calls for human approval
- Custom middleware: Intercept tool calls for error handling, logging, retry logic
- Command resume: Continue execution after human decisions (approve, edit, reject)
Requirements: Checkpointer + thread_id config for all HITL workflows.
</overview>
Human-in-the-Loop
<ex-basic-hitl-setup>
<python>
Set up an agent with HITL middleware that pauses before sending emails for approval.
</python>
<typescript>
Set up an agent with HITL that pauses before sending emails for human approval.
</typescript>
</ex-basic-hitl-setup>
<ex-running-with-interrupts>
<python>
Run the agent, detect an interrupt, then resume execution after human approval.
</python>
<typescript>
Run the agent, detect an interrupt, then resume execution after human approval.
</typescript>
</ex-running-with-interrupts>
<ex-editing-tool-arguments>
<python>
Edit the tool arguments before approving when the original values need correction.
</python>
<typescript>
Edit the tool arguments before approving when the original values need correction.
</typescript>
</ex-editing-tool-arguments>
<ex-rejecting-with-feedback>
<python>
Reject a tool call and provide feedback explaining why it was rejected.
</python>
</ex-rejecting-with-feedback>
<ex-multiple-tools-different-policies>
<python>
Configure different HITL policies for each tool based on risk level.
</python>
</ex-multiple-tools-different-policies>
<boundaries>
What You CAN Configure
- Which tools require approval (per-tool policies)
- Allowed decisions per tool (approve, edit, reject)
- Custom middleware hooks:
before_model,after_model,wrap_tool_call,before_agent,after_agent - Tool-specific middleware (apply only to certain tools)
</boundaries>
Custom Middleware Hooks
Six decorator hooks are available. Two patterns:
- Wrap hooks (
wrap_tool_call,wrap_model_call):(request, handler)— callhandler(request)to proceed, or return early to short-circuit. - Before/after hooks (
before_model,after_model,before_agent,after_agent):(state, runtime)— inspect or modify state. ReturnNoneor a dict of state updates.
<ex-wrap-tool-call>
<python>
@wrap_tool_call intercepts tool execution. Do NOT use yield — it creates a generator and causes NotImplementedError.
</python>
<typescript>
createMiddleware({ wrapToolCall }) intercepts tool execution.
</typescript>
</ex-wrap-tool-call>
<ex-before-after-hooks>
<python>
before_model / after_model / before_agent / after_agent all share (state, runtime) signature.
</python>
<typescript>
All before/after hooks share the same (state, runtime) signature via createMiddleware.
</typescript>
</ex-before-after-hooks>
<boundaries>
What You CANNOT Configure
- Interrupt after tool execution (must be before)
- Skip checkpointer requirement for HITL
</boundaries>
<fix-missing-checkpointer>
<python>
HITL middleware requires a checkpointer to persist state.
</python>
<typescript>
HITL requires a checkpointer to persist state.
</typescript>
</fix-missing-checkpointer>
<fix-no-thread-id>
<python>
Always provide thread_id when using HITL to track conversation state.
</python>
</fix-no-thread-id>
<fix-wrong-resume-syntax>
<python>
Use Command class to resume execution after an interrupt.
</python>
<typescript>
Use Command class to resume execution after an interrupt.
</typescript>
</fix-wrong-resume-syntax>


