SesameSesame

Sandboxes

How to add new sandbox execution providers

This guide explains how to add support for a new sandbox provider to Sesame.

Overview

Sandbox providers are responsible for:

  1. Creating workspaces - Setting up isolated environments
  2. Executing commands - Running agent CLIs and other tools
  3. Managing lifecycle - Starting, stopping, and cleaning up

Provider Interface

All providers implement the SandboxProvider interface:

interface SandboxProvider {
  // Create a new sandbox for a task
  create(options: CreateOptions): Promise<Sandbox>;

  // Get an existing sandbox
  get(id: string): Promise<Sandbox | null>;

  // List all sandboxes
  list(): Promise<Sandbox[]>;

  // Clean up resources
  cleanup(): Promise<void>;
}

interface Sandbox {
  id: string;
  workdir: string;

  // Execute a command
  exec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;

  // Stream command output
  execStream(command: string, args: string[], options?: ExecOptions): AsyncIterable<string>;

  // File operations
  readFile(path: string): Promise<string>;
  writeFile(path: string, content: string): Promise<void>;

  // Lifecycle
  start(): Promise<void>;
  stop(): Promise<void>;
  destroy(): Promise<void>;
}

File Structure

base.ts # Abstract base class
local.ts # Filesystem provider
docker.ts # Docker provider
vercel.ts # Vercel Sandbox provider
types.ts # Interfaces and types
index.ts # Provider factory
dev-server.ts
index.ts

Step 1: Create Provider Class

packages/sandbox/src/providers/my-provider.ts
import { SandboxProvider, Sandbox, CreateOptions } from "../types";

export class MyProvider implements SandboxProvider {
  private sandboxes = new Map<string, MySandbox>();

  async create(options: CreateOptions): Promise<Sandbox> {
    const id = options.taskId;

    // Create the sandbox environment
    const sandbox = new MySandbox(id, options);
    await sandbox.initialize();

    this.sandboxes.set(id, sandbox);
    return sandbox;
  }

  async get(id: string): Promise<Sandbox | null> {
    return this.sandboxes.get(id) ?? null;
  }

  async list(): Promise<Sandbox[]> {
    return Array.from(this.sandboxes.values());
  }

  async cleanup(): Promise<void> {
    for (const sandbox of this.sandboxes.values()) {
      await sandbox.destroy();
    }
    this.sandboxes.clear();
  }
}

class MySandbox implements Sandbox {
  id: string;
  workdir: string;

  constructor(id: string, options: CreateOptions) {
    this.id = id;
    this.workdir = `/path/to/workdir/${id}`;
  }

  async initialize(): Promise<void> {
    // Set up the sandbox environment
  }

  async exec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult> {
    // Execute command and return result
    const result = await this.runCommand(command, args, options);
    return {
      exitCode: result.exitCode,
      stdout: result.stdout,
      stderr: result.stderr,
    };
  }

  async *execStream(command: string, args: string[], options?: ExecOptions): AsyncIterable<string> {
    // Stream command output
    const process = this.spawnCommand(command, args, options);
    for await (const chunk of process.stdout) {
      yield chunk.toString();
    }
  }

  async readFile(path: string): Promise<string> {
    // Read file from sandbox
  }

  async writeFile(path: string, content: string): Promise<void> {
    // Write file to sandbox
  }

  async start(): Promise<void> {
    // Start/resume sandbox
  }

  async stop(): Promise<void> {
    // Pause sandbox
  }

  async destroy(): Promise<void> {
    // Clean up sandbox resources
  }
}

Step 2: Register Provider

Add your provider to the factory in packages/sandbox/src/providers/index.ts:

import { MyProvider } from "./my-provider";

export function createProvider(type: string): SandboxProvider {
  switch (type) {
    case "local":
      return new LocalProvider();
    case "docker":
      return new DockerProvider();
    case "vercel":
      return new VercelSandboxProvider();
    case "my-provider":
      return new MyProvider();
    default:
      throw new Error(`Unknown provider: ${type}`);
  }
}

Step 3: Add Configuration

Add environment variables for your provider:

packages/shared/src/config.ts
export const config = {
  sandbox: {
    provider: process.env.SANDBOX_PROVIDER ?? "local",
    // Add provider-specific config
    myProvider: {
      endpoint: process.env.MY_PROVIDER_ENDPOINT,
      apiKey: process.env.MY_PROVIDER_API_KEY,
    },
  },
};

Step 4: Add Documentation

Create documentation in docs/content/docs/sandbox/my-provider.mdx:

---
title: My Provider Sandbox
description: Run tasks using My Provider
icon: Cloud
---

## Overview

The My Provider sandbox runs tasks using My Provider's infrastructure.

## Configuration

\`\`\`bash
SANDBOX_PROVIDER=my-provider
MY_PROVIDER_ENDPOINT=https://api.myprovider.com
MY_PROVIDER_API_KEY=your-api-key
\`\`\`

## Features

- Feature 1
- Feature 2

Provider Patterns

Cloud Provider

For cloud-based sandboxes, see the Vercel provider implementation at packages/sandbox/src/providers/vercel.ts:

import { Sandbox } from "@vercel/sandbox";
import { BaseSandboxProvider } from "./base";

export class VercelSandboxProvider extends BaseSandboxProvider {
  private sandbox: Sandbox | null = null;

  async init(): Promise<void> {
    this.sandbox = await Sandbox.create({
      runtime: "node24",
      ports: [3000, 5173, 8080],
      timeout: 3600000,
      resources: { vcpus: 2 },
    });
  }

  async run(command: string, args: string[]): Promise<CommandResult> {
    const result = await this.sandbox.runCommand({
      cmd: command,
      args,
      cwd: this.projectDir,
    });
    return {
      success: result.exitCode === 0,
      exitCode: result.exitCode,
      stdout: await result.stdout(),
      stderr: await result.stderr(),
    };
  }
}

Key considerations for cloud providers:

  • Async initialization: Cloud sandboxes require async setup
  • Port declaration: Some providers (like Vercel) require ports upfront
  • Remote file operations: Use SDK methods instead of shell commands when available

Container Provider

For container-based sandboxes (like Docker, Kubernetes):

class ContainerProvider implements SandboxProvider {
  async create(options: CreateOptions): Promise<Sandbox> {
    // Create container
    const container = await docker.createContainer({
      Image: "ghcr.io/jakejarvis/sesame-sandbox:latest",
      Cmd: ["tail", "-f", "/dev/null"],
      HostConfig: {
        Binds: [`${options.workdir}:/workspace`],
      },
    });

    await container.start();
    return new ContainerSandbox(container);
  }
}

VM Provider

For VM-based sandboxes:

class VMProvider implements SandboxProvider {
  async create(options: CreateOptions): Promise<Sandbox> {
    // Create VM
    const vm = await this.hypervisor.createVM({
      image: "ubuntu-22.04",
      cpu: 2,
      memory: 4096,
    });

    await vm.start();
    return new VMSandbox(vm);
  }
}

Testing

packages/sandbox/src/__tests__/my-provider.test.ts
import { MyProvider } from "../providers/my-provider";

describe("MyProvider", () => {
  let provider: MyProvider;

  beforeEach(() => {
    provider = new MyProvider();
  });

  afterEach(async () => {
    await provider.cleanup();
  });

  it("creates sandbox", async () => {
    const sandbox = await provider.create({
      taskId: "test-task",
      workdir: "/tmp/test",
    });

    expect(sandbox.id).toBe("test-task");
  });

  it("executes commands", async () => {
    const sandbox = await provider.create({
      taskId: "test-task",
      workdir: "/tmp/test",
    });

    const result = await sandbox.exec("echo", ["hello"]);
    expect(result.stdout).toContain("hello");
  });
});

Security Considerations

When implementing a sandbox provider, consider how it interacts with Sesame's sandbox security layer.

Sandbox Security Integration

Sesame's @anthropic-ai/sandbox-runtime integration provides OS-level restrictions on top of your provider's isolation. Your provider should:

  1. Support the security wrapper: The task executor wraps agent processes with sandbox security. Ensure your exec() and execStream() methods work with this wrapper.

  2. Provide workdir correctly: Sandbox security allows writes to the workdir path. Ensure this is set correctly.

  3. Handle violations gracefully: If sandbox security blocks an operation, your provider should handle the resulting errors without crashing.

Provider Security Properties

Document your provider's security properties clearly:

/**
 * MyProvider security properties:
 * - Isolation: Container-level (Docker)
 * - Network: Isolated by default, configurable
 * - Filesystem: Bind-mounted workspace only
 * - Works with sandbox security: Yes
 */
export class MyProvider implements SandboxProvider {
  // ...
}

Testing with Sandbox Security

Test your provider both with and without sandbox security enabled:

describe("MyProvider with sandbox security", () => {
  beforeAll(() => {
    process.env.SANDBOX_SECURITY_ENABLED = "true";
  });

  it("executes commands within restrictions", async () => {
    // Test that commands work with filesystem restrictions
  });

  it("handles blocked network access", async () => {
    // Test behavior when network is restricted
  });
});

Checklist

  • Implemented SandboxProvider interface
  • Implemented Sandbox interface
  • Registered in provider factory
  • Added configuration options
  • Added documentation
  • Added tests
  • Handles errors gracefully
  • Cleans up resources properly
  • Works with sandbox security enabled
  • Documents security properties

On this page