Execution order, and the reversal
Three families, three ordering rules, and the middle one runs backwards. This lesson is the highest concentration of examinable material in the course.
The rule, exactly as written
before_*hooks: First to last ·after_*hooks: Last to first (reverse) ·wrap_*hooks: Nested.
Read it twice. The middle clause is where the questions come from.
A worked trace
Take middleware=[Auth, Logging, Metrics] and assume all three implement everything.
Auth.before_agent
Logging.before_agent
Metrics.before_agentAuth.before_model
Logging.before_model
Metrics.before_modelMetrics.after_model
Logging.after_model
Auth.after_modelMetrics.after_agent
Logging.after_agent
Auth.after_agentAuth (outermost)
Logging
Metrics (innermost)
-> the actual model call
Metrics unwinds
Logging unwinds
Auth unwindsWhy it reverses
It is not arbitrary. It is the shape of nested context managers, a stack unwinding, and middleware in every HTTP framework you have ever used.
Whatever Auth set up first should be torn down last, because Logging and Metrics were operating inside it. If after_* ran first to last, Auth would clean up while the two middlewares depending on it were still working. Symmetric wrapping requires reverse teardown.
Jumps
A hook can short-circuit the loop. The legal targets are exactly three:
jump_to = 'end' | 'tools' | 'model''end' finishes the run, 'tools' skips ahead to tool execution, 'model' goes back around for another model call.
The rule that makes this examinable: a hook that might jump must declare can_jump_to up front. You cannot quietly decide at runtime.
# The middleware declares which targets it may jump to,
# and only then is a runtime jump to one of them legal.
can_jump_to = ["end"]The reason is structural. The agent is a compiled graph and a jump is a conditional edge; declaring can_jump_to is what tells the builder to draw those edges before compilation. Undeclared jump, no edge, nowhere to go.
What to walk in with
Two things, small enough to hold at minute ninety of a two-hour exam.
- Before is forwards, after is backwards, wrap is nested.
- Jumps are
'end','tools','model', declared withcan_jump_to.
And one procedural habit worth more than either: if a question hands you an array and asks what runs when, write the array down and walk it on paper. Do not do it in your head. The reversal is precisely the thing your head gets wrong when it is tired.
Try it yourself
The ordering rule, verbatim
Three clauses. If you memorise one block of text from this entire course, make it this one.
State the execution order rule for the three families of middleware hooks.
Reveal answer
before_* hooks run first to last. after_* hooks run last to first, in reverse. wrap_* hooks are nested, so the first middleware in the array is the outermost layer and the last is the innermost, closest to the actual call.
Predict the order
Three middlewares, all implementing every hook.
middleware=[Auth, Logging, Metrics]One turn of the loop runs before_model, the model call, then after_model. Which sequence is correct?
Show answer
Correct answer: C — Auth, Logging, Metrics, model, Metrics, Logging, Auth
before_model runs in array order and after_model runs in reverse, so the sequence is symmetric around the model call. The first option is the one almost everybody picks first, because array order is the correct intuition for before hooks and it feels arbitrary to abandon it halfway through the turn. The symmetry is the point: whatever was set up first is torn down last.
Which after_model runs first
Same array of Auth, Logging and Metrics. The model returns. Which after_model hook runs first?
Show answer
Correct answer: A — Metrics, because after_* hooks run last to first
after_* hooks run in reverse array order, so Metrics goes first, then Logging, then Auth. The trap is Auth, because array order is the intuition everybody arrives with and it is genuinely correct for before_* hooks. The reversal exists so each middleware unwinds symmetrically around whatever it set up on the way in, exactly like nested context managers.
Which wrap sits closest to the call
Same array again, all three defining wrap_model_call. Which one touches the actual model call most directly?
Show answer
Correct answer: D — Metrics, since wrap hooks nest and the last in the array is innermost
wrap hooks nest, so the first in the array is the outermost layer and Metrics ends up innermost. The tempting answer is Auth, because being first in a list reads as being nearest the action; with nesting, first means outermost, which is furthest from the call and closest to the caller.
The legal jump targets
A hook wants to short-circuit the loop. Which set of targets is correct?
Show answer
Correct answer: B — 'end', 'tools', 'model'
The three legal targets are exactly 'end', 'tools' and 'model'. Every distractor swaps in one plausible node name, which is how this actually gets tested: the shape is memorable and the exact vocabulary is what separates people who read the page from people who reconstructed it. 'agent' is the most dangerous of them, because before_agent and after_agent exist and make it sound like a real node.
The jump targets again, as a plain list
A middleware hook returns jump_to with no scenario to lean on. Which set lists exactly the valid targets?
Show answer
Correct answer: D — 'end', 'tools', 'model'
The valid jump_to targets are exactly 'end', 'tools' and 'model', declared via @hook_config(can_jump_to=[...]). This is the same fact as the scenario question above, drilled here as bare vocabulary because the exam rewards recalling the exact three names cold. Every distractor swaps in a plausible-sounding node name. Docs: docs.langchain.com/oss/python/langchain/middleware.
Why can_jump_to has to be declared
A why question rather than a what question, and the answer is structural rather than stylistic.
Why must a middleware declare can_jump_to rather than simply returning a jump at runtime?
Reveal answer
Because the agent is a compiled graph and jumps are edges. Declaring can_jump_to tells the builder which conditional edges to draw from that middleware's node before the graph is compiled. A jump that was never declared has no edge to travel along, so the declaration is what makes the runtime jump legal rather than a decoration on top of it.