Modal Sandbox backend for deepagents. This package provides a ModalSandbox implementation of the SandboxBackendProtocol, enabling agents to execute commands, read/write files, and manage isolated container environments using Modal's serverless infrastructure.
read, write, edit, ls, grep, glob) work out of the boxsandbox property for advanced features# npm
npm install @langchain/modal
# yarn
yarn add @langchain/modal
# pnpm
pnpm add @langchain/modal
The package requires Modal authentication:
export MODAL_TOKEN_ID=your_token_id
export MODAL_TOKEN_SECRET=your_token_secret
const sandbox = await ModalSandbox.create({
auth: {
tokenId: "your-token-id",
tokenSecret: "your-token-secret",
},
});
import { createDeepAgent } from "deepagents";
import { ChatAnthropic } from "@langchain/anthropic";
import { ModalSandbox } from "@langchain/modal";
// Create and initialize the sandbox
const sandbox = await ModalSandbox.create({
imageName: "python:3.12-slim",
timeoutMs: 600_000, // 10 minutes
});
try {
const agent = createDeepAgent({
model: new ChatAnthropic({ model: "claude-sonnet-4-20250514" }),
systemPrompt: "You are a coding assistant with access to a sandbox.",
backend: sandbox,
});
const result = await agent.invoke({
messages: [
{ role: "user", content: "Create a hello world Python app and run it" },
],
});
} finally {
await sandbox.close();
}
ModalSandboxOptions extends the Modal SDK's SandboxCreateParams directly, so you can use any SDK option. We only wrap volumes and secrets to accept names instead of objects.
interface ModalSandboxOptions extends SandboxCreateParams {
/** Modal App name. @default "deepagents-sandbox" */
appName?: string;
/** Docker image to use. @default "alpine:3.21" */
imageName?: string;
/** Initial files to populate the sandbox with. */
initialFiles?: Record<string, string | Uint8Array>;
/** Authentication credentials (or use env vars). */
auth?: { tokenId?: string; tokenSecret?: string };
/** Modal Volume names to mount (keys are mount paths). */
volumes?: Record<string, string>;
/** Modal Secret names to inject. */
secrets?: string[];
// All SandboxCreateParams options are available:
timeoutMs?: number; // Max lifetime in milliseconds
idleTimeoutMs?: number; // Idle timeout in milliseconds
workdir?: string; // Working directory
gpu?: string; // GPU type (e.g., "T4", "A100")
cpu?: number; // CPU cores (fractional allowed)
memoryMiB?: number; // Memory in MiB
regions?: string[]; // Regions to run in
env?: Record<string, string>; // Environment variables
blockNetwork?: boolean; // Block network access
cidrAllowlist?: string[]; // Allowed CIDRs
verbose?: boolean; // Enable verbose logging
name?: string; // Sandbox name (unique within app)
}
Modal supports various NVIDIA GPUs for ML/AI workloads:
const sandbox = await ModalSandbox.create({
imageName: "python:3.12-slim",
gpu: "T4", // NVIDIA T4 (16GB VRAM)
// gpu: "L4", // NVIDIA L4 (24GB VRAM)
// gpu: "A10G", // NVIDIA A10G (24GB VRAM)
// gpu: "A100", // NVIDIA A100 (40/80GB VRAM)
// gpu: "H100", // NVIDIA H100 (80GB VRAM)
});
Mount Modal Volumes for persistent storage:
// Volume must be created in Modal first
const sandbox = await ModalSandbox.create({
imageName: "python:3.12-slim",
volumes: {
"/data": "my-data-volume",
"/models": "my-models-volume",
},
});
// Files in /data and /models persist across sandbox restarts
await sandbox.execute("echo 'Hello' > /data/test.txt");
Inject Modal Secrets as environment variables:
// Secrets must be created in Modal first
const sandbox = await ModalSandbox.create({
imageName: "python:3.12-slim",
secrets: ["my-api-keys", "database-credentials"],
});
// Secrets are available as environment variables
await sandbox.execute("echo $API_KEY");
Pre-populate the sandbox with files during creation:
const sandbox = await ModalSandbox.create({
imageName: "python:3.12-slim",
initialFiles: {
// String content
"/app/main.py": `
import json
def main():
print("Hello from Python!")
if __name__ == "__main__":
main()
`,
// JSON configuration
"/app/config.json": JSON.stringify(
{ name: "my-app", version: "1.0.0" },
null,
2,
),
// Uint8Array content also supported
"/app/data.bin": new Uint8Array([0x00, 0x01, 0x02]),
},
});
// Files are ready to use immediately
const result = await sandbox.execute("python /app/main.py");
console.log(result.output); // "Hello from Python!"
This is especially useful for:
For advanced features not exposed by BaseSandbox, you can access the underlying Modal SDK directly:
.client - The ModalClient instance for accessing other Modal resources.instance - The Sandbox instance for direct sandbox operationsconst modalSandbox = await ModalSandbox.create();
// Access the Modal client for other Modal resources
const client = modalSandbox.client;
// Access the raw Modal Sandbox for direct operations
const instance = modalSandbox.instance;
// Execute commands with specific options
const process = await instance.exec(["python", "-c", "print('Hello')"], {
stdout: "pipe",
stderr: "pipe",
});
// Open files for reading/writing
const writeHandle = await instance.open("/tmp/file.txt", "w");
await writeHandle.write(new TextEncoder().encode("Hello"));
await writeHandle.close();
Resume working with a sandbox that is still running:
// First session: create a sandbox
const sandbox = await ModalSandbox.create({
imageName: "python:3.12-slim",
timeout: 3600, // 1 hour
});
const sandboxId = sandbox.id;
// Later: reconnect to the same sandbox by ID
const reconnected = await ModalSandbox.fromId(sandboxId);
const result = await reconnected.execute("ls -la");
// Or reconnect by name (if sandbox has a name)
const sandbox2 = await ModalSandbox.create({
appName: "my-app",
sandboxName: "my-sandbox",
imageName: "python:3.12-slim",
});
const reconnected2 = await ModalSandbox.fromName("my-app", "my-sandbox");
import { ModalSandboxError } from "@langchain/modal";
try {
await sandbox.execute("some command");
} catch (error) {
if (error instanceof ModalSandboxError) {
switch (error.code) {
case "NOT_INITIALIZED":
await sandbox.initialize();
break;
case "COMMAND_TIMEOUT":
console.error("Command took too long");
break;
case "AUTHENTICATION_FAILED":
console.error("Check your Modal token credentials");
break;
default:
throw error;
}
}
}
| Code | Description |
|---|---|
NOT_INITIALIZED |
Sandbox not initialized - call initialize() |
ALREADY_INITIALIZED |
Cannot initialize twice |
AUTHENTICATION_FAILED |
Invalid or missing Modal tokens |
SANDBOX_CREATION_FAILED |
Failed to create sandbox |
SANDBOX_NOT_FOUND |
Sandbox ID/name not found or expired |
COMMAND_TIMEOUT |
Command execution timed out |
COMMAND_FAILED |
Command execution failed |
FILE_OPERATION_FAILED |
File read/write failed |
RESOURCE_LIMIT_EXCEEDED |
CPU, memory, or storage limits exceeded |
VOLUME_ERROR |
Volume operation failed |
ModalSandbox extends BaseSandbox and inherits these convenience methods:
| Method | Description |
|---|---|
read() |
Read a file's contents |
write() |
Write content to a file |
edit() |
Replace text in a file |
lsInfo() |
List directory contents |
grepRaw() |
Search for patterns in files |
globInfo() |
Find files matching a pattern |
| Constraint | Value |
|---|---|
| Max timeout | 86400 seconds (24 hours) |
| Default timeout | 300 seconds (5 minutes) |
| Network access | Full (by default, can be blocked) |
| File API | Alpha (up to 100 MiB read, 1 GiB write) |
| Variable | Description |
|---|---|
MODAL_TOKEN_ID |
Modal API token ID |
MODAL_TOKEN_SECRET |
Modal API token secret |
MIT
Get authentication credentials for Modal API.
Create an async factory function that creates a new Modal Sandbox per invocation.
Create a backend factory that reuses an existing Modal Sandbox.
Get authentication credentials for Modal API.
Create an async factory function that creates a new Modal Sandbox per invocation.
Create a backend factory that reuses an existing Modal Sandbox.