Pass a model string in provider:model format, or an initialized model instance. See supported models for all providers and suggested models for tested recommendations.
Use the provider:model format (for example openai:gpt-5.4) to quickly switch between models.
import { createDeepAgent } from "deepagents";process.env.OPENAI_API_KEY = "your-api-key";const agent = createDeepAgent({ model: "gpt-5.4" });// this calls initChatModel for the specified model with default parameters// to use specific model parameters, use initChatModel directly
import { createDeepAgent } from "deepagents";process.env.ANTHROPIC_API_KEY = "your-api-key";const agent = createDeepAgent({ model: "anthropic:claude-sonnet-4-6" });// this calls initChatModel for the specified model with default parameters// to use specific model parameters, use initChatModel directly
import { createDeepAgent } from "deepagents";process.env.AZURE_OPENAI_API_KEY = "your-api-key";process.env.AZURE_OPENAI_ENDPOINT = "your-endpoint";process.env.OPENAI_API_VERSION = "your-api-version";const agent = createDeepAgent({ model: "azure_openai:gpt-5.4" });// this calls initChatModel for the specified model with default parameters// to use specific model parameters, use initChatModel directly
import { createDeepAgent } from "deepagents";process.env.GOOGLE_API_KEY = "your-api-key";const agent = createDeepAgent({ model: "google-genai:gemini-3.1-pro-preview" });// this calls initChatModel for the specified model with default parameters// to use specific model parameters, use initChatModel directly
import { createDeepAgent } from "deepagents";// Follow the steps here to configure your credentials:// https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started.htmlconst agent = createDeepAgent({ model: "bedrock:anthropic.claude-sonnet-4-6" });// this calls initChatModel for the specified model with default parameters// to use specific model parameters, use initChatModel directly
import { initChatModel } from "langchain";import { createDeepAgent } from "deepagents";const model = await initChatModel("provider:model-name");const agent = createDeepAgent({ model });
LangChain chat models automatically retry failed API requests with exponential backoff. By default, models retry up to 6 times for network errors, rate limits (429), and server errors (5xx). Client errors like 401 (unauthorized) or 404 are not retried.You can adjust the maxRetries parameter when creating a model to tune this behavior for your environment:
import { ChatAnthropic } from "@langchain/anthropic";import { createDeepAgent } from "deepagents";const agent = createDeepAgent({ model: new ChatAnthropic({ model: "claude-sonnet-4-6", maxRetries: 10, // Increase for unreliable networks (default: 6) timeout: 120_000, // Increase timeout for slow connections }),});
For long-running agent tasks on unreliable networks, consider increasing max_retries to 10–15 and pairing it with a checkpointer so that progress is preserved across failures.
Deep Agents come with a built-in system prompt. A deep agent’s value comes from the orchestration layer the SDK provides on top of the model—planning, virtual-filesystem tools, and subagents—and the model needs to know those exist and when to reach for them. The built-in prompt teaches the agent how to use that scaffolding so you don’t have to re-derive it for every project; tweak it through a profile or your own system_prompt= rather than copying it verbatim.When middleware add special tools, like the filesystem tools, it appends them to the system prompt.Each deep agent should also include a custom system prompt specific to its specific use case:
import { createDeepAgent } from "deepagents";const researchInstructions = `You are an expert researcher. ` + `Your job is to conduct thorough research, and then ` + `write a polished report.`;const agent = createDeepAgent({ systemPrompt: researchInstructions,});
Deep Agents builds the system prompt from up to four named parts so that caller-supplied instructions, the SDK’s built-in agent guidance, and any model-specific profile overrides can coexist with predictable precedence. Without this layering, a profile suffix tuned for Claude (for example) could overwrite or be overwritten by your system_prompt= argument depending on call order; the named slots make the ordering explicit and stable.In practice, most callers only encounter two slots: USER (your system_prompt=) and BASE (the SDK default). Selecting a model with a built-in profile—Anthropic or OpenAI today—adds a SUFFIX. The full four-part assembly is mainly relevant when you author a custom HarnessProfile or debug why a profile’s text appears where it does.The four named parts (each may be absent):
Name
Source
Notes
USER
system_prompt= argument to create_deep_agent
str or SystemMessage; omitted when unset.
BASE
The SDK default (BASE_AGENT_PROMPT)
Always present unless replaced by a profile’s CUSTOM.
The order is always USER -> (BASE or CUSTOM) -> SUFFIX, joined by blank lines (\n\n). Two invariants follow:
USER is always at the front. The caller’s text precedes any SDK or profile content, so persona/instructions take precedence regardless of which model is selected.
SUFFIX is always at the end. Profile suffixes sit closest to the conversation history, where model-tuning guidance lands most reliably.
Assembled shapes (✓ = field is set, - = field is unset):
system_prompt=
profile base_system_prompt (CUSTOM)
profile system_prompt_suffix (SUFFIX)
Final assembled system prompt
None
-
-
BASE
None
-
✓
BASE + SUFFIX
None
✓
-
CUSTOM
None
✓
✓
CUSTOM + SUFFIX
str
-
-
USER + BASE
str
-
✓
USER + BASE + SUFFIX
str
✓
-
USER + CUSTOM
str
✓
✓
USER + CUSTOM + SUFFIX
Worked example — built-in profiles (Anthropic, OpenAI) ship only a system_prompt_suffix, so a typical call lands in the str + - + ✓ row:
agent = create_deep_agent( model="anthropic:claude-sonnet-4-6", system_prompt="You are a customer-support agent for ACME Corp.",)# Final = USER + BASE + SUFFIX# = "You are a customer-support agent for ACME Corp."# + "\n\n"# + BASE_AGENT_PROMPT# + "\n\n"# + <Claude-specific guidance>
Passing a SystemMessage (rather than a string) triggers a different concatenation path: the right-hand assembly (BASE-or-CUSTOM plus any SUFFIX) is appended as an additional text content block onto the message’s existing content_blocks. The same logical ordering applies (caller blocks first), and any cache_control markers on the caller’s blocks are preserved — useful for placing explicit Anthropic prompt-cache breakpoints.
Subagent prompts
The same overlay rules apply to declarative subagents — each subagent re-runs profile resolution against its own model, then applies the resolved profile’s base_system_prompt / system_prompt_suffix to its authored system_prompt. The subagent’s system_prompt plays the BASE role; CUSTOM and SUFFIX come from the profile that matches the subagent’s model (which may differ from the main agent’s profile).
spec["system_prompt"]
profile base_system_prompt (CUSTOM)
profile system_prompt_suffix (SUFFIX)
Final subagent system prompt
authored
-
-
authored
authored
-
✓
authored + SUFFIX
authored
✓
-
CUSTOM
authored
✓
✓
CUSTOM + SUFFIX
There is no USER segment for subagents — the spec’s authored system_prompt is the closest analog and stays in the BASE slot. A profile that ships only a system_prompt_suffix (the common case for built-in Anthropic / OpenAI profiles) just appends to whatever the subagent author wrote; a profile that sets base_system_prompt will replace the authored prompt outright, so reach for that field deliberately.
General-purpose subagent prompt
The auto-added general-purpose subagent follows the same overlay rules with one extra layer: the GP base prompt is resolved as general_purpose_subagent.system_prompt (if set) -> HarnessProfile.base_system_prompt (if set) -> SDK GP default. The profile suffix layers on top either way.The two override fields can both carry a base-prompt replacement, but they are not interchangeable. general_purpose_subagent.system_prompt is GP-specific configuration; base_system_prompt is a global override that primarily targets the main agent. When both are set, the GP-specific intent wins for the GP subagent so a user tuning both fields never sees their GP override silently dropped:
register_harness_profile( "anthropic", HarnessProfile( base_system_prompt="You are ACME's support orchestrator.", # main agent general_purpose_subagent=GeneralPurposeSubagentProfile( system_prompt="You are a research subagent. Cite sources.", # GP subagent ), system_prompt_suffix="Always think step by step.", ),)
Stack
Final system prompt
Main agent
"You are ACME's support orchestrator." + SUFFIX
GP subagent
"You are a research subagent. Cite sources." + SUFFIX
If general_purpose_subagent.system_prompt is unset, the GP subagent falls back to base_system_prompt (when set) and finally to the SDK GP default.
LangChain exposes additional prebuilt middleware that let you add-on various features, such as retries, fallbacks, or PII detection. See Prebuilt middleware for more.The deepagents package also exposes createSummarizationMiddleware for the same workflow. For more detail, see Summarization.
You can provide additional middleware to extend functionality, add tools, or implement custom hooks:
import { tool, createMiddleware } from "langchain";import { createDeepAgent } from "deepagents";import * as z from "zod";const getWeather = tool( ({ 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(), }), });let callCount = 0;const logToolCallsMiddleware = createMiddleware({ name: "LogToolCallsMiddleware", wrapToolCall: async (request, handler) => { // Intercept and log every tool call - demonstrates cross-cutting concern callCount += 1; const toolName = request.toolCall.name; console.log(`[Middleware] Tool call #${callCount}: ${toolName}`); console.log( `[Middleware] Arguments: ${JSON.stringify(request.toolCall.args)}` ); // Execute the tool call const result = await handler(request); // Log the result console.log(`[Middleware] Tool call #${callCount} completed`); return result; },});const agent = await createDeepAgent({ model: "google_genai:gemini-3.1-pro-preview", tools: [getWeather] as any, middleware: [logToolCallsMiddleware] as any,});
Do not mutate attributes after initializationIf you need to track values across hook invocations (for example, counters or accumulated data), use graph state.
Graph state is scoped to a thread by design, so updates are safe under concurrency.Do this:
let x = 1;const customMiddleware = createMiddleware({ name: "CustomMiddleware", beforeAgent: async () => { x += 1; // Mutation causes race conditions },});
Mutation in place, such as modifying state.x in beforeAgent, mutating a shared variable in beforeAgent, or changing other shared values in hooks, can lead to subtle bugs and race conditions because many operations run concurrently (subagents, parallel tools, and parallel invocations on different threads).For full details on extending state with custom properties, see Custom middleware - Custom state schema.
If you must use mutation in custom middleware, consider what happens when subagents, parallel tools, or concurrent agent invocations run at the same time.
Tools for a deep agent can make use of virtual file systems to store, access, and edit files. By default, deep agents use a StateBackend.If you are using skills or memory, you must add the expected skill or memory files to the backend before creating the agent.
StateBackend
FilesystemBackend
LocalShellBackend
StoreBackend
CompositeBackend
An ephemeral filesystem backend stored in langgraph state.This filesystem only persists for a single thread.
import { createDeepAgent, StateBackend } from "deepagents";// By default we provide a StateBackendconst agent = createDeepAgent();// Under the hood, it looks likeconst agent2 = createDeepAgent({ backend: new StateBackend(),});
The local machine’s filesystem.
This backend grants agents direct filesystem read/write access.
Use with caution and only in appropriate environments.
For more information, see FilesystemBackend.
import { createDeepAgent, FilesystemBackend } from "deepagents";const agent = createDeepAgent({ backend: new FilesystemBackend({ rootDir: ".", virtualMode: true }),});
A filesystem with shell execution directly on the host. Provides filesystem tools plus the execute tool for running commands.
This backend grants agents direct filesystem read/write access and unrestricted shell execution on your host.
Use with extreme caution and only in appropriate environments.
For more information, see LocalShellBackend.
import { createDeepAgent, LocalShellBackend } from "deepagents";const backend = new LocalShellBackend({ workingDirectory: "." });const agent = createDeepAgent({ backend });
A filesystem that provides long-term storage that is persisted across threads.
import { createDeepAgent, StoreBackend } from "deepagents";import { InMemoryStore } from "@langchain/langgraph";const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deploymentconst agent = createDeepAgent({ backend: new StoreBackend({ namespace: (ctx) => [ctx.runtime.context.userId], }), store});
When deploying to LangSmith Deployment, omit the store parameter. The platform automatically provisions a store for your agent.
The namespace parameter controls data isolation. For multi-user deployments, always set a namespace factory to isolate data per user or tenant.
A flexible backend where you can specify different routes in the filesystem to point towards different backends.
import { createDeepAgent, CompositeBackend, StateBackend, StoreBackend } from "deepagents";import { InMemoryStore } from "@langchain/langgraph";const store = new InMemoryStore();const agent = createDeepAgent({ backend: new CompositeBackend( new StateBackend(), { "/memories/": new StoreBackend(), } ), store,});
Sandboxes are specialized backends that run agent code in an isolated environment with their own filesystem and an execute tool for shell commands.
Use a sandbox backend when you want your deep agent to write files, install dependencies, and run commands without changing anything on your local machine.You configure sandboxes by passing a sandbox backend to backend when creating your deep agent:
import { createDeepAgent } from "deepagents";import { ChatAnthropic } from "@langchain/anthropic";import { DenoSandbox } from "@langchain/deno";// Create and initialize the sandboxconst sandbox = await DenoSandbox.create({ memoryMb: 1024, lifetime: "10m",});try { const agent = createDeepAgent({ model: new ChatAnthropic({ model: "claude-opus-4-6" }), systemPrompt: "You are a JavaScript coding assistant with sandbox access.", backend: sandbox, }); const result = await agent.invoke({ messages: [ { role: "user", content: "Create a simple HTTP server using Deno.serve and test it with curl", }, ], });} finally { await sandbox.close();}
You can use skills to provide your deep agent with new capabilities and expertise.
While tools tend to cover lower level functionality like native file system actions or planning, skills can contain detailed instructions on how to complete tasks, reference info, and other assets, such as templates.
These files are only loaded by the agent when the agent has determined that the skill is useful for the current prompt.
This progressive disclosure reduces the amount of tokens and context the agent has to consider upon startup.For example skills, see Deep Agents example skills.To add skills to your deep agent, pass them as an argument to create_deep_agent:
StateBackend
StoreBackend
FilesystemBackend
import { createDeepAgent, type FileData } from "deepagents";import { MemorySaver } from "@langchain/langgraph";const checkpointer = new MemorySaver();function createFileData(content: string): FileData { const now = new Date().toISOString(); return { content: content.split("\n"), created_at: now, modified_at: now, };}const skillsFiles: Record<string, FileData> = {};const skillUrl = "https://raw.githubusercontent.com/langchain-ai/deepagentsjs/refs/heads/main/examples/skills/langgraph-docs/SKILL.md";const response = await fetch(skillUrl);const skillContent = await response.text();skillsFiles["/skills/langgraph-docs/SKILL.md"] = createFileData(skillContent);const agent = await createDeepAgent({ checkpointer, // IMPORTANT: deepagents skill source paths are virtual (POSIX) paths relative to the backend root. skills: ["/skills/"],});const config = { configurable: { thread_id: `thread-${Date.now()}`, },};const result = await agent.invoke( { messages: [ { role: "user", content: "what is langraph? Use the langgraph-docs skill if available.", }, ], files: skillsFiles, }, config,);
import { createDeepAgent, StoreBackend, type FileData } from "deepagents";import { InMemoryStore, MemorySaver,} from "@langchain/langgraph";const checkpointer = new MemorySaver();const store = new InMemoryStore();function createFileData(content: string): FileData { const now = new Date().toISOString(); return { content: content.split("\n"), created_at: now, modified_at: now, };}const skillUrl = "https://raw.githubusercontent.com/langchain-ai/deepagentsjs/refs/heads/main/examples/skills/langgraph-docs/SKILL.md";const response = await fetch(skillUrl);const skillContent = await response.text();const fileData = createFileData(skillContent);await store.put(["filesystem"], "/skills/langgraph-docs/SKILL.md", fileData);const agent = await createDeepAgent({ backend: new StoreBackend(), store: store, checkpointer, // IMPORTANT: deepagents skill source paths are virtual (POSIX) paths relative to the backend root. skills: ["/skills/"],});const config = { recursionLimit: 50, configurable: { thread_id: `thread-${Date.now()}`, },};const result = await agent.invoke( { messages: [ { role: "user", content: "what is langraph? Use the langgraph-docs skill if available.", }, ], }, config,);
import { createDeepAgent, FilesystemBackend } from "deepagents";import { MemorySaver } from "@langchain/langgraph";const checkpointer = new MemorySaver();const backend = new FilesystemBackend({ rootDir: process.cwd() });const agent = await createDeepAgent({ backend, skills: ["./examples/skills/"], interruptOn: { read_file: true, write_file: true, delete_file: true, }, checkpointer, // Required!});const config = { configurable: { thread_id: `thread-${Date.now()}`, },};const result = await agent.invoke( { messages: [ { role: "user", content: "what is langraph? Use the langgraph-docs skill if available.", }, ], }, config,);
Use AGENTS.md files to provide extra context to your deep agent.You can pass one or more file paths to the memory parameter when creating your deep agent:
StateBackend
StoreBackend
Filesystem
import { createDeepAgent, type FileData } from "deepagents";import { MemorySaver } from "@langchain/langgraph";const AGENTS_MD_URL = "https://raw.githubusercontent.com/langchain-ai/deepagents/refs/heads/main/examples/text-to-sql-agent/AGENTS.md";async function fetchText(url: string): Promise<string> { const res = await fetch(url); if (!res.ok) { throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`); } return await res.text();}const agentsMd = await fetchText(AGENTS_MD_URL);const checkpointer = new MemorySaver();function createFileData(content: string): FileData { const now = new Date().toISOString(); return { content, mimeType: "text/plain", created_at: now, modified_at: now, };}const agent = await createDeepAgent({ memory: ["/AGENTS.md"], checkpointer: checkpointer,});const result = await agent.invoke( { messages: [ { role: "user", content: "Please tell me what's in your memory files.", }, ], // Seed the default StateBackend's in-state filesystem (virtual paths must start with "/"). files: { "/AGENTS.md": createFileData(agentsMd) }, }, { configurable: { thread_id: "12345" } });
import { createDeepAgent, StoreBackend, type FileData } from "deepagents"; import { InMemoryStore, MemorySaver, } from "@langchain/langgraph"; const AGENTS_MD_URL = "https://raw.githubusercontent.com/langchain-ai/deepagents/refs/heads/main/examples/text-to-sql-agent/AGENTS.md"; async function fetchText(url: string): Promise<string> { const res = await fetch(url); if (!res.ok) { throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`); } return await res.text(); } const agentsMd = await fetchText(AGENTS_MD_URL); function createFileData(content: string): FileData { const now = new Date().toISOString(); return { content, mimeType: "text/plain", created_at: now, modified_at: now, }; } const store = new InMemoryStore(); const fileData = createFileData(agentsMd); await store.put(["filesystem"], "/AGENTS.md", fileData); const checkpointer = new MemorySaver(); const agent = await createDeepAgent({ backend: new StoreBackend(), store: store, checkpointer: checkpointer, memory: ["/AGENTS.md"], }); const result = await agent.invoke( { messages: [ { role: "user", content: "Please tell me what's in your memory files.", }, ], }, { configurable: { thread_id: "12345" } } );
import { createDeepAgent, FilesystemBackend } from "deepagents";import { MemorySaver } from "@langchain/langgraph";// Checkpointer is REQUIRED for human-in-the-loopconst checkpointer = new MemorySaver();const agent = await createDeepAgent({ backend: new FilesystemBackend({ rootDir: "/Users/user/{project}" }), memory: ["./AGENTS.md", "./.deepagents/AGENTS.md"], interruptOn: { read_file: true, write_file: true, delete_file: true, }, checkpointer, // Required!});
A harness profile packages per-provider or per-model tweaks (system prompt suffixes, tool description overrides, excluded tools or middleware, extra middleware, and general-purpose subagent edits) so create_deep_agent applies them automatically when the matching model is selected.
from deepagents import HarnessProfile, register_harness_profile# Append a system-prompt suffix whenever gpt-5.4 is selected.register_harness_profile( "openai:gpt-5.4", HarnessProfile(system_prompt_suffix="Respond in under 100 words."),)
See Profiles for registration keys, merge semantics, and plugin packaging. A narrower companion API, provider profiles, packages model-construction arguments for a provider.
Deep Agents support structured output.You can set a desired structured output schema by passing it as the responseFormat argument to the call to createDeepAgent().
When the model generates the structured data, it’s captured, validated, and returned in the ‘structuredResponse’ key of the agent’s state.
import { tool } from "langchain";import { TavilySearch } from "@langchain/tavily";import { createDeepAgent } from "deepagents";import { z } from "zod";const internetSearch = tool( async ({ query, maxResults = 5, topic = "general", includeRawContent = false, }: { query: string; maxResults?: number; topic?: "general" | "news" | "finance"; includeRawContent?: boolean; }) => { const tavilySearch = new TavilySearch({ maxResults, tavilyApiKey: process.env.TAVILY_API_KEY, includeRawContent, topic, }); return await tavilySearch._call({ query }); }, { name: "internet_search", description: "Run a web search", schema: z.object({ query: z.string().describe("The search query"), maxResults: z.number().optional().default(5), topic: z .enum(["general", "news", "finance"]) .optional() .default("general"), includeRawContent: z.boolean().optional().default(false), }), });const weatherReportSchema = z.object({ location: z.string().describe("The location for this weather report"), temperature: z.number().describe("Current temperature in Celsius"), condition: z .string() .describe("Current weather condition (e.g., sunny, cloudy, rainy)"), humidity: z.number().describe("Humidity percentage"), windSpeed: z.number().describe("Wind speed in km/h"), forecast: z.string().describe("Brief forecast for the next 24 hours"),});const agent = await createDeepAgent({ responseFormat: weatherReportSchema, tools: [internetSearch],});const result = await agent.invoke({ messages: [ { role: "user", content: "What's the weather like in San Francisco?", }, ],});console.log(result.structuredResponse);// {// location: 'San Francisco, California',// temperature: 18.3,// condition: 'Sunny',// humidity: 48,// windSpeed: 7.6,// forecast: 'Clear skies with temperatures remaining mild. High of 18°C (64°F) during the day, dropping to around 11°C (52°F) at night.'// }