diff --git a/src/oss/deepagents/customization.mdx b/src/oss/deepagents/customization.mdx
index df6b85c73f..6dc8cbd814 100644
--- a/src/oss/deepagents/customization.mdx
+++ b/src/oss/deepagents/customization.mdx
@@ -71,7 +71,7 @@ Build the harness around your goal. `create_deep_agent` gives you a production-r
| [`backend=`](#backends) | Filesystem backend (StateBackend by default) |
| [`permissions=`](/oss/deepagents/permissions) | Path-level access control for the filesystem |
| [`subagents=`](#subagents) | Custom subagents for delegated tasks |
-| [`middleware=`](#middleware) | Extra middleware appended to the [default stack](#default-stack-main-agent) |
+| [`middleware=`](#middleware) | Extra middleware merged into the [default stack](#default-stack-main-agent); an instance whose `.name` matches a default replaces it in place, anything else lands after the last core middleware entry and before the profile, prompt-caching, and memory |
| [`interrupt_on=`](#human-in-the-loop) | Pause before tool calls for human approval |
| [`response_format=`](#structured-output) | Structured output schema |
| [`state_schema=`](/oss/deepagents/context-engineering#custom-state-schema) | Custom graph state schema |
@@ -266,7 +266,7 @@ Worked example—built-in profiles (Anthropic, OpenAI) ship only a `system_promp
Deep Agents support any [middleware](/oss/langchain/middleware/overview), including the built-in middleware listed below, prebuilt middleware from LangChain, provider-specific middleware, and custom middleware you write yourself.
:::python
-Pass middleware to the `middleware` argument of `create_deep_agent`. Custom middleware is appended after @[`PatchToolCallsMiddleware`] in the [default stack](#default-stack-main-agent).
+Pass middleware to the `middleware` argument of `create_deep_agent`. Each instance is merged into the [default stack](#default-stack-main-agent) by matching its `.name` against the defaults already in the stack: a match replaces the default instance in place, and anything that does not match is inserted after @[`PatchToolCallsMiddleware`]. See [Override a default middleware instance](#override-a-default-middleware-instance).
:::
:::js
@@ -288,7 +288,7 @@ From first to last:
5. @[`SummarizationMiddleware`]: Condenses message history to stay within context limits when conversations grow long (via @[create_summarization_middleware]).
6. @[`PatchToolCallsMiddleware`]: Repairs dangling tool calls in message history when a run resumes after an interruption or receives malformed tool-call arguments. Runs **before** Anthropic prompt caching and the tail stack below.
7. @[`AsyncSubAgentMiddleware`]: Only when you configure async subagents.
-8. **Your middleware argument**: Optional middleware you pass as the `middleware` argument is appended here (after Patch, before the tail stack).
+8. **Your middleware argument**: Optional middleware you pass as the `middleware` argument is merged after Patch but before the rest of the stack. An instance whose `.name` matches one of the defaults above replaces that default in place instead of duplicating it; anything else lands here. See [Override a default middleware instance](#override-a-default-middleware-instance).
9. **Harness profile extras**: Provider-specific middleware from the resolved model profile, if any.
10. **Excluded-tool filtering**: When the harness profile lists excluded tools, middleware removes those tools from the agent.
11. **Prompt caching** (@[`AnthropicPromptCachingMiddleware`] and @[`BedrockPromptCachingMiddleware`]): Both are always registered and run **after** Patch and after your middleware so the cached prefix matches what is actually sent to the model. Each no-ops on models it does not support (`unsupported_model_behavior="ignore"`), so the Anthropic middleware applies on Anthropic models and the Bedrock middleware on AWS Bedrock models with cache support.
@@ -419,6 +419,148 @@ Mutation in place, such as modifying `state.x` in `beforeAgent`, mutating a shar
If you must use mutation in custom middleware, consider what happens when subagents, parallel tools, or concurrent agent invocations run at the same time.
+### Override a default middleware instance
+
+:::python
+
+Overriding a default middleware by matching `.name` requires `deepagents>=0.7.0a3`.
+
+
+Pass a middleware instance whose `.name` matches an entry in the [default stack](#default-stack-main-agent), such as @[`SummarizationMiddleware`], to replace that default in place instead of appending a duplicate. Any middleware you pass whose `.name` does **not** match a default is not replaced, it lands after the last core middleware entry and before the profile, prompt-caching, and memory. See [Default stack (main agent)](#default-stack-main-agent) for the full ordering.
+
+```python
+from deepagents import create_deep_agent
+from deepagents.backends import StateBackend
+from deepagents.middleware import SummarizationMiddleware
+
+backend = StateBackend()
+model = "openai:gpt-5.5"
+
+custom_summarization = SummarizationMiddleware(
+ model=model,
+ backend=backend,
+ summary_prompt="Your custom summary prompt here.",
+)
+
+agent = create_deep_agent(
+ model=model,
+ middleware=[custom_summarization], # replaces the default SummarizationMiddleware
+)
+```
+
+
+An override **replaces** the default middleware instance, it is not merged with it. That means your replacement must be fully configured with any settings it needs. This is especially important for `FilesystemMiddleware`: if you override it, you must pass the `backend` (and `permissions`, if applicable) directly to your custom instance, since it won't inherit the `backend=` and `permissions=` passed to `create_deep_agent()`. To restrict the available filesystem tools, pass a `tools` allowlist to your custom @[`FilesystemMiddleware`] instance; see [Virtual filesystem access](/oss/deepagents/overview#virtual-filesystem-access) for the "Restricting filesystem tools" example.
+
+
+
+The general-purpose subagent, which Deep Agents adds automatically, inherits the same middleware customization you pass to the main agent.
+
+Declarative subagents defined via `subagents=` do not inherit the main agent's middleware customization. Pass the override directly in that subagent's own [`middleware`](/oss/deepagents/subagents#subagent-dictionary-based) field to apply it there; that field is matched against the subagent's own default stack, the same way `middleware=` is matched against the main agent's.
+
+#### Examples
+
+
+
+ Override @[`SummarizationMiddleware`] with custom `trigger` and `keep` thresholds to compact conversation history earlier or later than the default, and control how many recent messages survive each compaction.
+
+ ```python
+ from deepagents import create_deep_agent
+ from deepagents.backends import StateBackend
+ from deepagents.middleware import SummarizationMiddleware
+
+ backend = StateBackend()
+ model = "anthropic:claude-sonnet-4-6"
+
+ agent = create_deep_agent(
+ model=model,
+ middleware=[
+ SummarizationMiddleware(
+ model=model,
+ backend=backend,
+ trigger=("tokens", 100000), # summarize once the conversation exceeds 100k tokens
+ keep=("messages", 20), # keep the most recent 20 messages verbatim
+ ),
+ ],
+ )
+ ```
+
+ `trigger` also accepts `("fraction", ...)` for a percentage of the model's context window, and a list of thresholds combines them with OR semantics. See the @[`SummarizationMiddleware`] reference for the full set of options.
+
+
+ Override @[`AnthropicPromptCachingMiddleware`] to extend the cache lifetime beyond the default `5m` TTL, useful for agents with long gaps between turns. See [Prompt caching](/oss/deepagents/overview#prompt-caching) for how caching is applied by default.
+
+ ```python
+ from deepagents import create_deep_agent
+ from langchain_anthropic.middleware import AnthropicPromptCachingMiddleware
+
+ agent = create_deep_agent(
+ model="anthropic:claude-sonnet-4-6",
+ middleware=[
+ AnthropicPromptCachingMiddleware(ttl="1h"), # replaces the default 5m TTL
+ ],
+ )
+ ```
+
+
+ Override @[`FilesystemMiddleware`] with a `system_prompt` to replace the filesystem-specific instructions it appends to the system prompt in place of the dynamically generated default.
+
+ ```python
+ from deepagents import create_deep_agent
+ from deepagents.backends import StateBackend
+ from deepagents.middleware import FilesystemMiddleware
+
+ backend = StateBackend()
+
+ agent = create_deep_agent(
+ model="anthropic:claude-sonnet-4-6",
+ backend=backend,
+ middleware=[
+ FilesystemMiddleware(
+ backend=backend,
+ system_prompt="Use the virtual filesystem to track long-running work. Write intermediate results to files instead of repeating them in messages.",
+ ),
+ ],
+ )
+ ```
+
+ As with any @[`FilesystemMiddleware`] override, pass the same `backend` (and `permissions`, if applicable) used elsewhere, since the override is not merged with the default.
+
+
+ Override @[`SubAgentMiddleware`] with a `task_description` to replace the `task` tool's description, for example to steer the model on when to delegate. The override replaces the default stack outright, so redeclare the same `backend` and `subagents` passed to `create_deep_agent`. The auto-added [general-purpose subagent](/oss/deepagents/subagents#the-general-purpose-subagent) is not included unless you add an equivalent entry yourself.
+
+ ```python
+ from deepagents import create_deep_agent
+ from deepagents.backends import StateBackend
+ from deepagents.middleware import SubAgentMiddleware
+
+ backend = StateBackend()
+ model = "anthropic:claude-sonnet-4-6"
+ researcher = {
+ "name": "researcher",
+ "description": "Researches a topic and returns findings.",
+ "system_prompt": "You are a researcher.",
+ "model": model,
+ "tools": [search],
+ }
+
+ agent = create_deep_agent(
+ model=model,
+ subagents=[researcher],
+ middleware=[
+ SubAgentMiddleware(
+ backend=backend,
+ subagents=[researcher],
+ task_description="Delegate research to the `researcher` subagent only for multi-step lookups.",
+ ),
+ ],
+ )
+ ```
+
+ `task_description` also supports an `available_agents` template placeholder that is filled in with the subagent name and description list; see the @[`SubAgentMiddleware`] reference for details. For a narrower change that only rewords the `task` tool description without replacing the subagent stack, use a [harness profile](/oss/deepagents/profiles#harness-profiles)'s `tool_description_overrides` instead; see [Profiles](#profiles).
+
+
+:::
+
### Interpreters
Use [interpreters](/oss/deepagents/interpreters) to add an `eval` tool that runs JavaScript in a scoped QuickJS runtime. Interpreters are useful when the agent needs to compose tools programmatically, batch work, handle errors in code, or transform structured data without a full shell environment.
diff --git a/src/oss/deepagents/overview.mdx b/src/oss/deepagents/overview.mdx
index d6a977dc01..7a41b432ac 100644
--- a/src/oss/deepagents/overview.mdx
+++ b/src/oss/deepagents/overview.mdx
@@ -172,6 +172,33 @@ The backends support the following file system operations:
Removing @[`FilesystemMiddleware`] itself via `excluded_middleware` is intentionally rejected—it is required scaffolding in the [default middleware stack](/oss/deepagents/customization#default-stack-main-agent). Use `excluded_tools` to hide only the model-visible tool surface and leave the middleware in place. To remove the `task` tool, see [Running without subagents](/oss/deepagents/subagents#running-without-subagents).
+:::python
+
+
+ The `tools` allowlist on `FilesystemMiddleware` requires `deepagents>=0.7.0a4`.
+
+
+ To expose only a subset of the filesystem tools listed above, instead of hiding them all, pass a `tools` allowlist to @[`FilesystemMiddleware`] and provide the instance through `middleware=`. Any built-in filesystem tool left out of the list is removed from both the model's tool list and the middleware's dynamic system prompt section.
+
+ ```python
+ from deepagents import create_deep_agent
+ from deepagents.middleware import FilesystemMiddleware
+
+ # Read-only agent: write_file, edit_file, delete, and execute are never shown
+ agent = create_deep_agent(
+ model="claude-sonnet-4-6",
+ middleware=[
+ FilesystemMiddleware(backend=backend, tools=["read_file", "ls", "glob", "grep"]),
+ ],
+ )
+ ```
+
+ `read_file` must always be included in the list—omitting it raises `ValueError` when the agent is created. The `execute` and `delete` tools are also dropped from the tool surface whenever the configured backend doesn't support them, whether or not you include them in `tools`. Custom tools you add through `create_deep_agent`'s own `tools=` argument are never affected by this allowlist.
+
+ Passing your own @[`FilesystemMiddleware`] instance this way replaces the default one for the main agent and the general-purpose subagent inherits the same restriction. See [Override a default middleware instance](/oss/deepagents/customization#override-a-default-middleware-instance) for more information. Declarative subagents don't inherit it: include a `FilesystemMiddleware(tools=...)` instance in that subagent's own `middleware` field to restrict it independently.
+
+:::
+
The virtual filesystem is used by several other harness capabilities such as skills, memory, code execution, and context management.
You can also use the file system when building custom tools and middleware for Deep Agents.
diff --git a/src/oss/deepagents/subagents.mdx b/src/oss/deepagents/subagents.mdx
index 94486ca159..cd56682582 100644
--- a/src/oss/deepagents/subagents.mdx
+++ b/src/oss/deepagents/subagents.mdx
@@ -90,7 +90,7 @@ Define subagents as dictionaries matching the @[`SubAgent`] spec with the follow
| `system_prompt` | `str` | Required. Instructions for the subagent. Custom subagents must define their own. Include tool usage guidance and output format requirements.
Does not inherit from main agent. |
| `tools` | `list[Callable]` | Optional. Tools the subagent can use. Keep this minimal and include only what's needed.
Inherits from main agent by default. When specified, overrides the inherited tools entirely. |
| `model` | `str` \| `BaseChatModel` | Optional. Overrides the main agent's model. Omit to use the main agent's model.
Inherits from main agent by default. You can pass either a model identifier string like `'openai:gpt-5.5'` (using the `'provider:model'` format) or a LangChain chat model object (`init_chat_model("gpt-5.5")` or `ChatOpenAI(model="gpt-5.5")`). |
-| `middleware` | `list[Middleware]` | Optional. Additional middleware for custom behavior, logging, or rate limiting.
Does not inherit from the main agent. Appended to the [default subagent stack](/oss/deepagents/customization#default-stack-synchronous-subagents). |
+| `middleware` | `list[Middleware]` | Optional. Additional middleware for custom behavior, logging, or rate limiting.
Does not inherit from the main agent. Merged into the [default subagent stack](/oss/deepagents/customization#default-stack-synchronous-subagents): an instance whose `.name` matches a default replaces it in place, anything else lands after the last core middleware entry and before profile, prompt-caching, and memory. See [Override a default middleware instance](/oss/deepagents/customization#override-a-default-middleware-instance). For example, include a @[`FilesystemMiddleware`] instance with a `tools` allowlist here to restrict the subagent's filesystem tools independently of the main agent. For more information, see the "Restricting filesystem tools" section under [Virtual filesystem access](/oss/deepagents/overview#virtual-filesystem-access). |
| `interrupt_on` | `dict[str, bool \| InterruptOnConfig]` | Optional. Configure [human-in-the-loop](/oss/deepagents/human-in-the-loop) for specific tools. Options:`True`, `False`, or an `InterruptOnConfig` with `allowed_decisions`. Requires checkpointer.
Inherits from main agent by default. Subagent value overrides the default. |
| `skills` | `list[str]` | Optional. [Skills](/oss/deepagents/skills) source paths. When specified, the subagent will load skills from these directories (e.g., `["/skills/research/", "/skills/web-search/"]`). This allows subagents to have different skill sets than the main agent.
Does not inherit from main agent. Only the general-purpose subagent inherits the main agent's skills. When a subagent has skills, it runs its own independent @[`SkillsMiddleware`] instance. Skill state is fully isolated—a subagent's loaded skills are not visible to the parent, and vice versa. |
| `response_format` | `ResponseFormat` | Optional. [Structured output](/oss/langchain/structured-output) schema for the subagent. When set, the parent receives the subagent's result as JSON instead of free-form text. Accepts Pydantic models, `ToolStrategy(...)`, `ProviderStrategy(...)`, or a raw schema type. See [Structured output](#structured-output). |
diff --git a/src/oss/langchain/middleware/built-in.mdx b/src/oss/langchain/middleware/built-in.mdx
index 942999917a..8a01ff6386 100644
--- a/src/oss/langchain/middleware/built-in.mdx
+++ b/src/oss/langchain/middleware/built-in.mdx
@@ -2600,7 +2600,8 @@ agent = create_agent(
custom_tool_descriptions={
"ls": "Use the ls tool when...",
"read_file": "Use the read_file tool to..."
- } # Optional: Custom descriptions for filesystem tools
+ }, # Optional: Custom descriptions for filesystem tools
+ tools=["read_file", "ls", "glob", "grep"], # Optional: Allowlist restricting which filesystem tools are exposed
),
],
)