langchain-agents-middleware
- Repo stars 0
- Author repo skills-registry
Agentic Middleware
Middleware is how you compose cross-cutting agent behavior in LangChain v1+. It plugs into create_agent(...) (and is the underlying implementation of DeepAgents). For any production agent, the question is "which middlewares" — not "do I need middleware".
The model
from langchain.agents import create_agent
from langchain.agents.middleware import (
SummarizationMiddleware,
ModelRetryMiddleware,
ModelFallbackMiddleware,
ModelCallLimitMiddleware,
ToolRetryMiddleware,
PIIMiddleware,
)
agent = create_agent(
model="claude-sonnet-4-6",
tools=[...],
middleware=[
ModelRetryMiddleware(max_retries=3, backoff_factor=2.0, initial_delay=1.0),
ModelFallbackMiddleware("openai:gpt-4o-mini"),
ModelCallLimitMiddleware(run_limit=50),
ToolRetryMiddleware(max_retries=3, backoff_factor=2.0),
SummarizationMiddleware(model="claude-haiku-4-5", trigger=("tokens", 4000), keep=("messages", 20)),
PIIMiddleware("email", strategy="redact", apply_to_input=True),
],
)
create_agent returns a compiled LangGraph. All Runnable semantics apply: .invoke, .ainvoke, .stream, .astream, langgraph dev, langgraph build, etc.
Lifecycle hooks (for custom middleware)
Every middleware can override one or more of these:
| Hook | Fires | Return |
|---|---|---|
before_agent(state, runtime) |
Once, before the loop starts | dict to merge into state, or None |
before_model(state, runtime) |
Before each model call | dict to merge into state, or None |
wrap_model_call(request, handler) |
Wraps the model call | call handler(request) → ModelResponse; return it (possibly modified) |
after_model(state, runtime) |
After each model response | dict to merge into state, or None |
wrap_tool_call(request, handler) |
Wraps each tool call | call handler(request) → ToolMessage | Command; return it (possibly modified) |
after_agent(state, runtime) |
Once, after the loop ends | dict to merge into state, or None |
Node-style hooks (before_* / after_*) run sequentially. Wrap-style hooks compose like Python decorators — first middleware in the list is the outermost wrapper.
Built-in middlewares (provider-agnostic)
Import from langchain.agents.middleware:
| Middleware | Purpose | Constructor |
|---|---|---|
SummarizationMiddleware |
Auto-summarize long conversations to stay under token limits | (model, trigger=("tokens", N), keep=("messages", N)) |
HumanInTheLoopMiddleware |
Pause for human approve/edit/reject on sensitive tool calls | (interrupt_on={"tool_name": {"allowed_decisions": [...]}}) — requires a checkpointer |
ModelCallLimitMiddleware |
Cap model calls per run / per thread (cost containment, infinite-loop guard) | (thread_limit, run_limit, exit_behavior="end") |
ToolCallLimitMiddleware |
Cap tool calls globally or per-tool | (thread_limit, run_limit) or (tool_name, thread_limit, run_limit) |
ModelRetryMiddleware |
Retry transient model failures with exponential backoff | (max_retries, backoff_factor, initial_delay) |
ToolRetryMiddleware |
Retry transient tool failures with exponential backoff | same args |
ModelFallbackMiddleware |
Fall back to alternative models on primary failure | ("model-1", "model-2", ...) |
LLMToolSelectorMiddleware |
Use a small LLM to pick which tools to expose to the main model | (model, max_tools, always_include=[...]) |
PIIMiddleware |
Detect & redact / mask / block PII | ("email"|"credit_card"|..., strategy="redact"|"mask"|"block", apply_to_input=True) |
ContextEditingMiddleware |
Drop old tool outputs from context to free tokens | (edits=[ClearToolUsesEdit(trigger, keep)]) |
TodoListMiddleware |
Adds the write_todos planning tool to the agent |
() |
LLMToolEmulator |
Replace tool execution with LLM-generated outputs (testing) | () — never use in production |
ShellToolMiddleware |
Persistent shell session as a tool, with execution policy | (workspace_root, execution_policy) |
FilesystemFileSearchMiddleware |
Glob + Grep tools over a filesystem | (root_path, use_ripgrep=True) |
DeepAgents-specific (import from deepagents.middleware):
| Middleware | Purpose |
|---|---|
FilesystemMiddleware |
Virtual or backed filesystem for the agent (read/write/edit/ls/glob/grep) |
SubAgentMiddleware |
Adds the task tool with named sub-agents |
create_deep_agent(...) is a thin wrapper over create_agent(...) that pre-installs FilesystemMiddleware + SubAgentMiddleware + TodoListMiddleware. You can compose additional middlewares on top.
Production middleware stack (start here)
For any production agent, this is the default stack to copy and tune:
middleware=[
# Cost containment (set BEFORE retries — limits the multiplier)
ModelCallLimitMiddleware(run_limit=50),
ToolCallLimitMiddleware(run_limit=200),
# Resilience to transient failures
ModelRetryMiddleware(max_retries=3, backoff_factor=2.0, initial_delay=1.0),
ToolRetryMiddleware(max_retries=3, backoff_factor=2.0, initial_delay=1.0),
# Provider-level resilience
ModelFallbackMiddleware("openai:gpt-4o-mini"),
# Long-conversation hygiene
SummarizationMiddleware(model="claude-haiku-4-5", trigger=("tokens", 8000), keep=("messages", 20)),
# Privacy (only if user input may contain PII)
PIIMiddleware("email", strategy="redact", apply_to_input=True),
PIIMiddleware("credit_card", strategy="mask", apply_to_input=True),
]
Add HumanInTheLoopMiddleware for any tool that touches money, sends external messages, or makes irreversible changes. Requires a checkpointer (InMemorySaver for dev, PostgresSaver for production — see the deploy skill).
Custom middleware
Inherit from AgentMiddleware:
from typing import Any, Callable
from langchain.agents.middleware import (
AgentMiddleware, AgentState, ModelRequest, ModelResponse,
)
from langchain.tools.tool_node import ToolCallRequest
from langchain.messages import ToolMessage
from langgraph.types import Command
class TokenBudgetMiddleware(AgentMiddleware):
"""Hard-cap total tokens across the run. Halts the agent when exceeded."""
def __init__(self, budget: int) -> None:
self.budget = budget
def wrap_model_call(
self,
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
used = request.state.get("tokens_used", 0)
if used >= self.budget:
# short-circuit: return a synthetic "stop" response without calling the model
return ModelResponse(
messages=[{"role": "assistant", "content": "Token budget exceeded."}],
command=Command(goto="__end__"),
)
response = handler(request)
# ... extract token count from response.usage and add to state
return response
If you need extra fields in state, declare them on a subclass of AgentState and set state_schema = MyState on the middleware class.
Hard rules
- Order matters. Limits before retries (so retries don't burn through your budget). Privacy redaction before logging. Summarization should run before the model call, not after.
- HumanInTheLoopMiddleware needs a checkpointer. Without one, interrupts have nothing to resume from.
LLMToolEmulatoris a testing-only middleware. Never ship it.- Retries cost money. A
max_retries=3withbackoff_factor=2means up to 4 calls per failure. SetModelCallLimitMiddlewareBEFORE retries to cap the worst-case cost. - Don't roll your own retry/fallback/limit. The built-ins handle the edge cases (jitter, retryable error classification, streaming-aware wrapping). Custom middleware is for app-specific concerns.
Skills to load alongside this one
langchain-agents-deploy— productionisation: durable execution, checkpointers, deployment.langchain-agents-observability— tracing what middleware actually does at runtime.langchain-agents-langgraph-code— when to drop down to rawStateGraph(rare, but real cases exist).
<!-- tomevault:4.0:skill_md:2026-05-23 -->Source: cwijayasundara/agent_cli_langchain — distributed by TomeVault.
- Fluxly category
- AI
- Author-declared agents
- No explicit declaration found; this is not inferred or tested compatibility
- Static check
- 88 / 100 · heuristic scan, not runtime safety proof
- Author / version / license
- @tomevault-io · no license declared
- Fluxly token estimate
- Moderate
- Fluxly setup estimate
- Guided setup
- External API key
- No requirement detected
- Detected OS requirements
- macOS · Linux · Windows
- Runtime requirements
- Node.js · Python
- Detected file/system behavior
-
- Read-only
- Write / modify
- Shell exec
- Detected network behavior
- Local-only
- Install commands
- None (reference only)
Profile is derived at build time from SKILL.md and install vectors. Subject to drift from author intent.
Heads up: 未限定 allowed-tools,默认拥有全部工具权限。
The current SKILL.md does not define a fixed output example. createagent returns a compiled LangGraph. All Runnable semantics apply: .invoke, .ainvoke, .stream, .astream, langgraph dev, langgraph build, etc.
Every middleware can override one or more of these: Hook · Fires · Return beforeagent(state, runtime) · Once, before the loop starts · dict to merge into state, or None
Import from langchain.agents.middleware: Middleware · Purpose · Constructor SummarizationMiddleware · Auto-summarize long conversations to stay under token limits · (model, trigger=("tokens", N), keep=("messages", N))
For any production agent, this is the default stack to copy and tune: Add HumanInTheLoopMiddleware for any tool that touches money, sends external messages, or makes irreversible changes. Requires a checkpointer (InMemorySaver for dev, PostgresSaver for…
Inherit from AgentMiddleware: If you need extra fields in state, declare them on a subclass of AgentState and set stateschema = MyState on the middleware class.
Order matters. Limits before retries (so retries don't burn through your budget). Privacy redaction before logging. Summarization should run before the model call, not after. HumanInTheLoopMiddleware needs a checkpointer. Without one, interrupts have nothing…
# Agentic Middleware
Middleware is how you compose cross-cutting agent behavior in LangChain v1+. It plugs into `create_agent(...)` (and is the underlying implementation of DeepAgents). **For any production agent, the question is "which middlewares" — not "do I need middleware".**
## The model
```python
from langchain.agents import create_agent
from langchain.agents.middleware import (
SummarizationMiddleware,
ModelRetryMiddleware,
ModelFallbackMiddleware,
ModelCallLimitMiddleware,
ToolRetryMiddleware,
PIIMiddleware,
)
agent = create_agent(
model="claude-sonnet-4-6",
tools=[...],
middleware=[
ModelRetryMiddleware(max_retries=3, backoff_factor=2.0, initial_delay=1.0),
ModelFallbackMiddleware("openai:gpt-4o-mini"),
ModelCallLimitMiddleware(run_limit=50),
ToolRetryMiddleware(max_retries=3, backoff_factor=2.0),
SummarizationMiddleware(model="claude-haiku-4-5", trigger=("tokens", 4000), keep=("messages", 20)),
PIIMiddleware("email", strategy="redact", apply_to_input=True),
],
)
```
`create_agent` returns a compiled LangGraph. All `Runnable` semantics apply: `.invoke`, `.ainvoke`, `.stream`, `.astream`, `langgraph dev`, `langgraph build`, etc.
## Lifecycle hooks (for custom middleware)
Every middleware can override one or more of these:
| Hook | Fires | Return |
|---|---|---|
| `before_agent(state, runtime)` | Once, before the loop starts | dict to merge into state, or None |
| `before_model(state, runtime)` | Before each model call | dict to merge into state, or None |
| `wrap_model_call(request, handler)` | Wraps the model call | call `handler(request)` → `ModelResponse`; return it (possibly modified) |
… Author text anchors workflow facts; Fluxly only indexes current sections, terms, files, and commands.
sections -> The model → Lifecycle hooks (for custom middleware) → Built-in middlewares (provider-agnostic) → Production middleware stack (start here) → Custom middleware → Hard rules
terms -> first middleware in the list is the outermost wrapper · requires a checkpointer · Requires a checkpointer · Order matters. · HumanInTheLoopMiddleware needs a checkpointer. · LLMToolEmulator is a testing-only middleware. · Retries cost money. · Don't roll your own retry/fallback/limit.
files/cmd -> createagent(...) · createagent · Runnable · .invoke · .ainvoke · .stream · .astream · langgraph dev
body sha256 -> 88e854f1b590
Decide Fit First
Design Intent
How To Use It
Boundaries And Review