Sandboxes

Function blocks run in one of two places. JavaScript with no import or require runs in a local isolated VM and needs nothing configured — unless the block selects a sandbox, or references a file path from another block or a sandbox output path. Selecting a sandbox with no provider configured is refused with a 503; the file-path cases need the sandbox filesystem and are refused with a 422. Failing user code is also 422, so a 503 always means the deployment rather than the code. Python, Shell, JavaScript that imports, and custom workspace sandboxes run on a remote provider, and that provider needs both credentials and a dedicated Function base image.

Until the base image is configured, those languages fail with an explicit configuration error rather than falling back. A saved Python block is never silently serialized or executed as JavaScript.

Flags

VariablePurpose
SANDBOXES_ENABLEDGrants the server-side self-hosted entitlement
NEXT_PUBLIC_SANDBOXES_ENABLEDBrowser gate for the Function block's Sandbox selector. An assertion of provider readiness, not a check; on a Compose install or source checkout, npx sim-setup doctor compares it with the selected provider's readiness

Shell follows the server: a workspace offers it only when the Sandbox entitlement resolves on — SANDBOXES_ENABLED=true, or the ENTERPRISE_ENABLED master switch with SANDBOXES_ENABLED left unset — and the server has verified the provider credentials and an immutable Function base. Settings → Sandboxes stays in the sidebar either way, but shows an upgrade notice instead of the sandbox list until that check passes. NEXT_PUBLIC_SANDBOXES_ENABLED is a browser gate for one control, the Function block's Sandbox selector. Whether Python or a selected sandbox actually runs depends on the server-side provider and Function image, so set it only after those work, or you surface a selector for sandboxes that cannot run.

Building the Function base

Build the base image before enabling the UI. Custom workspace sandboxes layer their packages on top of it.

E2B_API_KEY=... \
  bun run apps/sim/scripts/build-function-e2b-template.ts \
  --name sim-function

The builder uses E2B's maintained code-interpreter-v1 base, assigns a fresh release generation, and prints both runtime values:

SANDBOX_PROVIDER=e2b
E2B_ENABLED=true
E2B_API_KEY=...
E2B_FUNCTION_TEMPLATE_ID=<sim-function-template>:<sim-function-build-id>
E2B_FUNCTION_TEMPLATE_GENERATION=<release-epoch-ms>
SANDBOXES_ENABLED=true
NEXT_PUBLIC_SANDBOXES_ENABLED=true

--generation remains available for release automation, and --base-template accepts an immutable base override when a deployment deliberately owns one.

The API key needs write:snapshots to build and write:sandboxes to execute.

The builder pins Daytona's packages to an accepted E2B manifest, so build the E2B template first and capture its manifest with the verification step under Promoting a build--parity-manifest expects that file to exist.

DAYTONA_API_KEY=... \
  bun run apps/sim/scripts/build-function-daytona-snapshot.ts \
  --name sim-function-2026-08-03 \
  --parity-manifest /tmp/function-sandbox-manifest.json

Use the immutable snapshot ID the builder prints:

SANDBOX_PROVIDER=daytona
DAYTONA_API_KEY=...
DAYTONA_FUNCTION_SNAPSHOT_ID=<snapshot-uuid>
SANDBOXES_ENABLED=true
NEXT_PUBLIC_SANDBOXES_ENABLED=true

E2B_FUNCTION_TEMPLATE_ID and DAYTONA_FUNCTION_SNAPSHOT_ID fail closed when unset or mutable. The E2B value must be an exact <template>:<build-id> ref, and E2B_FUNCTION_TEMPLATE_GENERATION must be the monotonic value printed by the same build. The Daytona value must be a snapshot ID, not a name.

Promoting a build

Use E2B as the release baseline before building or promoting Daytona.

# 1. Verify the exact E2B Function build and capture its accepted package/runtime surface.
# SANDBOX_PROVIDER is set explicitly: an exported `daytona` would otherwise make
# this step select Daytona and exit before writing the manifest.
SANDBOX_PROVIDER=e2b \
E2B_ENABLED=true \
E2B_API_KEY=... \
E2B_FUNCTION_TEMPLATE_ID=<sim-function-template>:<sim-function-build-id> \
E2B_FUNCTION_TEMPLATE_GENERATION=<release-epoch-ms> \
SANDBOX_PARITY_MANIFEST_OUT=/tmp/function-sandbox-manifest.json \
  bun run apps/sim/scripts/verify-sandbox-parity.ts

# 2. Pin Daytona's reconstructed packages to that accepted E2B manifest.
DAYTONA_API_KEY=... \
  bun run apps/sim/scripts/build-function-daytona-snapshot.ts \
  --name sim-function-2026-08-03 \
  --parity-manifest /tmp/function-sandbox-manifest.json

# 3. Verify the immutable Daytona snapshot against the same baseline before promotion.
SANDBOX_PROVIDER=daytona \
DAYTONA_API_KEY=... \
DAYTONA_FUNCTION_SNAPSHOT_ID=<snapshot-uuid> \
SANDBOX_PARITY_MANIFEST_BASELINE=/tmp/function-sandbox-manifest.json \
  bun run apps/sim/scripts/verify-sandbox-parity.ts

Assign every promoted E2B Function base a generation greater than every prior deployment. A rollback is a new promotion and therefore also needs a new, higher generation — do not reuse the generation from the older release.

Code tools in Chat

Chat's public run_function and run_code tools use a separate shell image, including for JavaScript without imports. The workflow-internal function_execute path uses the Function image instead. If your deployment uses those tools, configure that image for the selected provider as well:

# E2B
MOTHERSHIP_E2B_TEMPLATE_ID=<shell-template-ref>

# Daytona
DAYTONA_SHELL_SNAPSHOT_ID=<shell-snapshot-ref>

These values are selected only for code-tool calls. They never replace or act as a fallback for E2B_FUNCTION_TEMPLATE_ID or DAYTONA_FUNCTION_SNAPSHOT_ID — the shell, Function, document, and Pi images have separate package contracts and release cadences.