LangChainLangChain 1.4 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
46 small wins to finish your pathNext lesson →

The desk's guardrails

The desk's guards are four pieces of middleware around the model: they block passwords, mask card numbers, cap the loop, and pause refunds for approval.

Last updated: 27 Sep, 2026 · LangChain 1.4

The tools from lesson 33 answer questions. What makes it a desk you could put in front of customers is the middleware around the model, plus a checkpointer that keeps each thread.

The before_agent guardrail

python
from langchain.agents.middleware import before_agent

@before_agent(can_jump_to=["end"])
def guard(state, runtime):
    if bad(state["messages"][-1]):
        return {"messages": [answer], "jump_to": "end"}   # answer and stop

The password guardrail

The guardrail ends the run before a model call when someone types the word password.

python
from langchain.agents.middleware import before_agent
from langchain.messages import AIMessage


@before_agent(can_jump_to=["end"])
def no_passwords(state, runtime):
    if "password" in state["messages"][-1].text.lower():
        answer = AIMessage("I cannot help with passwords. Please use the reset link.")
        return {"messages": [answer], "jump_to": "end"}   # end before the model

The desk's imports

The rest are LangChain's own. Start with the imports the desk needs.

python
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware, ModelCallLimitMiddleware, PIIMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from desk_model import DeskModel
from guard import no_passwords
from search import search_policies
from tools import Customer, lookup_order, refund_order

Passing the middleware as a list

Pass the middleware as a list. It reads top to bottom as the order things happen in, and a checkpointer keeps each thread.

python
agent = create_agent(
    DeskModel(),
    tools=[lookup_order, refund_order, search_policies],
    context_schema=Customer,
    middleware=[
        no_passwords,
        PIIMiddleware("credit_card", strategy="mask"),
        ModelCallLimitMiddleware(run_limit=6),
        HumanInTheLoopMiddleware(interrupt_on={"refund_order": True}),
    ],
    checkpointer=InMemorySaver(),
)
  • no_passwords answers and ends the run, so a password question never reaches the model.
  • PIIMiddleware masks a card number in the message before the model or the checkpointer sees it.
  • ModelCallLimitMiddleware caps one run at six model calls, so a loop stops on its own.
  • HumanInTheLoopMiddleware pauses on refund_order and waits to be resumed.
  • InMemorySaver keeps each thread, so a customer's next message continues the last one.

Sending one message

A refund pauses the run, so the caller has to be able to answer. say sends one message as one customer, and approves anything the desk holds.

python
from langgraph.types import Command
from desk import Customer, agent


def say(who, text, thread):
    config = {"configurable": {"thread_id": thread}}
    result = agent.invoke({"messages": [{"role": "user", "content": text}]}, config,
                          context=Customer(who), version="v2")
    if result.interrupts:
        print(f"{who}: {text}\n  paused for approval: {result.interrupts[0].value['action_requests'][0]['args']}")
        result = agent.invoke(Command(resume={"decisions": [{"type": "approve"}]}), config,
                              context=Customer(who), version="v2")
        text = "(approved)"
    print(f"{who}: {text}\n  desk: {result.value['messages'][-1].text}")

Running a password and a card question

Two messages: a password question, and a card number sent with an order question.

Example
from chat import say

say("ravi", "What is my password?", "ravi-0")
say("ravi", "My card 4111 1111 1111 1111 was charged. Where is A17?", "ravi-1")

How each guard fired

  • The password question was answered by the guardrail with no model call at all.
  • The card question reached the model with the number already masked, and the answer is about the order.
  • List order is event order: no_passwords runs first, then the PII masking, then the call limit, then the refund pause.

The four guards

MiddlewareWhen it actsWhat it does
no_passwordsBefore the modelAnswers and ends the run
PIIMiddlewareBefore the model and checkpointerMasks a card number
ModelCallLimitMiddlewareEach model callStops after six
HumanInTheLoopMiddlewareBefore refund_order runsPauses for approval

When an agent needs guardrails

  • A public-facing agent that must refuse some inputs and redact others.
  • Any tool whose action is irreversible and needs a human to approve it first.
Watch out. Middleware runs in list order. Put no_passwords below the PII step and the password reaches the model before anything blocks it.
Try it yourself
  • Move no_passwords below the PII middleware and ask both questions again.
  • Set run_limit=1 and ask about an order.
  • Add "lookup_order": True to interrupt_on and see what pauses.

Slow is fine. Stopping is the only problem.