Skip to content

后端存储 (Backends) ​

面向状态、磁盘和 Store 的可插拔文件系统后端

Deep Agents 使用可插拔的文件系统后端为工具、技能和记忆提供文件操作支持。 无论是在沙箱内运行、连接到本地工作区、还是跨对话持久化数据,你都可以通过挑选或组合后端来满足你的安全和持久化需求。

后端适用场景持久性隔离性
StateBackend(默认)临时便签文件、短暂任务运行仅在线程内持久化按线程隔离
FilesystemBackend本地工作区、代码编辑持久保存在磁盘上宿主机本地机器
LocalShellBackend宿主机上的完整 Shell 执行(谨慎使用)持久保存在磁盘上宿主机本地机器
StoreBackend跨对话记忆、多智能体协作跨线程持久化命名空间隔离
CompositeBackend混合存储(例如本地代码 + 内存便签)因路由而异按路由隔离
ContextHubBackendLangSmith Hub 存储(用于 Managed Deep Agents)跨线程持久化Hub 仓库

快速起步 (Quickstart) ​

默认情况下,Deep Agents 使用存储在 LangGraph 状态中的 StateBackend。文件在同一个对话线程中保持有效,但在不同线程之间相互隔离。

若要让智能体直接在本地磁盘上操作,请传入 FilesystemBackend:

mermaid
graph TB
    Tools[Filesystem Tools] --> Backend[Backend]

    Backend --> State[State]
    Backend --> Disk[Filesystem]
    Backend --> Store[Store]
    Backend --> ContextHub[Context Hub]
    Backend --> Sandbox[Sandbox]
    Backend --> LocalShell[Local Shell]
    Backend --> Composite[Composite]
    Backend --> Custom[Custom]

    Composite --> Router{Routes}
    Router --> State
    Router --> Disk
    Router --> Store
    Router --> ContextHub

    Sandbox --> Execute["#43; execute tool"]
    LocalShell --> Execute["#43; execute tool"]

    classDef trigger fill:#F6FFDB,stroke:#6E8900,stroke-width:2px,color:#2E3900
    classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
    classDef decision fill:#FDF3FF,stroke:#7E65AE,stroke-width:2px,color:#504B5F
    classDef output fill:#EBD0F0,stroke:#885270,stroke-width:2px,color:#441E33

    class Tools trigger
    class Backend,State,Disk,Store,ContextHub,Sandbox,LocalShell,Composite,Custom process
    class Router decision
    class Execute output

内置后端 (Built-in backends) ​

StateBackend ​

默认后端。将所有文件保存在 LangGraph 图状态(agentState)中。文件在单次对话中保持有效,但不会跨线程共享。

  • 适用场景:无需持久化本地文件的短暂任务、测试与快速原型开发。
  • 工具支持:ls、read_file、write_file、edit_file、glob、grep。不支持 execute。

FilesystemBackend (本地磁盘) ​

让智能体直接读写本地磁盘上的指定根目录。

WARNING

该后端授予智能体对本地文件系统的直接读写权限。请务必谨慎使用,且仅在安全受控的环境中开启。

  • 适用场景:本地编码助手、需要在开发者机器上直接修改源码的项目。
  • 工具支持:ls、read_file、write_file、edit_file、glob、grep。
ts
import { createDeepAgent, StateBackend } from "deepagents";

// By default we provide a StateBackend
const agent = createDeepAgent();

// Under the hood, it looks like
const agent2 = createDeepAgent({
  backend: new StateBackend(),
});

LocalShellBackend (本地 Shell) ​

在本地文件系统的基础上,增加了在宿主机上执行原生 Shell 命令的 execute 工具。

WARNING

该后端授予智能体本地文件系统读写权限以及宿主机上的完全命令执行权限。具有极高风险,仅推荐在完全隔离的容器或沙箱中使用。

  • 适用场景:运行在隔离 Docker 容器内的自主编码智能体。
  • 工具支持:所有文件工具加上 execute。
ts
import { createDeepAgent, FilesystemBackend } from "deepagents";

const agent = createDeepAgent({
  model: "google:gemini-3.6-flash",
  backend: new FilesystemBackend({ rootDir: ".", virtualMode: true }),
});

StoreBackend (LangGraph Store 跨线程后端) ​

将文件持久化到 LangGraph 的分布式 BaseStore 中。这使得文件和记忆能够在不同的对话线程之间被持久化和跨会话访问。

  • 适用场景:跨会话记忆、用户长期文档、多智能体共享存储。
  • 工具支持:所有标准文件工具(无 Shell 执行)。

命名空间工厂 (Namespace factories) ​

为了在多租户生产环境中确保数据安全隔离,请传入命名空间工厂函数,根据请求上下文为每个用户或租户分配独立的路径命名空间:

ts
import { createDeepAgent, FilesystemBackend } from "deepagents";

const agent = createDeepAgent({
  model: "openai:gpt-5.5",
  backend: new FilesystemBackend({ rootDir: ".", virtualMode: true }),
});

ContextHubBackend ​

将文件存储在 LangSmith Hub 仓库中。这是 Managed Deep Agents 默认支持的托管云存储形式,方便团队统一管理指令、技能和配置资产。

CompositeBackend (复合路由后端) ​

复合后端是构建复杂智能体的核心工具。它允许你将虚拟文件系统中的不同目录路径分别路由到不同的底层存储后端。

例如:将 /workspace 路由到本地磁盘代码库,将 /notes 路由到内存临时状态,将 /shared 路由到跨线程的分布式 Store:

ts
import { createDeepAgent, FilesystemBackend } from "deepagents";

const agent = createDeepAgent({
  model: "anthropic:claude-sonnet-5",
  backend: new FilesystemBackend({ rootDir: ".", virtualMode: true }),
});

指定后端 (Specify a backend) ​

在调用 createDeepAgent 时,直接将已初始化的后端实例传递给 backend 参数。

路由到不同的后端 (Route to different backends) ​

通过 CompositeBackend,你可以轻松实现路径级别的安全沙箱与性能隔离。未匹配到专门路由规则的路径将回退至默认后端。

卸载二进制内容 (Offload binary content) ​

当工具生成庞大的二进制数据(如高清图像、音频文件或大尺寸 PDF)时,直接放入模型上下文会迅速耗尽 Token 预算。 后端协议支持将二进制数据直接流式转储至文件系统,模型仅接收轻量级的文件引用句柄。

自定义后端 (Custom backends) ​

你可以通过实现 BackendProtocolV2 接口,轻松对接自己的云存储(如 AWS S3、MinIO、数据库或企业内部专有系统)。

实现后端协议 (Implement the backend protocol) ​

一个标准的 V2 后端只需实现以下核心方法:

ts
import { createDeepAgent, FilesystemBackend } from "deepagents";

const agent = createDeepAgent({
  model: "openrouter:z-ai/glm-5.2",
  backend: new FilesystemBackend({ rootDir: ".", virtualMode: true }),
});

权限控制 (Permissions) ​

所有内置文件系统工具均无缝遵循声明式权限拦截规则。详情请参阅权限控制指南。

添加策略钩子 (Add policy hooks) ​

除了静态的 Glob 规则外,你还可以通过中间件或后端策略钩子实现动态拦截:例如禁止修改已锁定的 Git 分支、限制单次写入文件大小上限等。

多模态与二进制文件 (Multimodal and binary files) ​

在 BackendProtocolV2 规范下,Deep Agents 原生支持图像、PDF、视频和音频的多模态读写。

支持的 MIME 类型 ​

  • 图像:image/png、image/jpeg、image/webp、image/gif
  • 文档:application/pdf
  • 多媒体:audio/wav、audio/mpeg、video/mp4

读取二进制文件 ​

当调用 read_file 读取非文本文件时,后端会自动将二进制流打包为符合标准的多模态内容块返回给支持多模态的 LLM。

FileData 数据格式 ​

在内部协议中,多模态数据统一封装为包含 Base64 编码或原生 Uint8Array 的 FileData 结构。

从后端工厂迁移 (Migrate from backend factories) ​

在旧版本中,后端往往需要通过工厂函数进行延迟装配。在最新版本中,推荐直接实例化后端对象。

发生了哪些变更 ​

  • 废弃了繁琐的 BackendContext 包装。
  • 统一采用返回结构化 Result 对象的 BackendProtocolV2。

协议参考规范 (Protocol reference) ​

必需方法 (Required methods) ​

  • ls(path: string): Promise<LsResult>
  • read(path: string, options?: ReadOptions): Promise<ReadResult>
  • write(path: string, content: string | Uint8Array): Promise<WriteResult>
  • edit(path: string, oldStr: string, newStr: string): Promise<EditResult>

可选方法 (Optional methods) ​

  • glob(pattern: string): Promise<GlobResult>
  • grep(pattern: string, options?: GrepOptions): Promise<GrepResult>

沙箱扩展协议 (Sandbox extension) ​

若后端实现了 SandboxBackendProtocolV2,则必须额外实现 execute(command: string): Promise<ExecuteResult> 方法。

ts
import { createDeepAgent, FilesystemBackend } from "deepagents";

const agent = createDeepAgent({
  model: "fireworks:accounts/fireworks/models/glm-5p2",
  backend: new FilesystemBackend({ rootDir: ".", virtualMode: true }),
});
ts
import { createDeepAgent, FilesystemBackend } from "deepagents";

const agent = createDeepAgent({
  model: "baseten:zai-org/GLM-5.2",
  backend: new FilesystemBackend({ rootDir: ".", virtualMode: true }),
});
ts
import { createDeepAgent, FilesystemBackend } from "deepagents";

const agent = createDeepAgent({
  model: "ollama:north-mini-code-1.0",
  backend: new FilesystemBackend({ rootDir: ".", virtualMode: true }),
});

How it works:

  • Reads/writes real files under a configurable root_dir.
  • You can optionally set virtual_mode=True to sandbox and normalize paths under root_dir.
  • Uses secure path resolution, prevents unsafe symlink traversal when possible, can use ripgrep for fast grep.

Best for:

  • Local projects on your machine
  • CI sandboxes
  • Mounted persistent volumes

For a durable repository wiki that agents can read with these filesystem tools (from openwiki/), see OpenWiki.

TIP

Wrap FilesystemBackend in a CompositeBackend for most use cases. Deep Agents automatically write internal data to the backend, including offloaded large tool results (under /large_tool_results/) and conversation history (under /conversation_history/). When you use FilesystemBackend alone, these internal files are written to real disk under root_dir, mixing agent artifacts with your project files.

Use a CompositeBackend to route your project directory to FilesystemBackend while keeping internal paths in ephemeral StateBackend storage:

typescript
> import { createDeepAgent, CompositeBackend, FilesystemBackend, StateBackend } from "deepagents";
>
> const agent = createDeepAgent({
>   backend: new CompositeBackend(
>     new StateBackend(),
>     {
>       "/workspace/": new FilesystemBackend({ rootDir: "/path/to/project", virtualMode: true }),
>     },
>   ),
> });
> ```

>
> This way, agent reads and writes under `/workspace/` go to real disk, while offloaded tool results and other internal data stay in ephemeral state. See [Route to different backends](#route-to-different-backends) for more routing patterns.

### LocalShellBackend (local shell)

> [!WARNING]
>
> 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.
>
> **Appropriate use cases:**
>
> * Local development CLIs (coding assistants, development tools)
> * Personal development environments where you trust the agent's code
> * CI/CD pipelines with proper secret management
>
> **Inappropriate use cases:**
>
> * Production environments (such as web servers, APIs, multi-tenant systems)
> * Processing untrusted user input or executing untrusted code
>
> **Security risks:**
>
> * Agents can execute **arbitrary shell commands** with your user's permissions
> * Agents can read any accessible file, including secrets (API keys, credentials, `.env` files)
> * Secrets may be exposed
> * File modifications and command execution are **permanent and irreversible**
> * Commands run directly on your host system
> * Commands can consume unlimited CPU, memory, disk
>
> **Recommended safeguards:**
>
> 1. Enable [Human-in-the-Loop (HITL) middleware](/oss/javascript/deepagents/human-in-the-loop.md) to review and approve operations before execution. This is **strongly recommended**.
> 2. Run in dedicated development environments only. Never use on shared or production systems.
> 3. Use a [sandbox backend](/oss/javascript/deepagents/sandboxes.md) for production environments requiring shell execution.
>
> **Note:** `virtual_mode=True` provides no security with shell access enabled, since commands can access any path on the system.

```ts Google theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import { createDeepAgent, LocalShellBackend } from "deepagents";

const backend = new LocalShellBackend({ workingDirectory: "." });

const agent = createDeepAgent({
  model: "google:gemini-3.6-flash",
  backend,
});
ts
import { createDeepAgent, LocalShellBackend } from "deepagents";

const backend = new LocalShellBackend({ workingDirectory: "." });

const agent = createDeepAgent({
  model: "openai:gpt-5.5",
  backend,
});
ts
import { createDeepAgent, LocalShellBackend } from "deepagents";

const backend = new LocalShellBackend({ workingDirectory: "." });

const agent = createDeepAgent({
  model: "anthropic:claude-sonnet-5",
  backend,
});
ts
import { createDeepAgent, LocalShellBackend } from "deepagents";

const backend = new LocalShellBackend({ workingDirectory: "." });

const agent = createDeepAgent({
  model: "openrouter:z-ai/glm-5.2",
  backend,
});
ts
import { createDeepAgent, LocalShellBackend } from "deepagents";

const backend = new LocalShellBackend({ workingDirectory: "." });

const agent = createDeepAgent({
  model: "fireworks:accounts/fireworks/models/glm-5p2",
  backend,
});
ts
import { createDeepAgent, LocalShellBackend } from "deepagents";

const backend = new LocalShellBackend({ workingDirectory: "." });

const agent = createDeepAgent({
  model: "baseten:zai-org/GLM-5.2",
  backend,
});
ts
import { createDeepAgent, LocalShellBackend } from "deepagents";

const backend = new LocalShellBackend({ workingDirectory: "." });

const agent = createDeepAgent({
  model: "ollama:north-mini-code-1.0",
  backend,
});

How it works:

  • Extends FilesystemBackend with the execute tool for running shell commands on the host.
  • Commands run directly on your machine using subprocess.run(shell=True) with no sandboxing.
  • Supports timeout (default 120s), max_output_bytes (default 100,000), env, and inherit_env for environment variables.
  • Shell commands use root_dir as the working directory but can access any path on the system.

Best for:

  • Local coding assistants and development tools
  • Quick iteration during development when you trust the agent

StoreBackend (LangGraph store) ​

ts
import { createDeepAgent, StoreBackend } from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deployment

const agent = createDeepAgent({
  model: "google:gemini-3.6-flash",
  backend: new StoreBackend({
    namespace: (rt) => [rt.serverInfo.user.identity],
  }),
  store,
});
ts
import { createDeepAgent, StoreBackend } from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deployment

const agent = createDeepAgent({
  model: "openai:gpt-5.5",
  backend: new StoreBackend({
    namespace: (rt) => [rt.serverInfo.user.identity],
  }),
  store,
});
ts
import { createDeepAgent, StoreBackend } from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deployment

const agent = createDeepAgent({
  model: "anthropic:claude-sonnet-5",
  backend: new StoreBackend({
    namespace: (rt) => [rt.serverInfo.user.identity],
  }),
  store,
});
ts
import { createDeepAgent, StoreBackend } from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deployment

const agent = createDeepAgent({
  model: "openrouter:z-ai/glm-5.2",
  backend: new StoreBackend({
    namespace: (rt) => [rt.serverInfo.user.identity],
  }),
  store,
});
ts
import { createDeepAgent, StoreBackend } from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deployment

const agent = createDeepAgent({
  model: "fireworks:accounts/fireworks/models/glm-5p2",
  backend: new StoreBackend({
    namespace: (rt) => [rt.serverInfo.user.identity],
  }),
  store,
});
ts
import { createDeepAgent, StoreBackend } from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deployment

const agent = createDeepAgent({
  model: "baseten:zai-org/GLM-5.2",
  backend: new StoreBackend({
    namespace: (rt) => [rt.serverInfo.user.identity],
  }),
  store,
});
ts
import { createDeepAgent, StoreBackend } from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore(); // Good for local dev; omit for LangSmith Deployment

const agent = createDeepAgent({
  model: "ollama:north-mini-code-1.0",
  backend: new StoreBackend({
    namespace: (rt) => [rt.serverInfo.user.identity],
  }),
  store,
});

NOTE

When deploying to LangSmith Deployment, omit the store parameter. The platform automatically provisions a store for your agent.

TIP

The namespace parameter controls data isolation. For multi-user deployments, always set a namespace factory to isolate data per user or tenant.

How it works:

  • StoreBackend stores files in a LangGraph BaseStore provided by the runtime, enabling cross‑thread durable storage.

Best for:

  • When you already run with a configured LangGraph store (for example, Redis, Postgres, or cloud implementations behind BaseStore).
  • When you're deploying your agent through LangSmith Deployment (a store is automatically provisioned for your agent).

Namespace factories ​

A namespace factory controls where StoreBackend reads and writes data. It receives a LangGraph Runtime and returns a tuple of strings used as the store namespace. Use namespace factories to isolate data between users, tenants, or assistants.

Pass the namespace factory to the namespace parameter when constructing a StoreBackend:

python
NamespaceFactory = Callable[[Runtime], tuple[str, ...]]

The Runtime provides:

  • rt.context—User-supplied context passed via LangGraph's context schema (for example, user_id)

  • rt.serverInfo—Server-specific metadata when running on LangGraph Server (assistant ID, graph ID, authenticated user)

  • rt.executionInfo—Execution identity information (thread ID, run ID, checkpoint ID)

NOTE

The Runtime argument is available in deepagents>=1.9.1. Earlier 1.9.x releases passed a BackendContext instead—see migrating from BackendContext below. rt.serverInfo and rt.executionInfo require deepagents>=1.9.0.

Common namespace patterns:

typescript
import { StoreBackend } from "deepagents";

// Per-user: each user gets their own isolated storage
const backend = new StoreBackend({
  namespace: (rt) => [rt.serverInfo.user.identity],  
});

// Per-assistant: all users of the same assistant share storage
const backend = new StoreBackend({
  namespace: (rt) => [rt.serverInfo.assistantId],  
});

// Per-thread: storage scoped to a single conversation
const backend = new StoreBackend({
  namespace: (rt) => [rt.executionInfo.threadId],  
});

You can combine multiple components to create more specific scopes—for example, (user_id, thread_id) for per-user per-conversation isolation, or append a suffix like "filesystem" to disambiguate when the same scope uses multiple store namespaces.

Namespace components must contain only alphanumeric characters, hyphens, underscores, dots, @, +, colons, and tildes. Wildcards (*, ?) are rejected to prevent glob injection.

WARNING

The namespace parameter will be required in v1.9.0. Always set it explicitly for new code.

NOTE

When no namespace factory is provided, the legacy default uses the assistant_id from LangGraph config metadata. This means all users of the same assistant share the same storage. For multi-user going to production, always provide a namespace factory.

ContextHubBackend ​

NOTE

Before you begin: ContextHubBackend requires a Context Hub repo set up in LangSmith. Read the Context Hub concepts page first if you're unfamiliar with agent repos and skill repos.

ContextHubBackend stores your agent's filesystem in a LangSmith Context Hub repo. It can use a standalone repo or an agent repo that links out to skill repos.

Repo structure: In the Context Hub, an agent repo holds the agent's top-level instructions and configuration (for example, AGENTS.md, tools.json). It can link to one or more skill repos, each packaged as a reusable capability (for example, a SKILL.md with instructions for email formatting or code review). When you pass ContextHubBackend("my-agent"), the backend mounts the agent repo at the filesystem root; linked skill repos appear as subdirectories under /skills/.

This means your agent's context is intentionally spread across repos: one repo per agent, separate repos per skill. That separation lets skills be versioned, shared, and reused across multiple agents independently. If this feels fragmented, see Linked repos for the rationale.

Construct it with a repo identifier in owner/name or name format.

NOTE

Set LANGSMITH_API_KEY before using ContextHubBackend.

How it works:

  • Pulls the Hub repo tree lazily on first use, then serves reads from an in-memory cache.
  • Persists writes and edits as Hub commits and updates the cache after successful commits.
  • Uses optimistic parent-commit writes (parent_commit): each push targets the latest known commit hash.

Behavior and limits:

  • If the repo does not exist, first pull is treated as empty; the first successful write can create the repo.
  • If another writer advances the repo first, your stale parent-commit write can fail. Re-pull and retry on conflict.
  • upload_files() accepts UTF-8 text. Non-UTF-8 files are rejected per path with invalid_path.

Best for:

  • LangSmith-native durable filesystem persistence without separately wiring a LangGraph BaseStore.
  • Workflows that benefit from Hub commit history on filesystem changes.

CompositeBackend (router) ​

ts
import {
  createDeepAgent,
  CompositeBackend,
  StateBackend,
  StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore();

const agent = createDeepAgent({
  model: "google:gemini-3.6-flash",
  backend: new CompositeBackend(new StateBackend(), {
    "/memories/": new StoreBackend({
      namespace: () => ["memories"],
    }),
  }),
  store,
});
ts
import {
  createDeepAgent,
  CompositeBackend,
  StateBackend,
  StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore();

const agent = createDeepAgent({
  model: "openai:gpt-5.5",
  backend: new CompositeBackend(new StateBackend(), {
    "/memories/": new StoreBackend({
      namespace: () => ["memories"],
    }),
  }),
  store,
});
ts
import {
  createDeepAgent,
  CompositeBackend,
  StateBackend,
  StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore();

const agent = createDeepAgent({
  model: "anthropic:claude-sonnet-5",
  backend: new CompositeBackend(new StateBackend(), {
    "/memories/": new StoreBackend({
      namespace: () => ["memories"],
    }),
  }),
  store,
});
ts
import {
  createDeepAgent,
  CompositeBackend,
  StateBackend,
  StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore();

const agent = createDeepAgent({
  model: "openrouter:z-ai/glm-5.2",
  backend: new CompositeBackend(new StateBackend(), {
    "/memories/": new StoreBackend({
      namespace: () => ["memories"],
    }),
  }),
  store,
});
ts
import {
  createDeepAgent,
  CompositeBackend,
  StateBackend,
  StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore();

const agent = createDeepAgent({
  model: "fireworks:accounts/fireworks/models/glm-5p2",
  backend: new CompositeBackend(new StateBackend(), {
    "/memories/": new StoreBackend({
      namespace: () => ["memories"],
    }),
  }),
  store,
});
ts
import {
  createDeepAgent,
  CompositeBackend,
  StateBackend,
  StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore();

const agent = createDeepAgent({
  model: "baseten:zai-org/GLM-5.2",
  backend: new CompositeBackend(new StateBackend(), {
    "/memories/": new StoreBackend({
      namespace: () => ["memories"],
    }),
  }),
  store,
});
ts
import {
  createDeepAgent,
  CompositeBackend,
  StateBackend,
  StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";

const store = new InMemoryStore();

const agent = createDeepAgent({
  model: "ollama:north-mini-code-1.0",
  backend: new CompositeBackend(new StateBackend(), {
    "/memories/": new StoreBackend({
      namespace: () => ["memories"],
    }),
  }),
  store,
});

How it works:

  • CompositeBackend routes file operations to different backends based on path prefix.
  • Preserves the original path prefixes in listings and search results.

Best for:

  • When you want to give your agent both thread-scoped and cross-thread storage, a CompositeBackend allows you provide both a StateBackend and StoreBackend
  • When you have multiple sources of information that you want to provide to your agent as part of a single filesystem.
    • e.g. You have long-term memories stored under /memories/ in one Store and you also have a custom backend that has documentation accessible at /docs/.

Specify a backend ​

  • Pass a backend instance to createDeepAgent({ backend: ... }). The filesystem middleware uses it for all tooling.
  • The backend must implement AnyBackendProtocol (BackendProtocolV1 or BackendProtocolV2)—for example, new StateBackend(), new FilesystemBackend({ rootDir: "." }), new StoreBackend().
  • If omitted, the default is new StateBackend().

NOTE

Before version 1.9.0, only BackendProtocol was supported which is now BackendProtocolV1. V1 backends are automatically adapted to V2 at runtime via adaptBackendProtocol(). No code changes are required to keep using existing V1 backends. To update to v2, see update existing backends to v2.

Route to different backends ​

Route parts of the namespace to different backends. Commonly used to persist /memories/* across threads and keep everything else thread-scoped.

typescript
import { createDeepAgent, CompositeBackend, FilesystemBackend, StateBackend } from "deepagents";

const agent = createDeepAgent({
  backend: new CompositeBackend(
    new StateBackend(),
    {
      "/memories/": new FilesystemBackend({ rootDir: "/deepagents/myagent", virtualMode: true }),
    },
  ),
});

Behavior:

  • /workspace/plan.md → StateBackend (thread-scoped)
  • /memories/agent.md → FilesystemBackend under /deepagents/myagent
  • ls, glob, grep aggregate results and show original path prefixes.

Notes:

  • Longer prefixes win (for example, route "/memories/projects/" can override "/memories/").
  • For StoreBackend routing, ensure a store is provided via create_deep_agent(model=..., store=...) or provisioned by the platform.
  • Deep Agents write internal data (offloaded tool results, conversation history) to the default backend. Use StateBackend as the default to keep these artifacts ephemeral and avoid writing them to disk or a persistent store. See the FilesystemBackend tip for a complete example.

Offload binary content ​

Binary content offloading stores inline media in the backend instead of keeping base64 payloads in message history. This reduces checkpoint size for agents that read binary files or receive inline media in user messages. Offloading is opt-in and disabled by default.

Set offloadBinaryContent: true in createFilesystemMiddleware and pass the middleware in the middleware array to createDeepAgent. Use the same backend for the agent and the middleware.

Payloads are stored under /blobs/<sha256>.

Message history keeps content-addressed references. Before each model call, the middleware restores the original media from its cache or the backend. Re-reading unchanged content reuses the same blob; changed content creates a new blob.

Use a backend that stores blobs outside graph state, such as a sandbox or FilesystemBackend. Offloading has no effect when the blob path routes to StateBackend, including through CompositeBackend. If you keep StateBackend as the default backend, route the blob directory to external storage.

NOTE

Inline media in user messages is replaced at the next model call. The original input payload still appears in earlier checkpoints. Keep the blob storage available when resuming a thread so the middleware can restore its media.

Custom backends ​

Implement a custom backend to connect Deep Agents to storage systems such as databases, object stores, and remote filesystems. See community-built backends for examples.

Implement the backend protocol ​

Implement BackendProtocol (BackendProtocolV2) and provide the following methods:

MethodSignatureWhat it does
ls(path: string) => Promise<LsResult>List files and directories at the given path.
read(filePath: string, offset?, limit?) => Promise<ReadResult>Return file contents, optionally paginated. Binary files return Uint8Array content with a mimeType.
readRaw(filePath: string) => Promise<ReadRawResult>Return raw FileData (used internally by the framework).
write(filePath: string, content: string) => Promise<WriteResult>Create or overwrite a file.
edit(filePath: string, oldString: string, newString: string, replaceAll?: boolean) => Promise<EditResult>Find-and-replace within an existing file.
glob(pattern: string, path?: string) => Promise<GlobResult>Return paths matching a glob pattern.
grep(pattern: string, path?, glob?) => Promise<GrepResult>Search file contents for a literal string.

To also support the execute tool (running shell commands), implement SandboxBackendProtocol instead, which extends BackendProtocolV2 with an execute method.

All methods must return structured Result objects with an optional error field—do not throw on missing files or invalid patterns.

Example: S3-style backend skeleton

This skeleton maps filesystem paths to object keys. Fill in each method with your storage client's list, read, search, upload, and read-modify-write operations.

typescript
> import {
>   type BackendProtocolV2,
>   type EditResult,
>   type GlobResult,
>   type GrepResult,
>   type LsResult,
>   type ReadRawResult,
>   type ReadResult,
>   type WriteResult,
> } from "deepagents";
>
> class S3Backend implements BackendProtocolV2 {
>   constructor(private bucket: string, private prefix: string = "") {
>     this.prefix = prefix.replace(/\/$/, "");
>   }
>
>   private key(path: string): string {
>     return `${this.prefix}${path}`;
>   }
>
>   async ls(path: string): Promise<LsResult> {
>     ...
>   }
>
>   async read(filePath: string, offset?: number, limit?: number): Promise<ReadResult> {
>     ...
>   }
>
>   async readRaw(filePath: string): Promise<ReadRawResult> {
>     ...
>   }
>
>   async grep(pattern: string, path?: string | null, glob?: string | null): Promise<GrepResult> {
>     ...
>   }
>
>   async glob(pattern: string, path = "/"): Promise<GlobResult> {
>     ...
>   }
>
>   async write(filePath: string, content: string): Promise<WriteResult> {
>     ...
>   }
>
>   async edit(filePath: string, oldString: string, newString: string, replaceAll?: boolean): Promise<EditResult> {
>     ...
>   }
> }
> ```

## Permissions

Use [permissions](/oss/javascript/deepagents/permissions.md) to declaratively control which files and directories the agent can read or write. Permissions apply to the built-in filesystem tools and are evaluated before the backend is called.

For the full set of options including rule ordering, subagent permissions, and composite backend interactions, see the [permissions guide](/oss/javascript/deepagents/permissions.md).

## Add policy hooks

For custom validation logic beyond path-based allow/deny rules (rate limiting, audit logging, content inspection), enforce enterprise rules by subclassing or wrapping a backend.

Block writes/edits under selected prefixes (subclass):

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import { FilesystemBackend, type WriteResult, type EditResult } from "deepagents";

class GuardedBackend extends FilesystemBackend {
  private denyPrefixes: string[];

  constructor({ denyPrefixes, ...options }: { denyPrefixes: string[]; rootDir?: string }) {
    super(options);
    this.denyPrefixes = denyPrefixes.map(p => p.endsWith("/") ? p : p + "/");
  }

  async write(filePath: string, content: string): Promise<WriteResult> {
    if (this.denyPrefixes.some(p => filePath.startsWith(p))) {
      return { error: `Writes are not allowed under ${filePath}` };
    }
    return super.write(filePath, content);
  }

  async edit(filePath: string, oldString: string, newString: string, replaceAll = false): Promise<EditResult> {
    if (this.denyPrefixes.some(p => filePath.startsWith(p))) {
      return { error: `Edits are not allowed under ${filePath}` };
    }
    return super.edit(filePath, oldString, newString, replaceAll);
  }
}

Generic wrapper (works with any backend):

typescript
import {
  type BackendProtocolV2,
  type LsResult,
  type ReadResult,
  type ReadRawResult,
  type GrepResult,
  type GlobResult,
  type WriteResult,
  type EditResult,
} from "deepagents";

class PolicyWrapper implements BackendProtocolV2 {
  private denyPrefixes: string[];

  constructor(private inner: BackendProtocolV2, denyPrefixes: string[] = []) {
    this.denyPrefixes = denyPrefixes.map(p => p.endsWith("/") ? p : p + "/");
  }

  private isDenied(path: string): boolean {
    return this.denyPrefixes.some(p => path.startsWith(p));
  }

  ls(path: string): Promise<LsResult> { return this.inner.ls(path); }
  read(filePath: string, offset?: number, limit?: number): Promise<ReadResult> { return this.inner.read(filePath, offset, limit); }
  readRaw(filePath: string): Promise<ReadRawResult> { return this.inner.readRaw(filePath); }
  grep(pattern: string, path?: string | null, glob?: string | null): Promise<GrepResult> { return this.inner.grep(pattern, path, glob); }
  glob(pattern: string, path?: string): Promise<GlobResult> { return this.inner.glob(pattern, path); }

  async write(filePath: string, content: string): Promise<WriteResult> {
    if (this.isDenied(filePath)) return { error: `Writes are not allowed under ${filePath}` };
    return this.inner.write(filePath, content);
  }

  async edit(filePath: string, oldString: string, newString: string, replaceAll = false): Promise<EditResult> {
    if (this.isDenied(filePath)) return { error: `Edits are not allowed under ${filePath}` };
    return this.inner.edit(filePath, oldString, newString, replaceAll);
  }
}

Multimodal and binary files ​

NOTE

Multi-modal file support (PDFs, audio, video) requires deepagents>=1.9.0.

V2 backends support binary files natively. When read() encounters a binary file (determined by MIME type from the file extension), it returns a ReadResult with Uint8Array content and the corresponding mimeType. Text files return string content.

Supported MIME types ​

CategoryExtensionsMIME types
Images.png, .jpg/.jpeg, .gif, .webp, .svg, .heic, .heifimage/png, image/jpeg, image/gif, image/webp, image/svg+xml, image/heic, image/heif
Audio.mp3, .wav, .aiff, .aac, .ogg, .flacaudio/mpeg, audio/wav, audio/aiff, audio/aac, audio/ogg, audio/flac
Video.mp4, .webm, .mpeg/.mpg, .mov, .avi, .flv, .wmv, .3gppvideo/mp4, video/webm, video/mpeg, video/quicktime, video/x-msvideo, video/x-flv, video/x-ms-wmv, video/3gpp
Documents.pdf, .ppt, .pptxapplication/pdf, application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.presentation
Text.txt, .html, .json, .js, .ts, .py, etc.text/plain, text/html, application/json, etc.

Read binary files ​

typescript
const result = await backend.read("/workspace/screenshot.png");

if (result.error) {
  console.error(result.error);
} else if (result.content instanceof Uint8Array) {
  // Binary file — content is Uint8Array, mimeType is set
  console.log(`Binary file: ${result.mimeType}`); // "image/png"
} else {
  // Text file — content is string
  console.log(`Text file: ${result.mimeType}`); // "text/plain"
}

FileData format ​

FileData is the type used to store file content in state and store backends.

typescript
type FileData =
  // Current format (v2)
  | {
      content: string | Uint8Array; // string for text, Uint8Array for binary
      mimeType: string;             // e.g. "text/plain", "image/png"
      created_at: string;           // ISO 8601 timestamp
      modified_at: string;          // ISO 8601 timestamp
    }
  // Legacy format (v1)
  | {
      content: string[];            // array of lines
      created_at: string;           // ISO 8601 timestamp
      modified_at: string;          // ISO 8601 timestamp
    };

Backends may encounter either format when reading from state or store. The framework handles both transparently. New writes default to the v2 format. During rolling deployments where older readers need the legacy format, pass fileFormat: "v1" to the backend constructor (e.g., new StoreBackend({ fileFormat: "v1" })).

Migrate from backend factories ​

WARNING

The backend factory pattern is deprecated as of deepagents 1.9.0. Pass pre-constructed backend instances directly instead of factory functions.

Previously, backends like StateBackend and StoreBackend required a factory function that received a runtime object, because they needed runtime context (state, store) to operate. Backends now resolve this context internally via LangGraph's get_config(), get_store(), and get_runtime() helpers, so you can pass instances directly.

What changed ​

Before (deprecated)After
backend=lambda rt: StateBackend(rt)backend=StateBackend()
backend=lambda rt: StoreBackend(rt)backend=StoreBackend()
backend=lambda rt: CompositeBackend(default=StateBackend(rt), ...)backend=CompositeBackend(default=StateBackend(), ...)
backend: (config) => new StateBackend(config)backend: new StateBackend()
backend: (config) => new StoreBackend(config)backend: new StoreBackend()

Deprecated APIs ​

DeprecatedReplacement
BackendFactory typePass a backend instance directly
BackendRuntime interfaceBackends resolve context internally
StateBackend(runtime, options?) constructor overloadnew StateBackend(options?)
StoreBackend(stateAndStore, options?) constructor overloadnew StoreBackend(options?)
filesUpdate field on WriteResult and EditResultState writes are now handled internally by the backend

NOTE

The factory pattern still works at runtime and emits a deprecation warning. Update your code to use direct instances before the next major version.

Migration example ​

typescript
// Before (deprecated)
import { createDeepAgent, CompositeBackend, StateBackend, StoreBackend } from "deepagents";

const agent = createDeepAgent({
  backend: (config) => new CompositeBackend(
    new StateBackend(config),
    { "/memories/": new StoreBackend(config, {
      namespace: (rt) => [rt.serverInfo.user.identity],
    }) },
  ),
});

// After
const agent = createDeepAgent({
  backend: new CompositeBackend(
    new StateBackend(),
    { "/memories/": new StoreBackend({
      namespace: (rt) => [rt.serverInfo.user.identity],
    }) },
  ),
});

Migrating from BackendContext ​

In deepagents>=0.5.2 (Python) and deepagents>=1.9.1 (TypeScript), namespace factories receive a LangGraph Runtime directly instead of a BackendContext wrapper. The old BackendContext form still works via backwards-compatible .runtime and .state accessors, but those accessors emit a deprecation warning and will be removed in deepagents>=0.7.

What changed:

  • The factory argument is now a Runtime, not a BackendContext.
  • Drop the .runtime accessor—for example, ctx.runtime.context.user_id becomes rt.server_info.user.identity.
  • There is no direct replacement for ctx.state. Namespace info should be read-only and stable for the lifetime of a run, whereas state is mutable and changes step-to-step—deriving a namespace from it risks data ending up under inconsistent keys. If you have a use case that requires reading agent state, please open an issue.
typescript
// Before (deprecated, removed in v0.7)
new StoreBackend({
  namespace: (ctx) => [ctx.runtime.context.userId],  
});

// After
new StoreBackend({
  namespace: (rt) => [rt.serverInfo.user.identity],  
});

Protocol reference ​

Backends must implement BackendProtocol.

Required methods:

  • ls(path: str) -> LsResult
    • Return entries with at least path. Include is_dir, size, modified_at when available. Sort by path for deterministic output.
  • read(file_path: str, offset: int = 0, limit: int = 2000) -> ReadResult
    • Return file data on success. On missing file, return ReadResult(error="Error: File '/x' not found").
  • grep(pattern: str, path: Optional[str] = None, glob: Optional[str] = None) -> GrepResult
    • Return structured matches. On error, return GrepResult(error="...") (do not raise).
  • glob(pattern: str, path: Optional[str] = None) -> GlobResult
    • Return matched files as FileInfo entries (empty list if none).
  • write(file_path: str, content: str) -> WriteResult
    • Create-only. On conflict, return WriteResult(error=...). On success, set path and for state backends set files_update={...}; external backends should use files_update=None.
  • edit(file_path: str, old_string: str, new_string: str, replace_all: bool = False) -> EditResult
    • Enforce uniqueness of old_string unless replace_all=True. If not found, return error. Include occurrences on success.

Supporting types:

  • LsResult(error, entries)—entries is a list[FileInfo] on success, None on failure.
  • ReadResult(error, file_data)—file_data is a FileData dict on success, None on failure.
  • GrepResult(error, matches)—matches is a list[GrepMatch] on success, None on failure.
  • GlobResult(error, matches)—matches is a list[FileInfo] on success, None on failure.
  • WriteResult(error, path, files_update)
  • EditResult(error, path, files_update, occurrences)
  • FileInfo with fields: path (required), optionally is_dir, size, modified_at.
  • GrepMatch with fields: path, line, text.
  • FileData with fields: content (str), encoding ("utf-8" or "base64"), created_at, modified_at. :::

Backends implement BackendProtocolV2. All query methods return structured Result objects with { error?: string, ...data }.

Required methods ​

  • ls(path: string) → LsResult

    • List files and directories in the specified directory (non-recursive). Directories have a trailing / in their path and is_dir=true. Include is_dir, size, modified_at when available.
  • read(filePath: string, offset?: number, limit?: number) → ReadResult

    • Read file content. For text files, content is paginated by line offset/limit (default offset 0, limit 500). For binary files, the full raw Uint8Array content is returned with the mimeType field set. On missing file, return { error: "File '/x' not found" }.
  • readRaw(filePath: string) → ReadRawResult

    • Read file content as raw FileData. Returns the full file data including timestamps.
  • grep(pattern: string, path?: string | null, glob?: string | null) → GrepResult

    • Search file contents for a literal text pattern. Binary files (determined by MIME type) are skipped. On failure, return { error: "..." }.
  • glob(pattern: string, path?: string) → GlobResult

    • Return files matching a glob pattern as FileInfo entries.
  • write(filePath: string, content: string) → WriteResult

    • Create-only semantics. On conflict, return { error: "..." }. On success, set path and for state backends set filesUpdate={...}; external backends should use filesUpdate=null.
  • edit(filePath: string, oldString: string, newString: string, replaceAll?: boolean) → EditResult

    • Enforce uniqueness of oldString unless replaceAll=true. If not found, return error. Include occurrences on success.

Optional methods ​

  • uploadFiles(files: Array<[string, Uint8Array]>) → FileUploadResponse[]—Upload multiple files (for sandbox backends).
  • downloadFiles(paths: string[]) → FileDownloadResponse[]—Download multiple files (for sandbox backends).

Result types ​

TypeSuccess fieldsError field
ReadResultcontent?: string | Uint8Array, mimeType?: stringerror
ReadRawResultdata?: FileDataerror
LsResultfiles?: FileInfo[]error
GlobResultfiles?: FileInfo[]error
GrepResultmatches?: GrepMatch[]error
WriteResultpath?: stringerror
EditResultpath?: string, occurrences?: numbererror

Supporting types ​

  • FileInfo—path (required), optionally is_dir, size, modified_at.
  • GrepMatch—path, line (1-indexed), text.
  • FileData—File content with timestamps. See FileData format.

Sandbox extension ​

SandboxBackendProtocolV2 extends BackendProtocolV2 with:

  • execute(command: string) → ExecuteResponse—Run a shell command in the sandbox.
  • readonly id: string—Unique identifier for the sandbox instance.

Update existing backends to V2 ​

Migration guide

Method renames ​

V1 methodV2 methodReturn type change
lsInfo(path)ls(path)FileInfo[] → LsResult
read(filePath, offset, limit)read(filePath, offset, limit)string → ReadResult
readRaw(filePath)readRaw(filePath)FileData → ReadRawResult
grepRaw(pattern, path, glob)grep(pattern, path, glob)GrepMatch[] | string → GrepResult
globInfo(pattern, path)glob(pattern, path)FileInfo[] → GlobResult
write(...)write(...)Unchanged (WriteResult)
edit(...)edit(...)Unchanged (EditResult)

Type renames ​

V1 typeV2 type
BackendProtocolBackendProtocolV2
SandboxBackendProtocolSandboxBackendProtocolV2

Adaptation utilities ​

If you have existing V1 backends that you need to use with V2-only code, use the adaptation functions:

typescript
> import { adaptBackendProtocol, adaptSandboxProtocol } from "deepagents";
>
> // Adapt a V1 backend to V2
> const v2Backend = adaptBackendProtocol(v1Backend);
>
> // Adapt a V1 sandbox to V2
> const v2Sandbox = adaptSandboxProtocol(v1Sandbox);
> ```

>
> > [!NOTE]
> >
> > The framework auto-adapts V1 backends passed to `createDeepAgent()`. Manual adaptation is only needed when calling protocol methods directly.

## See also

* [OpenWiki](/oss/openwiki/overview.md): Generate durable repository Markdown that agents read through filesystem tools
* [Memory](/oss/javascript/deepagents/memory.md): Filesystem-backed long-term memory
* [Sandboxes](/oss/javascript/deepagents/sandboxes.md): Isolated filesystem and shell execution

***

[Connect these docs](/use-these-docs.md) to your agent of choice via MCP for real-time answers.

[Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/oss/deepagents/backends.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).