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
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 stopThe password guardrail
The guardrail ends the run before a model call when someone types the word password.
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 modelThe desk's imports
The rest are LangChain's own. Start with the imports the desk needs.
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_orderPassing 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.
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_orderand 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.
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.
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_passwordsruns first, then the PII masking, then the call limit, then the refund pause.
The four guards
| Middleware | When it acts | What it does |
|---|---|---|
| no_passwords | Before the model | Answers and ends the run |
| PIIMiddleware | Before the model and checkpointer | Masks a card number |
| ModelCallLimitMiddleware | Each model call | Stops after six |
| HumanInTheLoopMiddleware | Before refund_order runs | Pauses 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.
no_passwords below the PII step and the password reaches the model before anything blocks it.Related
- Previous: Three tools and a model that picks
- Next: A morning at the desk
- Reference: Middleware
- Move
no_passwordsbelow the PII middleware and ask both questions again. - Set
run_limit=1and ask about an order. - Add
"lookup_order": Truetointerrupt_onand see what pauses.
Slow is fine. Stopping is the only problem.