How to Integrate a Custom Backend Implementation Using the BackendProtocol in DeepAgents

To integrate a custom backend in DeepAgents, implement the BackendProtocol interface (or SandboxBackendProtocol for command execution), override the required file operation methods, and pass the instance or a factory function to DeepAgent.

The langchain-ai/deepagents repository provides a pluggable backend architecture that abstracts file-system operations behind the BackendProtocol interface. When you integrate a custom backend implementation using the BackendProtocol in DeepAgents, you can connect agents to diverse storage systems—from in-memory mocks and cloud object stores to remote containerized sandboxes—without modifying the core agent logic.

Understanding the BackendProtocol Architecture

DeepAgents defines a pluggable backend architecture that abstracts all file-system-like operations (list, read, write, edit, upload, download, grep, glob) behind the BackendProtocol interface. Backends that also need to run shell commands implement the derived SandboxBackendProtocol.

Core Protocol Definitions in protocol.py

The BackendProtocol in libs/deepagents/deepagents/backends/protocol.py (lines 44–512) declares the core contract for any backend. All file-related methods are declared here and raise NotImplementedError by default. Key methods include:

  • ls_info(path: str) -> LsResult
  • read(file_path: str, offset: int = 0, limit: int = 2000) -> ReadResult
  • write(file_path: str, content: str) -> WriteResult
  • edit(file_path: str, old_string: str, new_string: str, replace_all: bool = False) -> EditResult

SandboxBackendProtocol for Command Execution

For backends that support command execution, SandboxBackendProtocol (same file, lines 512–558) extends BackendProtocol with the execute(command: str, *, timeout: int | None = None) -> ExecuteResponse method. This is essential for remote containers, VMs, or any sandboxed runtime.

The BaseSandbox Mixin

The BaseSandbox class in libs/deepagents/deepagents/backends/sandbox.py (lines 217–260) provides a reusable mix-in that implements all file operations by delegating to execute(). Custom sandboxes usually subclass this instead of re-implementing every method, as it automatically translates file commands into shell commands.

Implementing a Custom Backend

To add a custom backend, choose the appropriate base protocol, implement the abstract methods your use-case requires, and optionally provide a factory that binds the backend to the runtime.

Minimal Read-Only Backend Example

The following example implements a read-only in-memory backend by subclassing BackendProtocol:


# file: my_backend.py

from __future__ import annotations
from typing import Final
from deepagents.backends.protocol import BackendProtocol, LsResult, ReadResult, WriteResult, EditResult, FileInfo

class InMemoryBackend(BackendProtocol):
    """A simple read‑only backend that stores files in a dict."""

    def __init__(self, files: dict[str, str]) -> None:
        # `files` maps absolute paths → file content (UTF‑8 string)

        self._store: Final = files

    # ---------- required file operations ----------

    def ls_info(self, path: str) -> LsResult:
        if not path.endswith("/"):
            path = path + "/"
        entries = [
            FileInfo(path=p, is_dir=False)
            for p in self._store
            if p.startswith(path)
        ]
        return LsResult(entries=entries)

    def read(self, file_path: str, offset: int = 0, limit: int = 2000) -> ReadResult:
        content = self._store.get(file_path)
        if content is None:
            return ReadResult(error="file_not_found")
        # emulate cat‑n format

        lines = content.splitlines()[offset : offset + limit]
        numbered = "\n".join(f"{i+1}\t{ln}" for i, ln in enumerate(lines, start=offset))
        return ReadResult(file_data={"content": numbered, "encoding": "utf-8",
                                    "created_at": "", "modified_at": ""})

    # The backend is read‑only, so write/edit just return an error.

    def write(self, file_path: str, content: str) -> WriteResult:
        return WriteResult(error="permission_denied")

    def edit(self, file_path: str, old_string: str, new_string: str,
             replace_all: bool = False) -> EditResult:
        return EditResult(error="permission_denied")

Using the backend in a DeepAgent

Pass the backend as a factory or instance when creating the agent:

from deepagents import DeepAgent
from deepagents.backends.protocol import BackendFactory
from my_backend import InMemoryBackend

def my_factory(runtime) -> InMemoryBackend:   # type: ignore[override]

    # `runtime` can be used to read env vars, tool configs, etc.

    demo_files = {
        "/app/hello.py": "print('Hello, DeepAgents!')\n",
        "/app/readme.md": "# Demo project\n\nThis is a demo.\n",

    }
    return InMemoryBackend(demo_files)

agent = DeepAgent(backend=my_factory)   # pass a factory or an instance

agent.run("list the files in /app")

Sandbox Backend with Command Execution

If you need to run shell commands, inherit from SandboxBackendProtocol or the helper BaseSandbox. Below is a mock sandbox that executes commands locally using subprocess.run:


# file: mock_sandbox.py

import subprocess
from deepagents.backends.protocol import SandboxBackendProtocol, ExecuteResponse

class MockLocalSandbox(SandboxBackendProtocol):
    @property
    def id(self) -> str:
        return "mock-local-sandbox"

    def execute(self, command: str, *, timeout: int | None = None) -> ExecuteResponse:
        try:
            completed = subprocess.run(
                command,
                shell=True,
                capture_output=True,
                text=True,
                timeout=timeout,
                check=False,
            )
            return ExecuteResponse(
                output=completed.stdout + completed.stderr,
                exit_code=completed.returncode,
                truncated=False,
            )
        except subprocess.TimeoutExpired:
            return ExecuteResponse(
                output="",
                exit_code=None,
                truncated=True,
            )

Note – The BaseSandbox class (see [libs/deepagents/deepagents/backends/sandbox.py](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/deepagents/backends/sandbox.py#L217-L260)) already implements all file methods (ls_info, read, write, …) by delegating to execute. If you subclass BaseSandbox you only need to provide id and execute.

Registering the sandbox for the agent

from deepagents import DeepAgent
from mock_sandbox import MockLocalSandbox

agent = DeepAgent(backend=MockLocalSandbox())
agent.run("run `ls -R /` and show the first 20 lines")

Runtime Configuration with BackendFactory

When your backend requires runtime values (e.g., API keys or environment variables), use a BackendFactory instead of a direct instance:

def s3_backend_factory(runtime) -> BackendProtocol:
    bucket = runtime.env.get("S3_BUCKET")   # runtime.env is a dict‑like view

    aws_key = runtime.env.get("AWS_ACCESS_KEY_ID")
    # Assume `S3Backend` is a concrete implementation you wrote elsewhere.

    return S3Backend(bucket_name=bucket, aws_key=aws_key)

agent = DeepAgent(backend=s3_backend_factory)

The runtime object provides access to environment variables and tool configurations, allowing lazy initialization of the backend when the agent starts.

Reference Implementations and Key Files

Study these concrete implementations to understand the minimal set of methods required for different backend types:

File Role Link
libs/deepagents/deepagents/backends/protocol.py Core BackendProtocol & SandboxBackendProtocol definitions protocol.py
libs/deepagents/deepagents/backends/sandbox.py BaseSandbox mix‑in that implements file ops via execute sandbox.py
libs/deepagents/deepagents/backends/filesystem.py Real filesystem backend – good reference for path handling filesystem.py
libs/deepagents/deepagents/backends/local_shell.py Combines FilesystemBackend with SandboxBackendProtocol – shows hybrid implementation local_shell.py
libs/harbor/deepagents_harbor/backend.py Example of a sandbox backend that talks to a remote Harbor service Harbor backend
libs/deepagents/tests/unit_tests/backends/test_protocol.py Test suite that validates the abstract contract – useful to see expected behaviours test_protocol.py

Summary

  • Choose the right protocol: Use BackendProtocol for file-only backends or SandboxBackendProtocol if you need to execute shell commands.
  • Implement required methods: Override ls_info, read, write, edit, and other file operations declared in libs/deepagents/deepagents/backends/protocol.py.
  • Leverage BaseSandbox: Subclass BaseSandbox from libs/deepagents/deepagents/backends/sandbox.py to get default file operation implementations that delegate to your execute method.
  • Use factories for runtime config: Pass a BackendFactory function to DeepAgent when your backend requires environment variables or lazy initialization.
  • Register and run: Pass your backend instance or factory to the backend parameter of DeepAgent, and the runtime automatically wires it into all middleware and tools.

Frequently Asked Questions

What is the difference between BackendProtocol and SandboxBackendProtocol?

BackendProtocol defines the core contract for file-system-like operations such as read, write, and ls_info. SandboxBackendProtocol extends this interface with an execute(command: str) method for running shell commands. If your backend needs to support command execution (like a Docker container or remote VM), implement SandboxBackendProtocol or subclass BaseSandbox.

Do I need to implement every method in BackendProtocol?

No. You only need to implement the methods your use case requires. The base protocol in libs/deepagents/deepagents/backends/protocol.py raises NotImplementedError by default for all methods. However, for a complete backend, you should implement at least ls_info, read, write, and edit to support basic file operations.

How do I access environment variables inside my backend?

Use a BackendFactory instead of instantiating the backend directly. The factory receives a runtime object (of type ToolRuntime) that exposes runtime.env, a dict-like view of environment variables. This pattern allows you to configure API keys, bucket names, or connection strings at runtime rather than hard-coding them.

Can I combine file operations with command execution without writing boilerplate?

Yes. Subclass BaseSandbox from libs/deepagents/deepagents/backends/sandbox.py. This mixin implements all file methods (ls_info, read, write, etc.) by translating them into shell commands and delegating to your execute method. You only need to implement id and execute to get a fully functional sandbox backend.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →