Build your own Sandcastle sandbox provider using createBindMountSandboxProvider or createIsolatedSandboxProvider. Covers handle interfaces, factory signatures, and when to use each.
Use this file to discover all available pages before exploring further.
Sandcastle ships with built-in providers for Docker, Podman, and Vercel, but you can create your own provider for any environment that can run commands and manage files. Two factory functions cover the two provider types — one for environments that can mount host directories, and one for environments with their own independent filesystem.
Before implementing a custom provider, decide which type fits your target environment:
Bind-mount provider
For environments that can mount host directories directly into the sandbox — typically local container runtimes. The agent writes to the host filesystem through the mount. No file syncing is needed.Examples: Docker, Podman, custom local container runtimes.
Isolated provider
For environments with their own independent filesystem — typically remote or cloud-based sandboxes. Sandcastle syncs the repository into the sandbox and extracts commits back to the host after the run.Examples: Vercel microVMs, E2B, Daytona, remote VMs.
Use this factory when your environment can bind-mount a host directory into the sandbox. The factory accepts a BindMountSandboxProviderConfig object:
export interface BindMountSandboxProviderConfig { /** Human-readable name for this provider (e.g. "docker", "podman"). */ readonly name: string; /** Environment variables injected by this provider. Merged at launch time. */ readonly env?: Record<string, string>; /** * Absolute path to the home directory inside the sandbox (e.g. "/home/agent"). * Used to expand "~" in user-provided sandboxPath mount configs. */ readonly sandboxHomedir?: string; /** Create a sandbox handle from the given options. */ readonly create: ( options: BindMountCreateOptions, ) => Promise<BindMountSandboxHandle>;}
Your create function receives BindMountCreateOptions:
export interface BindMountCreateOptions { /** Host-side path to the worktree directory. */ readonly worktreePath: string; /** Host-side path to the original repo root. */ readonly hostRepoPath: string; /** Volume mounts to apply (host:sandbox pairs). */ readonly mounts: Array<{ hostPath: string; sandboxPath: string; readonly?: boolean; }>; /** Environment variables to inject into the sandbox. */ readonly env: Record<string, string>;}
exec must support line-by-line streaming via onLine. This is how Sandcastle delivers live feedback and enforces idle timeouts. A buffered implementation that only calls onLine after the process exits does not satisfy the contract.
Use this factory when your environment has its own filesystem and cannot mount host directories. The factory accepts an IsolatedSandboxProviderConfig object:
export interface IsolatedSandboxProviderConfig { /** Human-readable name for this provider (e.g. "daytona", "e2b"). */ readonly name: string; /** Environment variables injected by this provider. Merged at launch time. */ readonly env?: Record<string, string>; /** Create an isolated sandbox handle from the given options. */ readonly create: ( options: IsolatedCreateOptions, ) => Promise<IsolatedSandboxHandle>;}
Your create function receives IsolatedCreateOptions:
export interface IsolatedCreateOptions { /** Environment variables to inject into the sandbox. */ readonly env: Record<string, string>;}
It must return an IsolatedSandboxHandle:
export interface IsolatedSandboxHandle { /** Absolute path to the worktree inside the sandbox. */ readonly worktreePath: string; exec( command: string, options?: { onLine?: (line: string) => void; cwd?: string; sudo?: boolean; stdin?: string; }, ): Promise<ExecResult>; interactiveExec?( args: string[], options: InteractiveExecOptions, ): Promise<{ exitCode: number }>; /** Copy a file or directory from the host into the sandbox. */ copyIn(hostPath: string, sandboxPath: string): Promise<void>; /** Copy a single file from the sandbox to the host. */ copyFileOut(sandboxPath: string, hostPath: string): Promise<void>; close(): Promise<void>;}
Note that isolated handles expose copyIn (which accepts directories) instead of copyFileIn (files only). This is how Sandcastle transfers the full repository into the sandbox before the agent runs.
The provider type determines the default branch strategy used when you do not pass branchStrategy to run():
Provider type
Default branch strategy
Bind-mount
{ type: "head" }
Isolated
{ type: "merge-to-head" }
Isolated providers cannot use the head strategy — that strategy requires writing directly to the host working directory, which is not possible from a remote filesystem.