langchain-ai/langchain
Middleware
The plugin system for create_agent. Each middleware is an AgentMiddleware subclass that hooks into one or more lifecycle events: before_agent, before_model, wrap_model_call, wrap_tool_call, after_model, after_agent. Source: libs/langchain_v1/langchain/agents/middleware/.
Purpose
Middleware lets you add cross-cutting behavior to an agent — retries, fallbacks, summarization, redaction, human approval, custom tool execution policies — without forking create_agent. Each middleware is a small, composable unit. Multiple middleware compose left-to-right (outermost first).
Built-in middleware
The package ships 16 middleware modules under libs/langchain_v1/langchain/agents/middleware/. Each is independently importable from langchain.agents.middleware.
| Middleware | File | Purpose |
|---|---|---|
SummarizationMiddleware |
summarization.py |
Summarizes older messages when token usage exceeds a threshold; replaces them with a system-message summary |
HumanInTheLoopMiddleware |
human_in_the_loop.py |
Pauses execution before specified tool calls and awaits human approval |
PIIMiddleware (and PIIDetectionError) |
pii.py |
Detects PII in user messages and tool calls; can block, redact, or warn |
ContextEditingMiddleware, ClearToolUsesEdit |
context_editing.py |
Edit the message stream — drop, mask, or rewrite portions before they reach the model |
ModelCallLimitMiddleware |
model_call_limit.py |
Hard cap on model invocations per agent run |
ModelFallbackMiddleware |
model_fallback.py |
Try alternative models when the primary fails |
ModelRetryMiddleware |
model_retry.py |
Retry the model call with exponential backoff |
ToolCallLimitMiddleware |
tool_call_limit.py |
Cap the number of tool calls per run |
ToolRetryMiddleware |
tool_retry.py |
Retry tool execution on configured exceptions |
LLMToolEmulator |
tool_emulator.py |
Replace real tool execution with a model-emulated response (useful for testing) |
LLMToolSelectorMiddleware |
tool_selection.py |
Use a separate model call to pre-select which tools to expose for a given input |
TodoListMiddleware |
todo.py |
Maintains a model-managed todo list across the agent loop |
ShellToolMiddleware, HostExecutionPolicy, DockerExecutionPolicy, CodexSandboxExecutionPolicy, RedactionRule |
shell_tool.py |
Adds a shell tool with sandboxing options (host, Docker, Codex sandbox) |
FilesystemFileSearchMiddleware |
file_search.py |
Adds a filesystem-search tool the agent can use |
Internal modules (private, used by the public middleware): _execution.py (process and Docker exec), _redaction.py (text redaction utilities), _retry.py (shared retry helpers).
The AgentMiddleware contract
libs/langchain_v1/langchain/agents/middleware/types.py (~1,700 lines) defines:
class AgentMiddleware(Generic[StateT, ContextT]):
state_schema: type[StateT] = AgentState
tools: list[BaseTool] = []
@hook_config(...)
def before_agent(self, state: StateT, runtime: Runtime[ContextT]) -> dict | Command | None: ...
@hook_config(...)
def before_model(self, state: StateT, runtime: Runtime[ContextT]) -> dict | Command | None: ...
def wrap_model_call(
self,
request: ModelRequest[ContextT],
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse | AIMessage | ExtendedModelResponse: ...
async def awrap_model_call(self, request, handler) -> ...: ...
def wrap_tool_call(
self,
request: ToolCallRequest[ContextT],
handler: Callable[[ToolCallRequest], ToolMessage],
) -> ToolMessage | Command | list[Command]: ...
async def awrap_tool_call(self, request, handler) -> ...: ...
def after_model(self, state: StateT, runtime: Runtime[ContextT]) -> dict | Command | None: ...
def after_agent(self, state: StateT, runtime: Runtime[ContextT]) -> dict | Command | None: ...You only override the hooks you need; the base class provides no-op defaults.
hook_config(...) is a decorator that lets you declare the hook's relationship to the state graph (e.g. can_jump_to=["__end__"] to allow short-circuiting).
Middleware can also declare state_schema (a custom TypedDict) and tools (extra tools the middleware adds to the agent's toolbox).
Hook semantics
sequenceDiagram
participant Agent as Agent loop
participant MW as Middleware
participant Model
participant Tool
Agent->>MW: before_agent (once)
loop until no tool_calls
Agent->>MW: before_model
MW->>Model: wrap_model_call(request, handler)
Model-->>MW: AIMessage
MW->>Agent: ModelResponse
Agent->>MW: after_model
opt tool_calls present
Agent->>MW: wrap_tool_call(request, handler) per call
MW->>Tool: handler(request)
Tool-->>MW: ToolMessage
MW-->>Agent: ToolMessage
end
end
Agent->>MW: after_agent (once)before_*andafter_*hooks return adictpatch to merge into state, aCommandfor control-flow operations, orNonefor no change.wrap_*hooks must call thehandlerto proceed (or skip it to short-circuit). They can wrap calls with retries, fallbacks, or redaction.- All hooks have sync and async variants. The agent loop picks based on whether
invokeorainvokeis used.
Composition
Multiple middleware compose with the first in the list as the outermost layer:
agent = create_agent(
model="openai:gpt-5",
tools=[...],
middleware=[A, B, C], # A wraps B wraps C
)For wrap_model_call, factory.py's _chain_model_call_handlers folds the list into a single composed handler (right-to-left), so when A calls its handler, it actually calls B, which calls C, which calls the real model. Commands from each layer are accumulated into a list (inner-first, then outer).
For before_* and after_* hooks, langgraph runs each middleware's hook in sequence (first-to-last for before_*, last-to-first for after_*).
Writing your own middleware
Two helpers make simple middleware concise:
from langchain.agents.middleware import wrap_model_call, before_model
@wrap_model_call
def add_timestamp_metadata(request, handler):
request = request.merge_metadata({"timestamp": time.time()})
return handler(request)
@before_model
def reject_empty_input(state, runtime):
if not state["messages"]:
return Command(goto="__end__")
return NoneThe decorators wrap a function as an AgentMiddleware with that hook. For larger middleware that needs to override multiple hooks or hold state, subclass AgentMiddleware directly.
Examples in the codebase
A few of the built-in middleware are good templates:
SummarizationMiddleware(summarization.py, ~26 KB) — overridesbefore_modelto count tokens and inject a summary system message when over the threshold.HumanInTheLoopMiddleware(human_in_the_loop.py, ~14 KB) — overrideswrap_tool_callto pause execution vialanggraph'sinterruptand resume after approval.ToolRetryMiddleware(tool_retry.py, ~14 KB) — overrideswrap_tool_callwithtenacity-style retry logic.PIIMiddleware(pii.py, ~12 KB) — runs a redaction pass on inbound and outbound text content.ShellToolMiddleware(shell_tool.py, ~32 KB) — adds a shell tool plus three sandboxing policies (host process, Docker container, Codex sandbox).
Integration points
- Built on
langgraphgraph nodes andRuntimecontext. - Operate on
langchain-coremessages, content blocks, and tools. - Trace through the standard callback machinery, with input scrubbing in
factory.py._scrub_inputsto keepruntimeandhandlerout of LangSmith traces.
Entry points for modification
- For a new built-in middleware, add a module under
libs/langchain_v1/langchain/agents/middleware/and re-export frommiddleware/__init__.py. - For a new hook type, edit
AgentMiddlewareinmiddleware/types.pyand wire the dispatch infactory.py. This is rare and significant — adds to the middleware ABI. - For new sandbox policies for
ShellToolMiddleware, follow the*ExecutionPolicypattern inshell_tool.py.
Related
- features/agents — the consumer of middleware
- packages/langchain — the package shipping this
- primitives/tools — tools that middleware can wrap
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.