| title | Backends |
|---|---|
| description | Choose and configure filesystem backends for Deep Agents. You can specify routes to different backends, implement virtual filesystems, and enforce policies. |
import BackendStatePy from '/snippets/backend-state-py.mdx'; import BackendStateJs from '/snippets/backend-state-js.mdx'; import BackendFilesystemPy from '/snippets/backend-filesystem-py.mdx'; import BackendFilesystemJs from '/snippets/backend-filesystem-js.mdx'; import BackendLocalShellPy from '/snippets/backend-local-shell-py.mdx'; import BackendLocalShellJs from '/snippets/backend-local-shell-js.mdx'; import BackendStorePy from '/snippets/backend-store-py.mdx'; import BackendStoreJs from '/snippets/backend-store-js.mdx'; import BackendCompositePy from '/snippets/backend-composite-py.mdx'; import BackendCompositeJs from '/snippets/backend-composite-js.mdx';
Deep Agents expose a filesystem surface to the agent via tools like ls, read_file, write_file, edit_file, glob, and grep. These tools operate through a pluggable backend. The read_file tool natively supports image files (.png, .jpg, .jpeg, .gif, .webp) across all backends, returning them as multimodal content blocks.
:::js
The read_file tool natively supports binary files (images, PDFs, audio, video) across all backends, returning a ReadResult with typed content and mimeType.
:::
Sandboxes and the @[LocalShellBackend] also provide an execute tool.
This page explains how to:
- choose a backend,
- route different paths to different backends,
- implement your own virtual filesystem (e.g., S3 or Postgres),
- set permissions on filesystem access, :::js
- add policy hooks, work with binary and multimodal files, :::
- comply with the backend protocol, :::js
- and update existing backends to v2. :::
Here are a few prebuilt filesystem backends that you can quickly use with your deep agent:
| Built-in backend | Description |
|---|---|
| Default | agent = create_deep_agent(model="google_genai:gemini-3.1-pro-preview") Ephemeral in state. The default filesystem backend for an agent is stored in langgraph state. Note that this filesystem only persists for a single thread. |
| Local filesystem persistence | agent = create_deep_agent(model="google_genai:gemini-3.1-pro-preview", backend=FilesystemBackend(root_dir="/Users/nh/Desktop/")) This gives the deep agent access to your local machine's filesystem. You can specify the root directory that the agent has access to. Note that any provided root_dir must be an absolute path. |
| Durable store (LangGraph store) | agent = create_deep_agent(model="google_genai:gemini-3.1-pro-preview", backend=StoreBackend()) This gives the agent access to long-term storage that is persisted across threads. This is great for storing longer term memories or instructions that are applicable to the agent over multiple executions. |
| Sandbox | agent = create_deep_agent(model="google_genai:gemini-3.1-pro-preview", backend=sandbox) Execute code in isolated environments. Sandboxes provide filesystem tools plus the execute tool for running shell commands. Choose from Modal, Daytona, Deno, or local VFS. |
| Local shell | agent = create_deep_agent(model="google_genai:gemini-3.1-pro-preview", backend=LocalShellBackend(root_dir=".", env={"PATH": "/usr/bin:/bin"})) Filesystem and shell execution directly on the host. No isolationβuse only in controlled development environments. See security considerations below. |
| Composite | Ephemeral by default, /memories/ persisted. The Composite backend is maximally flexible. You can specify different routes in the filesystem to point towards different backends. See Composite routing below for a ready-to-paste example. |
graph TB
Tools[Filesystem Tools] --> Backend[Backend]
Backend --> State[State]
Backend --> Disk[Filesystem]
Backend --> Store[Store]
Backend --> Sandbox[Sandbox]
Backend --> LocalShell[Local Shell]
Backend --> Composite[Composite]
Backend --> Custom[Custom]
Composite --> Router{Routes}
Router --> State
Router --> Disk
Router --> Store
Sandbox --> Execute["#43; execute tool"]
LocalShell --> Execute["#43; execute tool"]
classDef trigger fill:#DCFCE7,stroke:#16A34A,stroke-width:2px,color:#14532D
classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:2px,color:#1E3A8A
classDef decision fill:#FEF3C7,stroke:#F59E0B,stroke-width:2px,color:#78350F
classDef output fill:#F3E8FF,stroke:#9333EA,stroke-width:2px,color:#581C87
class Tools trigger
class Backend,State,Disk,Store,Sandbox,LocalShell,Composite,Custom process
class Router decision
class Execute output
:::python :::
:::js :::
How it works:
- Stores files in LangGraph agent state for the current thread via @[
StateBackend]. - Persists across multiple agent turns on the same thread via checkpoints.
Best for:
- A scratch pad for the agent to write intermediate results.
- Automatic eviction of large tool outputs which the agent can then read back in piece by piece.
Note that this backend is shared between the supervisor agent and subagents, and any files a subagent writes will remain in the LangGraph agent state even after that subagent's execution is complete. Those files will continue to be available to the supervisor agent and other subagents.
@[FilesystemBackend] reads and writes real files under a configurable root directory.
Appropriate use cases:
- Local development CLIs (coding assistants, development tools)
- CI/CD pipelines (see security considerations below)
Inappropriate use cases:
- Web servers or HTTP APIs - use
StateBackend,StoreBackend, or a sandbox backend instead
Security risks:
- Agents can read any accessible file, including secrets (API keys, credentials,
.envfiles) - Combined with network tools, secrets may be exfiltrated via SSRF attacks
- File modifications are permanent and irreversible
Recommended safeguards:
- Enable Human-in-the-Loop (HITL) middleware to review sensitive operations.
- Exclude secrets from accessible filesystem paths (especially in CI/CD).
- Use a sandbox backend for production environments requiring filesystem interaction.
- Always use
virtual_mode=Truewithroot_dirto enable path-based access restrictions (blocks..,~, and absolute paths outside root). Note that the default (virtual_mode=False) provides no security even withroot_dirset.
:::python :::
:::js :::
How it works:
- Reads/writes real files under a configurable
root_dir. - You can optionally set
virtual_mode=Trueto sandbox and normalize paths underroot_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
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,
.envfiles) - 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:
- Enable Human-in-the-Loop (HITL) middleware to review and approve operations before execution. This is strongly recommended.
- Run in dedicated development environments only. Never use on shared or production systems.
- Use a sandbox backend 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.
:::python :::
:::js :::
How it works:
- Extends
FilesystemBackendwith theexecutetool 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, andinherit_envfor environment variables. - Shell commands use
root_diras 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
:::python :::
:::js :::
The `namespace` parameter controls data isolation. For multi-user deployments, always set a [namespace factory](/oss/deepagents/backends#namespace-factories) 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).
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:
NamespaceFactory = Callable[[Runtime], tuple[str, ...]]The Runtime provides:
rt.contextβ User-supplied context passed via LangGraph's context schema (for example,user_id) :::pythonrt.server_infoβ Server-specific metadata when running on LangGraph Server (assistant ID, graph ID, authenticated user)rt.execution_infoβ Execution identity information (thread ID, run ID, checkpoint ID) ::: :::jsrt.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) :::
:::python
The Runtime argument is available in deepagents>=0.5.2. Earlier 0.5.x releases passed a BackendContext instead β see migrating from BackendContext below. rt.server_info and rt.execution_info require deepagents>=0.5.0.
:::
:::js
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:
:::python
from deepagents.backends import StoreBackend
# Per-user: each user gets their own isolated storage
backend = StoreBackend(
namespace=lambda rt: (rt.server_info.user.identity,), # [!code highlight]
)
# Per-assistant: all users of the same assistant share storage
backend = StoreBackend(
namespace=lambda rt: (
rt.server_info.assistant_id, # [!code highlight]
),
)
# Per-thread: storage scoped to a single conversation
backend = StoreBackend(
namespace=lambda rt: (
rt.execution_info.thread_id, # [!code highlight]
),
):::
:::js
import { StoreBackend } from "deepagents";
// Per-user: each user gets their own isolated storage
const backend = new StoreBackend({
namespace: (rt) => [rt.serverInfo.user.identity], // [!code highlight]
});
// Per-assistant: all users of the same assistant share storage
const backend = new StoreBackend({
namespace: (rt) => [rt.serverInfo.assistantId], // [!code highlight]
});
// Per-thread: storage scoped to a single conversation
const backend = new StoreBackend({
namespace: (rt) => [rt.executionInfo.threadId], // [!code highlight]
});:::
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.
:::python
The namespace parameter will be required in v0.5.0. Always set it explicitly for new code.
:::
:::js
The namespace parameter will be required in v1.9.0. Always set it explicitly for new code.
:::
:::python :::
:::js :::
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 ephemeral and cross-thread storage, a
CompositeBackendallows you provide both aStateBackendandStoreBackend - 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/.
- e.g. You have long-term memories stored under
:::python
- Pass a backend instance to
create_deep_agent(model=..., backend=...). The filesystem middleware uses it for all tooling. - The backend must implement
BackendProtocol(for example,StateBackend(),FilesystemBackend(root_dir="."),StoreBackend()). - If omitted, the default is
StateBackend(). :::
:::js
- Pass a backend instance to
createDeepAgent({ backend: ... }). The filesystem middleware uses it for all tooling. - The backend must implement
AnyBackendProtocol(BackendProtocolV1orBackendProtocolV2) β for example,new StateBackend(),new FilesystemBackend({ rootDir: "." }),new StoreBackend(). - If omitted, the default is
new StateBackend().
Route parts of the namespace to different backends. Commonly used to persist /memories/* and keep everything else ephemeral.
:::python
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, FilesystemBackend
agent = create_deep_agent(
model="google_genai:gemini-3.1-pro-preview",
backend=CompositeBackend(
default=StateBackend(),
routes={
"/memories/": FilesystemBackend(root_dir="/deepagents/myagent", virtual_mode=True),
},
)
):::
:::js
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(ephemeral)/memories/agent.mdβFilesystemBackendunder/deepagents/myagentls,glob,grepaggregate 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.
Build a custom backend to project a remote or database filesystem (e.g., S3 or Postgres) into the tools namespace.
Design guidelines:
- Paths are absolute (
/x/y.txt). Decide how to map them to your storage keys/rows. - Implement
lsandglobefficiently (server-side filtering where available, otherwise local filter). - For external persistence (S3, Postgres, etc.), return
files_update=None(Python) or omitfilesUpdate(JS) in write/edit results β only in-memory state backends need to return a files update dict.
:::python
- Use
lsandglobas the method names. - Return structured result types with an
errorfield for missing files or invalid patterns (do not raise). :::
:::js
- Use
lsandglobas the method names. - All query methods (
ls,read,readRaw,grep,glob) must return structured Result objects (e.g.,LsResult,ReadResult) with an optionalerrorfield. - Support binary files in
read()by returningUint8Arraycontent with the appropriatemimeType. :::
S3-style outline:
:::python
from deepagents.backends.protocol import (
BackendProtocol, WriteResult, EditResult, LsResult, ReadResult, GrepResult, GlobResult,
)
class S3Backend(BackendProtocol):
def __init__(self, bucket: str, prefix: str = ""):
self.bucket = bucket
self.prefix = prefix.rstrip("/")
def _key(self, path: str) -> str:
return f"{self.prefix}{path}"
def ls(self, path: str) -> LsResult:
# List objects under _key(path); build FileInfo entries (path, size, modified_at)
...
def read(self, file_path: str, offset: int = 0, limit: int = 2000) -> ReadResult:
# Fetch object; return ReadResult(file_data=...) or ReadResult(error=...)
...
def grep(self, pattern: str, path: str | None = None, glob: str | None = None) -> GrepResult:
# Optionally filter serverβside; else list and scan content
...
def glob(self, pattern: str, path: str = "/") -> GlobResult:
# Apply glob relative to path across keys
...
def write(self, file_path: str, content: str) -> WriteResult:
# Enforce createβonly semantics; return WriteResult(path=file_path, files_update=None)
...
def edit(self, file_path: str, old_string: str, new_string: str, replace_all: bool = False) -> EditResult:
# Read β replace (respect uniqueness vs replace_all) β write β return occurrences
...:::
:::js
import {
type BackendProtocolV2,
type LsResult,
type ReadResult,
type ReadRawResult,
type GrepResult,
type GlobResult,
type WriteResult,
type EditResult,
} 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> {
// List objects under key(path); return { files: [...] }
...
}
async read(filePath: string, offset?: number, limit?: number): Promise<ReadResult> {
// Fetch object; return { content, mimeType }
// For binary files, return Uint8Array content
...
}
async readRaw(filePath: string): Promise<ReadRawResult> {
// Return { data: FileData }
...
}
async grep(pattern: string, path?: string | null, glob?: string | null): Promise<GrepResult> {
// Search text files; skip binary; return { matches: [...] }
...
}
async glob(pattern: string, path = "/"): Promise<GlobResult> {
// Apply glob relative to path; return { files: [...] }
...
}
async write(filePath: string, content: string): Promise<WriteResult> {
// Enforce create-only semantics; return { path: filePath, filesUpdate: null }
...
}
async edit(filePath: string, oldString: string, newString: string, replaceAll?: boolean): Promise<EditResult> {
// Read β replace β write β return { path, occurrences }
...
}
}:::
Postgres-style outline:
:::python
- Table
files(path text primary key, content text, created_at timestamptz, modified_at timestamptz) - Map tool operations onto SQL:
lsusesWHERE path LIKE $1 || '%'globfilter in SQL or fetch then apply glob in Pythongrepcan fetch candidate rows by extension or last modified time, then scan lines :::
:::js
- Table
files(path text primary key, content text, mime_type text, created_at timestamptz, modified_at timestamptz) - Map tool operations onto SQL:
lsusesWHERE path LIKE $1 || '%'β returnLsResultglobfilter in SQL or fetch then apply glob locally β returnGlobResultgrepcan fetch candidate rows by extension or last modified time, then scan lines (skip rows wheremime_typeis binary) β returnGrepResult:::
Use permissions 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.
:::python
from deepagents import create_deep_agent, FilesystemPermission
agent = create_deep_agent(
model="google_genai:gemini-3.1-pro-preview",
backend=CompositeBackend(
default=StateBackend(),
routes={
"/memories/": StoreBackend(
namespace=lambda rt: (rt.server_info.user.identity,),
),
"/policies/": StoreBackend(
namespace=lambda rt: (rt.context.org_id,),
),
},
),
permissions=[
FilesystemPermission(
operations=["write"],
paths=["/policies/**"],
mode="deny",
),
],
):::
For the full set of options including rule ordering, subagent permissions, and composite backend interactions, see the permissions guide.
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):
:::python
from deepagents.backends.filesystem import FilesystemBackend
from deepagents.backends.protocol import WriteResult, EditResult
class GuardedBackend(FilesystemBackend):
def __init__(self, *, deny_prefixes: list[str], **kwargs):
super().__init__(**kwargs)
self.deny_prefixes = [p if p.endswith("/") else p + "/" for p in deny_prefixes]
def write(self, file_path: str, content: str) -> WriteResult:
if any(file_path.startswith(p) for p in self.deny_prefixes):
return WriteResult(error=f"Writes are not allowed under {file_path}")
return super().write(file_path, content)
def edit(self, file_path: str, old_string: str, new_string: str, replace_all: bool = False) -> EditResult:
if any(file_path.startswith(p) for p in self.deny_prefixes):
return EditResult(error=f"Edits are not allowed under {file_path}")
return super().edit(file_path, old_string, new_string, replace_all):::
:::js
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):
:::python
from deepagents.backends.protocol import (
BackendProtocol, WriteResult, EditResult, LsResult, ReadResult, GrepResult, GlobResult,
)
class PolicyWrapper(BackendProtocol):
def __init__(self, inner: BackendProtocol, deny_prefixes: list[str] | None = None):
self.inner = inner
self.deny_prefixes = [p if p.endswith("/") else p + "/" for p in (deny_prefixes or [])]
def _deny(self, path: str) -> bool:
return any(path.startswith(p) for p in self.deny_prefixes)
def ls(self, path: str) -> LsResult:
return self.inner.ls(path)
def read(self, file_path: str, offset: int = 0, limit: int = 2000) -> ReadResult:
return self.inner.read(file_path, offset=offset, limit=limit)
def grep(self, pattern: str, path: str | None = None, glob: str | None = None) -> GrepResult:
return self.inner.grep(pattern, path, glob)
def glob(self, pattern: str, path: str = "/") -> GlobResult:
return self.inner.glob(pattern, path)
def write(self, file_path: str, content: str) -> WriteResult:
if self._deny(file_path):
return WriteResult(error=f"Writes are not allowed under {file_path}")
return self.inner.write(file_path, content)
def edit(self, file_path: str, old_string: str, new_string: str, replace_all: bool = False) -> EditResult:
if self._deny(file_path):
return EditResult(error=f"Edits are not allowed under {file_path}")
return self.inner.edit(file_path, old_string, new_string, replace_all):::
:::js
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);
}
}:::
:::js
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.
| Category | Extensions | MIME types |
|---|---|---|
| Images | .png, .jpg/.jpeg, .gif, .webp, .svg, .heic, .heif |
image/png, image/jpeg, image/gif, image/webp, image/svg+xml, image/heic, image/heif |
| Audio | .mp3, .wav, .aiff, .aac, .ogg, .flac |
audio/mpeg, audio/wav, audio/aiff, audio/aac, audio/ogg, audio/flac |
| Video | .mp4, .webm, .mpeg/.mpg, .mov, .avi, .flv, .wmv, .3gpp |
video/mp4, video/webm, video/mpeg, video/quicktime, video/x-msvideo, video/x-flv, video/x-ms-wmv, video/3gpp |
| Documents | .pdf, .ppt, .pptx |
application/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. |
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 is the type used to store file content in state and store backends.
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" })).
:::
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.
| 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() |
:::python
| Deprecated | Replacement |
|---|---|
Passing a callable to backend= in create_deep_agent |
Pass a backend instance directly |
runtime constructor argument on StateBackend(runtime) |
StateBackend() (no arguments needed) |
runtime constructor argument on StoreBackend(runtime) |
StoreBackend() or StoreBackend(namespace=..., store=...) |
files_update field on WriteResult and EditResult |
State writes are now handled internally by the backend |
Command wrapping in middleware write/edit tools |
Tools return plain strings; no Command(update=...) needed |
:::
:::js
| Deprecated | Replacement |
|---|---|
BackendFactory type |
Pass a backend instance directly |
BackendRuntime interface |
Backends resolve context internally |
StateBackend(runtime, options?) constructor overload |
new StateBackend(options?) |
StoreBackend(stateAndStore, options?) constructor overload |
new StoreBackend(options?) |
filesUpdate field on WriteResult and EditResult |
State writes are now handled internally by the backend |
:::
The factory pattern still works at runtime and emits a deprecation warning. Update your code to use direct instances before the next major version.:::python
# Before (deprecated)
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
agent = create_deep_agent(
model="google_genai:gemini-3.1-pro-preview",
backend=lambda rt: CompositeBackend(
default=StateBackend(rt),
routes={"/memories/": StoreBackend(rt, namespace=lambda rt: (rt.server_info.user.identity,))},
),
)
# After
agent = create_deep_agent(
model="google_genai:gemini-3.1-pro-preview",
backend=CompositeBackend(
default=StateBackend(),
routes={"/memories/": StoreBackend(namespace=lambda rt: (rt.server_info.user.identity,))},
),
):::
:::js
// 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],
}) },
),
});:::
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 aBackendContext. - Drop the
.runtimeaccessor β for example,ctx.runtime.context.user_idbecomesrt.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.
:::python
# Before (deprecated, removed in v0.7)
StoreBackend(
namespace=lambda ctx: (ctx.runtime.context.user_id,), # [!code --]
)
# After
StoreBackend(
namespace=lambda rt: (rt.server_info.user.identity,), # [!code ++]
):::
:::js
// Before (deprecated, removed in v0.7)
new StoreBackend({
namespace: (ctx) => [ctx.runtime.context.userId], // [!code --]
});
// After
new StoreBackend({
namespace: (rt) => [rt.serverInfo.user.identity], // [!code ++]
});:::
Backends must implement @[BackendProtocol].
Required methods:
ls(path: str) -> LsResult- Return entries with at least
path. Includeis_dir,size,modified_atwhen available. Sort bypathfor deterministic output.
- Return entries with at least
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").
- Return file data on success. On missing file, return
grep(pattern: str, path: Optional[str] = None, glob: Optional[str] = None) -> GrepResult- Return structured matches. On error, return
GrepResult(error="...")(do not raise).
- Return structured matches. On error, return
glob(pattern: str, path: str = "/") -> GlobResult- Return matched files as
FileInfoentries (empty list if none).
- Return matched files as
write(file_path: str, content: str) -> WriteResult- Create-only. On conflict, return
WriteResult(error=...). On success, setpathand for state backends setfiles_update={...}; external backends should usefiles_update=None.
- Create-only. On conflict, return
edit(file_path: str, old_string: str, new_string: str, replace_all: bool = False) -> EditResult- Enforce uniqueness of
old_stringunlessreplace_all=True. If not found, return error. Includeoccurrenceson success.
- Enforce uniqueness of
Supporting types:
LsResult(error, entries)βentriesis alist[FileInfo]on success,Noneon failure.ReadResult(error, file_data)βfile_datais aFileDatadict on success,Noneon failure.GrepResult(error, matches)βmatchesis alist[GrepMatch]on success,Noneon failure.GlobResult(error, matches)βmatchesis alist[FileInfo]on success,Noneon failure.WriteResult(error, path, files_update)EditResult(error, path, files_update, occurrences)FileInfowith fields:path(required), optionallyis_dir,size,modified_at.GrepMatchwith fields:path,line,text.FileDatawith fields:content(str),encoding("utf-8"or"base64"),created_at,modified_at. :::
:::js
Backends implement BackendProtocolV2. All query methods return structured Result objects with { error?: string, ...data }.
-
ls(path: string) β LsResult- List files and directories in the specified directory (non-recursive). Directories have a trailing
/in their path andis_dir=true. Includeis_dir,size,modified_atwhen available.
- List files and directories in the specified directory (non-recursive). Directories have a trailing
-
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
Uint8Arraycontent is returned with themimeTypefield set. On missing file, return{ error: "File '/x' not found" }.
- Read file content. For text files, content is paginated by line offset/limit (default offset 0, limit 500). For binary files, the full raw
-
readRaw(filePath: string) β ReadRawResult- Read file content as raw
FileData. Returns the full file data including timestamps.
- Read file content as raw
-
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: "..." }.
- Search file contents for a literal text pattern. Binary files (determined by MIME type) are skipped. On failure, return
-
glob(pattern: string, path?: string) β GlobResult- Return files matching a glob pattern as
FileInfoentries.
- Return files matching a glob pattern as
-
write(filePath: string, content: string) β WriteResult- Create-only semantics. On conflict, return
{ error: "..." }. On success, setpathand for state backends setfilesUpdate={...}; external backends should usefilesUpdate=null.
- Create-only semantics. On conflict, return
-
edit(filePath: string, oldString: string, newString: string, replaceAll?: boolean) β EditResult- Enforce uniqueness of
oldStringunlessreplaceAll=true. If not found, return error. Includeoccurrenceson success.
- Enforce uniqueness of
uploadFiles(files: Array<[string, Uint8Array]>) β FileUploadResponse[]β Upload multiple files (for sandbox backends).downloadFiles(paths: string[]) β FileDownloadResponse[]β Download multiple files (for sandbox backends).
| Type | Success fields | Error field |
|---|---|---|
ReadResult |
content?: string | Uint8Array, mimeType?: string |
error |
ReadRawResult |
data?: FileData |
error |
LsResult |
files?: FileInfo[] |
error |
GlobResult |
files?: FileInfo[] |
error |
GrepResult |
matches?: GrepMatch[] |
error |
WriteResult |
path?: string |
error |
EditResult |
path?: string, occurrences?: number |
error |
FileInfoβpath(required), optionallyis_dir,size,modified_at.GrepMatchβpath,line(1-indexed),text.FileDataβ File content with timestamps. See FileData format.
SandboxBackendProtocolV2 extends BackendProtocolV2 with:
execute(command: string) β ExecuteResponseβ Run a shell command in the sandbox.readonly id: stringβ Unique identifier for the sandbox instance. :::
:::js
| V1 method | V2 method | Return 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) |
| V1 type | V2 type |
|---|---|
BackendProtocol |
BackendProtocolV2 |
SandboxBackendProtocol |
SandboxBackendProtocolV2 |
If you have existing V1 backends that you need to use with V2-only code, use the adaptation functions:
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);