Skip to main content
Deep agents are built with a modular middleware architecture. Deep agents have access to:
  1. A planning tool
  2. A filesystem for storing context and long-term memories
  3. The ability to spawn subagents
Each feature is implemented as separate middleware. When you create a deep agent with create_deep_agent, we automatically attach TodoListMiddleware, FilesystemMiddleware, and SubAgentMiddleware to your agent. Middleware is composable—you can add as many or as few middleware to an agent as needed. You can use any middleware independently. The following sections explain what each middleware provides.

To-do list middleware

Planning is integral to solving complex problems. If you’ve used Claude Code recently, you’ll notice how it writes out a to-do list before tackling complex, multi-part tasks. You’ll also notice how it can adapt and update this to-do list on the fly as more information comes in. TodoListMiddleware provides your agent with a tool specifically for updating this to-do list. Before and while it executes a multi-part task, the agent is prompted to use the write_todos tool to keep track of what it’s doing and what still needs to be done.
import { createAgent, todoListMiddleware } from "langchain";

// todoListMiddleware is included by default in createDeepAgent
// You can customize it if building a custom agent
const agent = createAgent({
  model: "claude-sonnet-4-5-20250929",
  middleware: [
    todoListMiddleware({
      // Optional: Custom addition to the system prompt
      systemPrompt: "Use the write_todos tool to...",
    }),
  ],
});

Filesystem middleware

Context engineering is a main challenge in building effective agents. This is particularly difficult when using tools that return variable-length results (for example, web_search and rag), as long tool results can quickly fill your context window. FilesystemMiddleware provides four tools for interacting with both short-term and long-term memory:
  • ls: List the files in the filesystem
  • read_file: Read an entire file or a certain number of lines from a file
  • write_file: Write a new file to the filesystem
  • edit_file: Edit an existing file in the filesystem
import { createAgent } from "langchain";
import { createFilesystemMiddleware } from "deepagents";

// FilesystemMiddleware is included by default in createDeepAgent
// You can customize it if building a custom agent
const agent = createAgent({
  model: "claude-sonnet-4-5-20250929",
  middleware: [
    createFilesystemMiddleware({
      backend: undefined,  // Optional: custom backend (defaults to StateBackend)
      systemPrompt: "Write to the filesystem when...",  // Optional custom system prompt override
      customToolDescriptions: {
        ls: "Use the ls tool when...",
        read_file: "Use the read_file tool to...",
      },  // Optional: Custom descriptions for filesystem tools
    }),
  ],
});

Short-term vs. long-term filesystem

By default, these tools write to a local “filesystem” in your graph state. To enable persistent storage across threads, configure a CompositeBackend that routes specific paths (like /memories/) to a StoreBackend.
import { createAgent } from "langchain";
import { createFilesystemMiddleware, CompositeBackend, StateBackend, StoreBackend } from "deepagents";
import { InMemoryStore } from "@langchain/langgraph-checkpoint";

const store = new InMemoryStore();

const agent = createAgent({
  model: "claude-sonnet-4-5-20250929",
  store,
  middleware: [
    createFilesystemMiddleware({
      backend: (config) => new CompositeBackend(
        new StateBackend(config),
        { "/memories/": new StoreBackend(config) }
      ),
      systemPrompt: "Write to the filesystem when...", // Optional custom system prompt override
      customToolDescriptions: {
        ls: "Use the ls tool when...",
        read_file: "Use the read_file tool to...",
      }, // Optional: Custom descriptions for filesystem tools
    }),
  ],
});
When you configure a CompositeBackend with a StoreBackend for /memories/, any files prefixed with /memories/ are saved to persistent storage and survive across different threads. Files without this prefix remain in ephemeral state storage.

Subagent middleware

Handing off tasks to subagents isolates context, keeping the main (supervisor) agent’s context window clean while still going deep on a task. The subagents middleware allows you to supply subagents through a task tool.
import { tool } from "langchain";
import { createAgent } from "langchain";
import { createSubAgentMiddleware } from "deepagents";
import { z } from "zod";

const getWeather = tool(
  async ({ city }: { city: string }) => {
    return `The weather in ${city} is sunny.`;
  },
  {
    name: "get_weather",
    description: "Get the weather in a city.",
    schema: z.object({
      city: z.string(),
    }),
  },
);

const agent = createAgent({
  model: "claude-sonnet-4-5-20250929",
  middleware: [
    createSubAgentMiddleware({
      defaultModel: "claude-sonnet-4-5-20250929",
      defaultTools: [],
      subagents: [
        {
          name: "weather",
          description: "This subagent can get weather in cities.",
          systemPrompt: "Use the get_weather tool to get the weather in a city.",
          tools: [getWeather],
          model: "gpt-4o",
          middleware: [],
        },
      ],
    }),
  ],
});
A subagent is defined with a name, description, system prompt, and tools. You can also provide a subagent with a custom model, or with additional middleware. This can be particularly useful when you want to give the subagent an additional state key to share with the main agent. For more complex use cases, you can also provide your own pre-built LangGraph graph as a subagent.
import { tool, createAgent } from "langchain";
import { createSubAgentMiddleware, type SubAgent } from "deepagents";
import { z } from "zod";

const getWeather = tool(
  async ({ city }: { city: string }) => {
    return `The weather in ${city} is sunny.`;
  },
  {
    name: "get_weather",
    description: "Get the weather in a city.",
    schema: z.object({
      city: z.string(),
    }),
  },
);

const weatherSubagent: SubAgent = {
  name: "weather",
  description: "This subagent can get weather in cities.",
  systemPrompt: "Use the get_weather tool to get the weather in a city.",
  tools: [getWeather],
  model: "gpt-4o",
  middleware: [],
};

const agent = createAgent({
  model: "claude-sonnet-4-5-20250929",
  middleware: [
    createSubAgentMiddleware({
      defaultModel: "claude-sonnet-4-5-20250929",
      defaultTools: [],
      subagents: [weatherSubagent],
    }),
  ],
});
In addition to any user-defined subagents, the main agent has access to a general-purpose subagent at all times. This subagent has the same instructions as the main agent and all the tools it has access to. The primary purpose of the general-purpose subagent is context isolation—the main agent can delegate a complex task to this subagent and get a concise answer back without bloat from intermediate tool calls.
Connect these docs programmatically to Claude, VSCode, and more via MCP for real-time answers.