getMiddlewares(ctx) and it runs on every model turn.
Build the middleware with createMiddleware
Import Canonical source: weather-middleware.ts.
createMiddleware from langchain. Set a name (appears in error messages) and any of the six hooks.Pick the right hook
createMiddleware accepts exactly six hooks. Pick the one whose timing matches your work — there is no onError, no beforeToolCall, no afterToolCall.| Hook | Fires | Use for |
|---|---|---|
beforeAgent | Once, before the agent loop starts | One-time setup per turn |
beforeModel | Before every LLM call | Logging, timers, observation |
wrapModelCall | Around every LLM call (you call handler) | Error handling, fallbacks, retries |
wrapToolCall | Around every tool call (you call handler) | Tool-level interception |
afterModel | After every LLM call | Logging, emitting events |
afterAgent | Once, after the agent loop ends | Teardown, final accounting |
wrapModelCall: wrap the handler(request) call in a try/catch and retry with a fallback model. The hook owns the call, so you decide what happens on failure.Return undefined or jumpTo — never a state object
Returning a partial state object from a plugin middleware breaks LangGraph checkpointer thread continuity — the observed symptom is a brand-new thread spawned on every message. Message and state rewrites belong in the transport layer (the messages controller), not in a middleware hook.
Return it from getMiddlewares(ctx)
The runtime appends your middleware after the framework’s built-in middleware (see the list below).See
getMiddlewares in weather.plugin.ts.What to know before shipping
- Four always-on middleware run first and are not removable, in this order:
capability-gate,tool-validation,tool-repetition-guard,tool-retry. Two more run only when the matching hook is set oncreateOracleApp:page-context(whenhooks.getRoomTitleis provided) andsafety-guardrail(whenhooks.safetyModelis provided). Your plugin middleware is appended after all of these. - Plugin middleware runs in topological dependency order — encode ordering via
dependsOnif it matters. - Middleware fires per LLM turn, not per individual tool invocation. Wrap the tool handler directly for per-tool behaviour.
- Closure-scoped timers interleave across concurrent model calls. For accurate timing, push start times onto a per-call ID.
- Don’t reimplement auth in a middleware — auth runs at the HTTP layer (
AuthHeaderMiddleware), not in the agent loop.
Where to read next
Add a sub-agent
Sub-agent-scoped middleware.
State schema
What’s in
state when your hooks fire.