Open-Source Wikis

/

LangChain

/

Features

/

Middleware

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_* and after_* hooks return a dict patch to merge into state, a Command for control-flow operations, or None for no change.
  • wrap_* hooks must call the handler to 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 invoke or ainvoke is 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 None

The 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) — overrides before_model to count tokens and inject a summary system message when over the threshold.
  • HumanInTheLoopMiddleware (human_in_the_loop.py, ~14 KB) — overrides wrap_tool_call to pause execution via langgraph's interrupt and resume after approval.
  • ToolRetryMiddleware (tool_retry.py, ~14 KB) — overrides wrap_tool_call with tenacity-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 langgraph graph nodes and Runtime context.
  • Operate on langchain-core messages, content blocks, and tools.
  • Trace through the standard callback machinery, with input scrubbing in factory.py._scrub_inputs to keep runtime and handler out 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 from middleware/__init__.py.
  • For a new hook type, edit AgentMiddleware in middleware/types.py and wire the dispatch in factory.py. This is rare and significant — adds to the middleware ABI.
  • For new sandbox policies for ShellToolMiddleware, follow the *ExecutionPolicy pattern in shell_tool.py.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Middleware – LangChain wiki | Factory