LangChain LangGraph
Module 1 Module 2 Module 3
Module 3 · Middleware and HITL
1 3.2 Managing Msgs
2 3.3 HITL
3 3.4 Dyn Models
4 3.4 Dyn Prompts
5 3.4 Dyn Tools
6 3.5 Email Agent
SUM Module Summary
HomeLangChainModule 3 · Middleware and HITL3.3 HITL
📓 Notebook 📖 Explained
LANGCHAIN MODULE 3 · LESSON 3.3

Human-in-the-loop
Approve · Reject · Edit

This section adds manual approval to high-risk tools: reading emails can be executed automatically, sending emails must be paused and wait for human decision-making, and when resuming, the tool can be approved, rejected, or edited to adjust Use.

1 What problem HITL solves

Agent can adjust Use tools, but not all tools should be executed automatically. The risk of reading emails is low, while sending emails will have external effects, so it is more suitable to add manual approval.

toolWhether to interruptReason
read_emailnoOnly read the email content in state, no external side effects are generated Use
send_emailyesAn email will be sent, which is an external action that requires confirmation.
Core idea

Rather than having people involved in every step, Human-in-the-loop only pauses before high-risk actions are performed.

2 Define read-email and send-email tools

Notebook Define two tools first. read_email reads the original text of the email from state; send_email simulates sending an email.

Python
@tool
def read_email(runtime: ToolRuntime) -> str:
    """Read an email from the given address."""
    return runtime.state["email"]

@tool
def send_email(body: str) -> str:
    """Send an email to the given address with the given subject and body."""
    return "Email sent"
state_schema

The email body is placed in EmailState.email so tools can read it through runtime.state["email"].

3 Configure HumanInTheLoopMiddleware

The key Configure of HumanInTheLoopMiddleware is interrupt_on. It declares exactly which tool calls need to be paused.

Python
from langchain.agents.middleware import HumanInTheLoopMiddleware

class EmailState(AgentState):
    email: str

humanInTheLoopMiddleware = HumanInTheLoopMiddleware(
    interrupt_on={
        "read_email": False,
        "send_email": True,
    },
    description_prefix="Tool execution requires approval",
)

agent = create_agent(
    model=deepseek_model(),
    tools=[read_email, send_email],
    state_schema=EmailState,
    checkpointer=InMemorySaver(),
    middleware=[humanInTheLoopMiddleware],
)
A checkpointer is required

Use the same one after interruptionthread_idResume execution, so the Agent needs checkpointer to save the state when paused.

4 Trigger an interrupt

User requested "read emails and reply immediately". The Agent can read the email first, but when it prepares the Usesend_emailwill return when__interrupt__, waiting for human decision.

Python
config = {"configurable": {"thread_id": "1"}}

message = [HumanMessage(
    content="Please read my email and send a response immediately. Send the reply now in the same thread."
)]

response = agent.invoke(
    {
        "messages": message,
        "email": "Hi Seán, I'm going to be late for our meeting tomorrow. Can we reschedule? Best, John.",
    },
    config=config,
)

print(response)
Pause point

The interrupt occurs before the tool is executed. At this time, you can check the body of the email that the model intends to send, and then decide whether to allow it.

5 Three resume modes: approve, reject, edit

Passed in when resuming executionCommand(resume=...). The course shows three types of Decision: approving the original tool to adjust Use, rejecting and giving feedback to the model, and editing tool parameters before executing.

Python
from langgraph.types import Command

response = agent.invoke(
    Command(resume={"decisions": [{"type": "approve"}]}),
    config=config,
)

response = agent.invoke(
    Command(resume={
        "decisions": [{
            "type": "reject",
            "message": "No please sign off - Your merciful leader, Seán.",
        }]
    }),
    config=config,
)

response = agent.invoke(
    Command(resume={
        "decisions": [{
            "type": "edit",
            "edited_action": {
                "name": "send_email",
                "args": {"body": "This is the last straw, you're fired!"},
            },
        }]
    }),
    config=config,
)
DecisionMeaning
approveExecute tool adjustment Use according to the original plan of the model
rejectThe tool is not executed and the Put rejection Reason is returned to the Agent.
editExecute after modifying tool name or parameters

6 Lesson takeaway