Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 145 additions & 3 deletions src/oss/deepagents/customization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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.
</Warning>

### Override a default middleware instance

:::python
<Note>
Overriding a default middleware by matching `.name` requires `deepagents>=0.7.0a3`.
</Note>

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
Comment thread
imnishitha marked this conversation as resolved.
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
)
```

<Note>
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.

</Note>

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

<AccordionGroup>
<Accordion title="Adjust when summarization triggers" icon="adjustments">
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.
</Accordion>
<Accordion title="Update the prompt cache TTL" icon="clock">
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
],
)
```
</Accordion>
<Accordion title="Customize the filesystem instructions in the system prompt" icon="file-text">
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.
</Accordion>
<Accordion title="Customize the task tool description" icon="message">
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).
</Accordion>
</AccordionGroup>
:::

### 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.
Expand Down
27 changes: 27 additions & 0 deletions src/oss/deepagents/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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).
</Accordion>

:::python
<Accordion title="Restricting filesystem tools" icon="filter">
<Note>
The `tools` allowlist on `FilesystemMiddleware` requires `deepagents>=0.7.0a4`.
</Note>

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.
</Accordion>
:::

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.

Expand Down
2 changes: 1 addition & 1 deletion src/oss/deepagents/subagents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.<br></br>Does not inherit from main agent. |
| `tools` | `list[Callable]` | Optional. Tools the subagent can use. Keep this minimal and include only what's needed.<br></br>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.<br></br>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.<br></br>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.<br></br>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.<br></br>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.<br></br>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). |
Expand Down
3 changes: 2 additions & 1 deletion src/oss/langchain/middleware/built-in.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
),
],
)
Expand Down
Loading