Skip to content

feat(sandbox): add Daytona as a manual-flip failover for E2B#5860

Merged
TheodoreSpeaks merged 8 commits into
stagingfrom
feat/e2b-alternatives
Jul 23, 2026
Merged

feat(sandbox): add Daytona as a manual-flip failover for E2B#5860
TheodoreSpeaks merged 8 commits into
stagingfrom
feat/e2b-alternatives

Conversation

@TheodoreSpeaks

@TheodoreSpeaks TheodoreSpeaks commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • E2B was a hard single point of failure — lib/execution/e2b.ts had no retry and no fallback, so one failed Sandbox.create() killed Python function blocks, JS-with-imports, shell, doc generation, and the Pi cloud agent
  • Extracts a SandboxRunner boundary (lib/execution/remote-sandbox) with an E2B runner and a Daytona runner, selected by the sandbox-provider-daytona AppConfig flag — flip it during an E2B outage, no redeploy
  • Everything above the provider boundary (marker parsing, mount materialization, file export, corruption handling) is unchanged; all 8 call sites are import-only updates
  • Adds scripts/verify-sandbox-parity.ts — 8 live executions against whichever provider the flag selects, as the pre-flip confidence check
  • Drops the dead E2BExecutionResult.images field (populated, never consumed)

Companion: simstudioai/mothership#369 (builds the shell + doc images on Daytona)

Notes

Selection resolves once before create() and never mid-execution, since user code has side effects. Every sandbox kind fails closed when its snapshot id is unset.

Three Daytona quirks the adapter absorbs:

  • language binds at create(), not per call — Daytona applies it as a sandbox label and silently runs JS through Python if passed to codeRun
  • Python routes via CodeInterpreter for its {name,value,traceback} error shape, which matches E2B's and keeps formatE2BError's line offsets correct
  • the streaming path (Pi) delivers env via the filesystem API, since SessionExecuteRequest has no env field and secrets must not reach a command line

Known caveat: Daytona cold-starts a sandbox in ~45s when it lands on a runner without the image cached (warm is ~800ms vs E2B's ~200ms). It costs wall-clock, not correctness — timeoutMs goes to runCode, not create, and plan sync budgets are 300s/3000s.

Type of Change

  • New feature

Testing

  • New conformance suite runs 11 scenarios twice, once per provider, asserting identical results — 25/25 pass
  • verify-sandbox-parity.ts against both providers with live sandboxes: E2B 8/8, Daytona 8/8 (marker round-trip, data-science imports, structured errors, outbound network, JS-under-node, shell envs, file mount + text export, xlsx compile + base64 binary export)
  • bun run lint and bun run check:api-validation:strict pass; tsc --noEmit clean
  • Affected suites 192/193 — the one failure is cloud-review-tools.test.ts spawning Python that needs a real rg binary, absent locally

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@TheodoreSpeaks
TheodoreSpeaks requested a review from a team as a code owner July 22, 2026 21:36
@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 23, 2026 9:08pm

Request Review

@cursor

cursor Bot commented Jul 22, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Touches the core function-execute, document compile, and Pi agent paths where untrusted code runs; misconfiguration or provider drift could break execution or change error/export semantics despite conformance tests.

Overview
Adds a provider-agnostic remote execution layer so Python/shell/JS-with-imports, doc tooling, and Pi cloud agents no longer depend solely on E2B. The old lib/execution/e2b.ts is removed; shared behavior (mounts, __SIM_RESULT__ parsing, exports, withPiSandbox) lives in lib/execution/remote-sandbox with E2B and Daytona adapters selected by SANDBOX_PROVIDER (default e2b). Provider is chosen once per run (no mid-execution failover).

Config and API surface renames: isE2bEnabledisRemoteSandboxEnabled and isE2BDocEnabledisDocSandboxEnabled, keyed off the active provider’s credentials/snapshots. Call sites switch to executeInSandbox / executeShellInSandbox. User-facing errors refer to a generic “remote code sandbox” instead of naming E2B only.

Daytona ops: new env vars (DAYTONA_* snapshot IDs), @daytonaio/sdk, Pi image build script + shared pi-sandbox-packages.ts, and verify-sandbox-parity.ts for live checks. A dual-provider conformance test suite asserts matching results across E2B and Daytona.

Reviewed by Cursor Bugbot for commit d09765b. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions github-actions Bot added the requires-mothership-merge Has a companion PR on the mothership/copilot side — merge in lockstep label Jul 22, 2026
@github-actions

Copy link
Copy Markdown

⚠️ Cross-repo companion check

One or more companion PRs aren't merged into staging yet. Merging this without them will leave copilot and sim out of sync — merge them in lockstep.

  • simstudioai/mothership#369OPEN, not merged (targets staging) — feat(sandbox): build the mothership sandbox images on Daytona too

Comment thread apps/sim/lib/execution/remote-sandbox/index.ts
Comment thread apps/sim/lib/execution/remote-sandbox/daytona.ts
Comment thread apps/sim/lib/execution/remote-sandbox/daytona.ts Outdated
@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds Daytona as a manually selected alternative to E2B for remote sandbox workloads. The main changes are:

  • A shared sandbox interface with E2B and Daytona implementations.
  • Provider-aware gates for function execution and document generation.
  • Updated execution, Pi agent, and document workflows.
  • Cross-provider conformance tests and live parity scripts.
  • Daytona snapshot build configuration and dependencies.

Confidence Score: 5/5

This looks safe to merge.

  • The function execution gate now recognizes a Daytona-only deployment.
  • The document gate now uses Daytona credentials and the document snapshot when Daytona is selected.
  • The updated imports and exports are consistent across the affected runtime and test paths.
  • No blocking issue remains in the reviewed fixes.

Important Files Changed

Filename Overview
apps/sim/lib/core/config/env-flags.ts Adds provider-aware availability flags for general execution and document sandboxes.
apps/sim/app/api/function/execute/route.ts Moves shell, Python, and imported JavaScript execution to the provider-neutral sandbox interface.
apps/sim/lib/execution/remote-sandbox/index.ts Selects the configured provider and exposes the shared execution API.
apps/sim/lib/execution/remote-sandbox/daytona.ts Implements Daytona execution, file transfer, environment handling, and sandbox cleanup.
apps/sim/lib/copilot/tools/server/files/doc-compile.ts Routes supported document compilation through the selected remote sandbox provider.

Reviews (6): Last reviewed commit: "fix(sandbox): handle orphaned stream pro..." | Re-trigger Greptile

Comment thread apps/sim/app/api/function/execute/route.ts
Comment thread apps/sim/lib/copilot/tools/server/files/doc-compile.ts
E2B was a hard single point of failure: lib/execution/e2b.ts had no retry
and no fallback, so a failed Sandbox.create() killed Python function blocks,
JS-with-imports, shell, doc generation and the Pi cloud agent outright.

Extract a SandboxRunner boundary (lib/execution/remote-sandbox) with an E2B
runner and a Daytona runner, selected once per execution by the
sandbox-provider-daytona AppConfig flag. Everything above the provider
boundary — marker parsing, mount materialization, file export, corruption
handling — is unchanged.

Selection resolves before create() and never mid-execution, since user code
has side effects. Each sandbox kind fails closed when its snapshot id is unset.

Notes on the Daytona adapter:
- language binds at create(), not per call: Daytona applies it as a sandbox
  label and silently runs JS through Python if passed to codeRun
- Python routes via CodeInterpreter for its {name,value,traceback} error shape,
  which matches E2B's and keeps formatE2BError's line offsets correct
- timeouts convert ms to seconds
- the streaming path delivers env via the filesystem API, as
  SessionExecuteRequest has no env field and secrets must not reach a command line

Drops the dead E2BExecutionResult.images field (populated, never consumed).
Replaces the boolean sandbox-provider-daytona feature flag with a
SANDBOX_PROVIDER env var naming the provider ('e2b' default, or 'daytona').
A boolean doesn't scale to a third adapter; a keyed registry does.

- PROVIDERS is a Record<SandboxProviderId, SandboxProvider>, so adding an
  adapter is one entry plus one id-union member — an unhandled provider is a
  compile error, not a runtime surprise
- resolveProvider() reads env synchronously and throws on an unknown value
  (fail fast) instead of an async feature-flag lookup
- drops the sandbox-provider-daytona flag and SANDBOX_PROVIDER_DAYTONA fallback

Verified end-to-end: a Python function block through the running app routes to
Daytona (Creating Daytona sandbox, kind: code) with SANDBOX_PROVIDER=daytona.
Addresses the review round on #5860.

- Availability was gated on isE2bEnabled / isE2BDocEnabled, so a Daytona-only
  deployment (E2B_ENABLED unset) had its Python/shell/JS-with-imports and doc
  paths rejected before the provider-neutral sandbox call could run. Replace both
  with provider-aware flags (isRemoteSandboxEnabled / isDocSandboxEnabled) derived
  from the selected SANDBOX_PROVIDER's own credentials + image. E2B behavior is
  unchanged (the E2B branch mirrors the old definitions exactly).
- Make the function-block gate error messages provider-neutral.
- Daytona's streaming runCommand (Pi) returned empty stdout/stderr and delivered
  output only via callbacks, so the Pi cloud flow — which parses markers from
  stdout and formats errors from stderr — saw nothing. Accumulate the streamed
  chunks and return them while still forwarding to the callbacks.

Renames the env-flag exports (and the @sim/testing mock) to match. Adds a
conformance test that the streamed Pi output lands in stdout/stderr.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/execution/remote-sandbox/index.ts
Addresses the round-2 review on #5860. env-flags lowercased SANDBOX_PROVIDER
for the availability gate, but resolveProvider looked up the raw value in a
lowercase-keyed map — so 'Daytona' passed the gate then threw Unknown
SANDBOX_PROVIDER at create. resolveProvider now normalizes casing identically.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit bf6991c. Configure here.

check:utils flagged the inline `error instanceof Error ? error.message : ...`
pattern in the two new scripts. Use getErrorMessage from @sim/utils/errors,
matching the repo convention the check enforces.
Comment thread apps/sim/lib/execution/remote-sandbox/index.ts
Daytona merges both streams into stdout and returns an empty stderr, but the
shell-error, base64-export, and URL-mount error builders read only
result.stderr — so Daytona failures surfaced a generic 'Process exited with
code N' / 'base64 failed' / 'curl exited N' instead of the real command output
that the API and agents rely on. Fall back to stdout before the generic message
(provider-agnostic: E2B still populates stderr). Strengthens the shell-error
conformance test to assert the real output surfaces.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/execution/remote-sandbox/daytona.ts
Comment thread apps/sim/app/api/function/execute/route.ts
… copy

- Daytona's streaming path (Pi) started the command with runAsync:true and then
  awaited getSessionCommandLogs with no bound, so a hung command never timed out
  the way E2B's commands.run({ timeoutMs }) does. Race the log stream against the
  timeout; on expiry return exit 124 with the accumulated output, and the finally's
  deleteSession terminates the still-running command.
- Two user-facing strings still named E2B after the provider-neutral rename (the
  isolated-vm sandboxPath remediation and the disabled-xlsx message). Made both
  provider-neutral.

Adds a streaming-timeout conformance test.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/execution/remote-sandbox/daytona.ts Outdated
Comment thread apps/sim/lib/execution/remote-sandbox/daytona.ts Outdated
Two regressions from the previous timeout fix:
- When the timeout won the race, the abandoned getSessionCommandLogs promise
  would reject on deleteSession with no handler (unhandledRejection). Attach a
  .catch that records the error and yields an 'error' outcome, so a late
  rejection is always handled.
- The streaming catch dropped the thrown error, so failures before any chunks
  (env write, executeSessionCommand, missing cmdId) surfaced as empty output.
  Fall back to getErrorMessage(error) when nothing streamed.

Adds conformance tests for the stream-reject and start-throw paths.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit d09765b. Configure here.

@TheodoreSpeaks
TheodoreSpeaks merged commit 96c67e9 into staging Jul 23, 2026
20 checks passed
@TheodoreSpeaks
TheodoreSpeaks deleted the feat/e2b-alternatives branch July 23, 2026 23:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

requires-mothership-merge Has a companion PR on the mothership/copilot side — merge in lockstep

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant