The order middleware runs in
Middleware order is the sequence the hooks run in: before hooks run in list order and after hooks run in reverse.
Last updated: 27 Sep, 2026 · LangChain 1.4
Layers run in list order
The clip starts where the guardrails crash course moves from before_agent to after_agent. A before-agent hook is an input filter: it runs as soon as the input arrives. An after-agent hook runs once the agent has produced its output, which suits model-based safety evaluation, compliance scanning and removing sensitive information that slipped through; the video's example asks a second model whether each reply is safe. Then the crash course stacks the guardrails it built into one agent, one by one: a content filter as layer 1, PIIMiddleware as layer 2, human-in-the-loop approval next, PII redaction on the output, and a model-based output safety check last.
That list order is the order the checks meet a request. Back to the airport from Middleware: code around the model: you pass the bag check before immigration, never the other way round. The first entry in the list is the outer layer: its before hook runs first and its after hook runs last. When two middleware touch the same thing, the order decides which one sees the other's change. A small class that prints its own name makes the order visible.
The video shows ordering with its stack of guardrails. The code below makes the same rule visible with a smaller example: a middleware class that prints its name from each hook, so the order is printed as it happens.
Pick one to watch it run, step by step.
The AgentMiddleware class
from langchain.agents.middleware import AgentMiddleware
class Tag(AgentMiddleware):
def before_model(self, state, runtime): ... # runs first to last
def after_model(self, state, runtime): ... # runs last to first
agent = create_agent(model, tools=[], middleware=[Tag(), Tag()])The Tag middleware class
Write a middleware class. It takes a label and gives itself a name from it. The name property matters: LangChain names each middleware after its class unless told otherwise, and refuses two with the same name.
from langchain.agents.middleware import AgentMiddleware
class Tag(AgentMiddleware):
def __init__(self, label):
super().__init__()
self.label = label
@property
def name(self): # each middleware needs a unique name
return self.labelThe before and after methods
Add the two hooks to the class, each printing the label.
def before_model(self, state, runtime):
print("before", self.label)
def after_model(self, state, runtime):
print("after ", self.label)Running two middleware in order
Create the agent with two tags, one labelled first and one second, on the same Groq model as Middleware: code around the model; no tools are needed to say hello. Before hooks fire first to last, after hooks last to first. The reply itself is not printed; only the hooks are.
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
model = init_chat_model("groq:openai/gpt-oss-120b", temperature=0)
agent = create_agent(model, tools=[], middleware=[Tag("first"), Tag("second")])
agent.invoke({"messages": [{"role": "user", "content": "Hello"}]})before first before second after second after first
The argument has to be called runtime
LangChain passes the runtime to a hook by the name runtime. A hook that calls the argument rt fails the first time it runs. Here is that mistake, so you recognise the error when you meet it.
from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware
from langchain.chat_models import init_chat_model
class Short(AgentMiddleware):
def before_model(self, state, rt):
print(len(state["messages"]))
model = init_chat_model("groq:openai/gpt-oss-120b", temperature=0)
agent = create_agent(model, tools=[], middleware=[Short()])
agent.invoke({"messages": [{"role": "user", "content": "Hello"}]})Traceback (most recent call last):
File "main.py", line 13, in <module>
agent.invoke({"messages": [{"role": "user", "content": "Hello"}]})
TypeError: Short.before_model() missing 1 required positional argument: 'rt'
During task with name 'Short.before_model' and id 'dfc3d329-afcd-b3b9-79fb-86d59bbb2a56'What the print order shows
- Before hooks ran first to last, in the order of the list; after hooks ran last to first. Picture the list as layers around the model: the first entry is the outermost, first in and last out.
- The name property gives each middleware a unique name; LangChain refuses two with the same name, so the tags differ by label.
Before hooks vs after hooks
| before hooks | after hooks | |
|---|---|---|
| Order | List order, first to last | Reverse, last to first |
| Layer | First entry is outermost, first in | First entry is outermost, last out |
| Sees | The state before the model | The state after the model |
Where ordering matters
- Stacking middleware where an outer one must wrap an inner one, like a guard around a logger.
- Working out which middleware sees a change first when two touch the same state.
runtime, or it fails on the first question, not when the agent is built.Related
- Previous: Middleware: code around the model
- Next: Changing the call: wrap_model_call
- Reference: Middleware
- Pass
Tag("first")twice and read the error. - Add a third
Tagin the middle of the list and predict the six lines before you run it. - Add a
before_agentmethod toTagand see where it prints.
Every expert started right here.