Skip to content

Sandbox clients

Use this page to choose where sandbox work should run. In most cases, the SandboxAgent definition stays the same while the sandbox client and client-specific options change in the sandbox run option.

GoalStart withWhy
Fastest local iteration on macOS or LinuxUnixLocalSandboxClientNo extra service dependency and a simple local filesystem workflow.
Basic container isolationDockerSandboxClientRuns work inside Docker with a specific image.
Hosted execution or production-style isolationA hosted sandbox clientMoves the workspace boundary to a provider-managed environment.

For most users, start with one of these two sandbox clients:

ClientInstallChoose it when
UnixLocalSandboxClientnoneFastest local iteration on macOS or Linux. Good default for local development.
DockerSandboxClientDocker CLI available locallyYou want container isolation or a specific image for local parity.

Unix-local is the easiest way to start developing against a local filesystem. Move to Docker or a hosted provider when you need stronger environment isolation or production-style parity.

To switch from Unix-local to Docker, keep the agent definition the same and change only the client:

Use Docker
import { run } from '@openai/agents';
import { SandboxAgent } from '@openai/agents/sandbox';
import { DockerSandboxClient } from '@openai/agents/sandbox/local';
const agent = new SandboxAgent({
name: 'Workspace reviewer',
model: 'gpt-5.6-sol',
instructions: 'Inspect the sandbox workspace before answering.',
});
const result = await run(agent, 'Inspect the workspace.', {
sandbox: {
client: new DockerSandboxClient({ image: 'node:22-bookworm-slim' }),
},
});
console.log(result.finalOutput);

The same agent can usually run with either local client:

Switch between local clients
import {
DockerSandboxClient,
UnixLocalSandboxClient,
} from '@openai/agents/sandbox/local';
const client = process.env.USE_DOCKER
? new DockerSandboxClient({ image: 'node:22-bookworm-slim' })
: new UnixLocalSandboxClient();

There are two lifecycle styles.

StyleWhat you passWho closes the sessionUse it when
SDK-ownedsandbox: { client }The runnerThe sandbox only needs to live for one run.
Developer-ownedsandbox: { session }Your codeYou need to inspect files afterward, reuse the same live session, or coordinate multiple runs.

On normal completion or failure, the runner closes an SDK-owned session. When a run pauses for an approval interruption, or when an unfinished streamed run is cancelled, the runner instead preserves the owned sandbox state in RunState so the same run can continue.

When you create a session yourself, close it yourself:

Own the sandbox session lifecycle
import { run } from '@openai/agents';
import { Manifest, SandboxAgent } from '@openai/agents/sandbox';
import { UnixLocalSandboxClient } from '@openai/agents/sandbox/local';
const manifest = new Manifest();
const agent = new SandboxAgent({
name: 'Workspace reviewer',
model: 'gpt-5.6-sol',
instructions: 'Inspect the sandbox workspace before answering.',
});
const client = new UnixLocalSandboxClient();
const session = await client.create({ manifest });
try {
await run(agent, 'First pass.', { sandbox: { session } });
await run(agent, 'Follow-up pass.', { sandbox: { session } });
} finally {
await session.close?.();
}

Sandbox state and conversation state are separate:

  • SDK conversation state lives in result.history, an SDK Session, conversationId, or previousResponseId.
  • Sandbox state lives in the live sandbox session, serialized sessionState, RunState sandbox payloads, or snapshots.

Use sessionState when you want to reconnect to the same backend session through a sandbox client. Use a snapshot when you want a fresh session seeded from saved workspace contents.

Serialize and resume sandbox state
import { Manifest } from '@openai/agents/sandbox';
import { UnixLocalSandboxClient } from '@openai/agents/sandbox/local';
const manifest = new Manifest();
const client = new UnixLocalSandboxClient({
snapshot: { type: 'local', baseDir: '/tmp/my-sandbox-snapshots' },
});
const session = await client.create({ manifest });
const state = await client.serializeSessionState?.(session.state);
await session.close?.();
if (state) {
const restored = await client.resume?.(
await client.deserializeSessionState!(state),
);
await restored?.close?.();
}

RunState can also preserve runner-managed sandbox state when you pause or resume a larger workflow. Use explicit sessionState when the sandbox lifecycle is managed outside a serialized run.

For Docker, resuming the same in-memory RunState can reuse the live container only after the SDK verifies its identity and revalidates the current manifest, environment, and path grants. If live reuse is unavailable or rejected, Docker falls back to a restorable snapshot. A RunState serialized and reconstructed with RunState.fromString(...) does not carry trusted live-container authority, so Docker also restores from a snapshot instead of attaching to the serialized container ID. Resume fails rather than attaching to an unverified container when no restorable snapshot is configured.

For local snapshots with no explicit baseDir, set OPENAI_AGENTS_SANDBOX_SNAPSHOT_DIR to override the storage directory. Otherwise the SDK uses ~/Library/Application Support/openai-agents-js/sandbox-snapshots on macOS, %LOCALAPPDATA%\openai-agents-js\sandbox-snapshots on Windows, and $XDG_STATE_HOME/openai-agents-js/sandbox-snapshots on other platforms, with home-directory and temporary-directory fallbacks when those locations are unavailable.

Manifest entries are prepared before the agent runs. You can tune materialization concurrency per run or per client create call:

Tune manifest materialization concurrency
import { run } from '@openai/agents';
import { SandboxAgent } from '@openai/agents/sandbox';
import { UnixLocalSandboxClient } from '@openai/agents/sandbox/local';
const agent = new SandboxAgent({
name: 'Repository inspector',
model: 'gpt-5.6-sol',
instructions: 'Inspect the repository before answering.',
});
await run(agent, 'Inspect the repo.', {
sandbox: {
client: new UnixLocalSandboxClient(),
concurrencyLimits: {
manifestEntries: 4,
localDirFiles: 16,
},
},
});

manifestEntries limits parallel top-level entry work. localDirFiles limits file copy concurrency inside localDir() entries.

When a session implements pathExists(), false means the backend confirmed that the path is missing. Permission errors, I/O failures, and provider probe failures reject with a provider error instead of being treated as a missing path. This prevents editor, manifest, and memory flows from overwriting or replacing paths that may exist but are inaccessible.

Mount entries describe what storage to expose; mount strategies describe how a sandbox backend attaches that storage. Import the built-in mount entries and generic strategies from @openai/agents/sandbox.

Common mount options:

  • mountPath: where the storage appears in the sandbox. Relative paths are resolved under the manifest root; absolute paths are used as-is.
  • readOnly: set this when the sandbox should not write back to the mounted storage.
  • mountStrategy: use a strategy that matches both the mount entry and the sandbox backend.

Mounts are treated as ephemeral workspace entries. Snapshot and persistence flows detach or skip mounted paths instead of copying mounted remote storage into the saved workspace.

Generic local/container strategies:

Strategy or patternUse it whenNotes
inContainerMountStrategy(...)The sandbox image can run a mount command such as rclone, mount-s3, or blobfuse2.Available as a generic strategy; support depends on the backend.
dockerVolumeMountStrategy(...)Docker should attach a volume-driver-backed mount before the container starts.Docker-only.
localBindMountStrategy()A local backend should bind an absolute local path into the workspace.Supported by local workspace materialization where allowed.

Backend support is intentionally explicit:

BackendMount notes
UnixLocalSandboxClientSupports local bind-style mounts through the local workspace model.
DockerSandboxClientSupports local bind mounts and Docker volume-style strategies where Docker can attach the storage.
Hosted providersProvider-specific strategies live with each provider implementation. Check that provider’s docs for supported mounts and required setup.

Do not assume a mount entry works on every backend. If a client cannot enforce manifest metadata, identity, or mount behavior, it should fail early instead of silently ignoring that part of the manifest.

When you need a hosted environment, the same SandboxAgent definition usually carries over and only the sandbox client changes in the sandbox run option.

Hosted provider implementations are available from @openai/agents-extensions provider subpaths. Check the provider’s docs for exact environment variables, runnable examples, port behavior, PTY support, snapshot behavior, and cleanup behavior.

Install @openai/agents-extensions and satisfy its package-level peers. Each provider may also require a provider SDK package or backend setup:

ClientImport pathProvider requirement
BlaxelSandboxClient@openai/agents-extensions/sandbox/blaxelnpm peer: @blaxel/core
CloudflareSandboxClient@openai/agents-extensions/sandbox/cloudflareCloudflare Sandbox bridge Worker URL and Worker auth
DaytonaSandboxClient@openai/agents-extensions/sandbox/daytonanpm peer: @daytonaio/sdk
E2BSandboxClient@openai/agents-extensions/sandbox/e2bnpm peer: e2b or @e2b/code-interpreter
ModalSandboxClient@openai/agents-extensions/sandbox/modalnpm peer: modal
RunloopSandboxClient@openai/agents-extensions/sandbox/runloopnpm peer: @runloop/api-client
VercelSandboxClient@openai/agents-extensions/sandbox/vercelnpm peer: @vercel/sandbox

CloudflareSandboxClient does not import a Cloudflare npm SDK. It talks to a deployed Cloudflare Sandbox bridge Worker over HTTP instead.

VercelSandboxClient resolves each PAT credential field with the precedence per-create options, constructor options, then VERCEL_PROJECT_ID, VERCEL_TEAM_ID, and VERCEL_TOKEN. It forwards credentials only when the resulting projectId, teamId, and token are all non-empty. Otherwise it omits all three fields and lets @vercel/sandbox resolve authentication, including platform OIDC or local provider credentials. Complete resolved credentials are preserved when sessions are serialized and resumed. Serialized credentials are treated as a complete triple and are not mixed with current options or environment variables; incomplete serialized credentials are discarded before current configuration is resolved as a fallback. Because the token remains present in serialized session state, store that state securely.

Hosted sandbox clients expose provider-specific mount strategies. Choose the backend and mount strategy that best fit your storage provider:

Credential-bearing mounts that run a helper inside the sandbox are rejected by default because model-controlled code can access that helper’s process credentials. This includes credentials supplied through supported mount fields, ambient AWS or GCP environment variables, RCLONE_CONFIG_* values, and workload identity or managed identity discovery. Credentialless rclone and mountpoint helpers disable ambient or metadata authentication and use anonymous access where the backend supports it. Prefer external or provider-native strategies such as Docker volumes, Modal cloud buckets, or Cloudflare bucket mounts.

If an in-sandbox helper is required, acknowledge each trusted effective mount path on the application-created manifest. Use the manifest returned by manifest.withInContainerMountCredentialExposureAcknowledged('mounted/path') for mount-scoped credentials supplied directly through typed mount fields. Ambient credentials, workload or managed identity, and external credential or configuration files require the manifest returned by manifest.withInContainerMountBroadCredentialExposureAcknowledged('mounted/path'). A mount that uses both kinds of authority requires both acknowledgements. These exact-path acknowledgements are runtime-only and are never accepted from manifest init objects or serialized manifest data. They permit the selected helper to receive credentials; they do not confine those credentials to the mount path, so other model-controlled code in the same sandbox may still recover them. Use sandbox-scoped, short-lived, least-privilege credentials.

Credential files referenced by a mount cannot resolve directly or through symlinks to serializable manifest entries. Dynamic manifest mutations are serialized per session; if a privileged mount transition or replacement unmount fails after provider effects may have started, the SDK invalidates and terminates that session instead of reusing or persisting ambiguous state.

Serialized sandbox state omits mount credentials, including ambient environment credentials selected by an in-sandbox mount helper. Any persisted mount topology requires a current trusted manifest before resume. For resumable external or provider-native mounts, the SDK restores credentials only after the credential-free current and persisted mount topologies match. Explicit sessionState can reconnect an existing provider-native mount only when its unredacted live authority still matches the current trusted manifest; redacted, rotated, or removed authority requires a fresh sandbox. Opaque configuration that cannot be separated into credential-free topology is non-resumable. When the Runner restores a serialized RunState entry that used an in-sandbox mount helper, it discards that stored session and creates a fresh sandbox from current trusted configuration. Caller-supplied explicit sessionState and direct provider resume instead reject in-container mount state; start a fresh sandbox explicitly. Runloop credential-file variables supplied through opaque secretRefs are rejected for in-container mounts; provide the current trusted path through managedSecrets so the SDK can validate its effective path before mounting.

BackendMount notes
DockerSupports s3Mount(), gcsMount(), r2Mount(), azureBlobMount(), boxMount(), and s3FilesMount() with local strategies such as inContainerMountStrategy() and dockerVolumeMountStrategy().
ModalSandboxClientSupports cloud bucket mounts with ModalCloudBucketMountStrategy on S3, R2, and HMAC-authenticated GCS mount entries.
CloudflareSandboxClientSupports Cloudflare bucket mounts with CloudflareBucketMountStrategy on S3, R2, and HMAC-authenticated GCS mount entries.
BlaxelSandboxClientSupports cloud bucket mounts with BlaxelCloudBucketMountStrategy on S3, R2, and GCS mount entries. Credential-bearing cloud mounts require an SDK-owned sandbox; reused unowned named sandboxes can use credentialless cloud mounts or persistent Blaxel Drives with BlaxelDriveMount and BlaxelDriveMountStrategy.
DaytonaSandboxClientSupports rclone-backed mounts with DaytonaCloudBucketMountStrategy on S3, GCS, R2, Azure Blob, and Box mount entries.
E2BSandboxClientSupports rclone-backed mounts with E2BCloudBucketMountStrategy on S3, GCS, R2, Azure Blob, and Box mount entries.
RunloopSandboxClientSupports rclone-backed mounts with RunloopCloudBucketMountStrategy on S3, GCS, R2, Azure Blob, and Box mount entries.
VercelSandboxClientSupports create-time S3 mounts with VercelCloudBucketMountStrategy. For new code, acknowledge inline credentials with withInContainerMountCredentialExposureAcknowledged() for each exact mount path. The deprecated allowS3CredentialExposure: true option remains accepted for compatibility with released inline S3 credential configurations, but it does not authorize ambient credentials or other broad authority. Reuse the live mounted session because serialized mount state cannot be resumed directly.

For E2B and Runloop rclone-backed mounts, the SDK uses an existing rclone binary when available. Otherwise it installs an SDK-pinned Linux archive only after verifying its SHA-256 checksum; unsupported architectures and checksum failures abort the mount.

The table below summarizes which remote storage entries each backend can mount directly:

BackendAWS S3Cloudflare R2GCSAzure Blob StorageBoxS3 Files
Dockeryesyesyesyesyesyes
ModalSandboxClientyesyesyesnonono
CloudflareSandboxClientyesyesyesnonono
BlaxelSandboxClientyesyesyesnonono
DaytonaSandboxClientyesyesyesyesyesno
E2BSandboxClientyesyesyesyesyesno
RunloopSandboxClientyesyesyesyesyesno
VercelSandboxClientyesnonononono

A “yes” means that the backend can execute that storage mount type; it does not bypass the credential boundary described above. Docker’s dockerVolumeMountStrategy(), Modal cloud bucket mounts, and Cloudflare bucket mounts keep mount credentials outside the model-controlled sandbox. Docker’s inContainerMountStrategy() and the Daytona, E2B, and Runloop rclone strategies require exact-path acknowledgement whenever they receive credentials. Box mounts require authentication, so choose the acknowledgement that matches whether the Box authority is supplied through typed mount fields or through external credential or configuration files. Docker S3 Files mounts use broad workload identity and therefore require withInContainerMountBroadCredentialExposureAcknowledged().

Sandbox clients can expose endpoints through resolveExposedPort(port) when the backend supports it.

ClientBehavior
UnixLocalSandboxClientResolves configured ports to 127.0.0.1.
DockerSandboxClientPublishes configured container ports and resolves their host endpoints.

Declare the ports in the client options when you need a backend to enforce an allowlist:

Expose a port
import { DockerSandboxClient } from '@openai/agents/sandbox/local';
const client = new DockerSandboxClient({
image: 'node:22-bookworm-slim',
exposedPorts: [3000],
});
CapabilityUnix-localDocker
exec_commandSupportedSupported
PTY write_stdinSupportedSupported
apply_patchSupportedSupported through workspace file APIs
view_imageSupportedSupported through workspace file APIs
runAs for commandsSupported when the host can resolve and switch to the userLimited by container/user setup
Local snapshotsSupportedSupported
Local/Docker mountsLocal bind-style supportBind and Docker volume-style support

Local PTY support uses a small Python 3 bridge in the SDK process. The bridge is only used for tty: true sessions, where Node.js does not provide a built-in PTY API and the SDK needs standard POSIX PTY behavior for interactive stdin, signal handling, and exit status reporting. Install python3 in the environment that runs your SDK code, or set OPENAI_AGENTS_PYTHON to a Python 3 executable. This is separate from the Python version, if any, installed inside a Docker sandbox image.

Hosted provider support varies by provider. Check the provider-specific docs for exact options, environment variables, port behavior, PTY support, snapshot behavior, and cleanup behavior.