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
| Variable | Purpose |
|---|---|
SANDBOXES_ENABLED | Grants the server-side self-hosted entitlement |
NEXT_PUBLIC_SANDBOXES_ENABLED | Browser 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-functionThe 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.jsonUse 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=trueE2B_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.tsAssign 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.