Deterministic sandbox backend selector¶
The selector is a pure function that picks one sandbox backend (worktree, docker, e2b, modal, daytona, blaxel, runloop, vercel) given a workspace manifest, an operator policy, and the credentials currently visible to the orchestrator. Same inputs, same backend pick, every time. No I/O, no registry side effects.
Why it exists¶
Picking a sandbox by reading os.environ mid-spawn produced two failure modes. First, plan replays and dry-runs disagreed with live runs because the environment shifted between invocations. Second, silent fall-throughs to a different runtime when the requested backend lacked credentials hid configuration mistakes for hours.
The selector replaces that with three rules:
- Override-first. An explicit
--sandbox <name>flag (or the equivalent policy field) wins over every heuristic. Missing credentials raiseSandboxSelectionErrorso the operator sees the failure loudly instead of getting a quiet runtime swap. - Cost-aware default order. When no override is set the selector prefers cheaper backends first (
worktree,docker) and only escalates to paid cloud backends when the manifest demands a capability the cheaper backends cannot serve, or when the operator opted into paid execution. - Capability-gated filtering. Backends that cannot satisfy the manifest's required capability set are dropped before precedence runs. The selector's log lines explain why a given backend was skipped.
Default precedence¶
Backends not present in DEFAULT_PRECEDENCE are appended in sorted-name order so plug-in backends still get a stable position.
How to use it¶
Most callers materialise the registered backends with list_backends and hand them to select_sandbox, which returns the chosen backend instance directly:
from bernstein.core.sandbox.selector import (
SandboxEnvironment,
SandboxPolicy,
SandboxSelectionError,
select_sandbox,
)
from bernstein.core.sandbox.registry import list_backends
policy = SandboxPolicy(allow_paid=False) # cheap backends only
env = SandboxEnvironment(
available_credentials=frozenset({"E2B_API_KEY"}),
budget_remaining_usd=2.50,
)
backend = select_sandbox(list_backends(), policy=policy, environment=env)
Force a specific backend with an override:
When no backend satisfies the policy, select_sandbox raises SandboxSelectionError; its attempted attribute lists the backends that were considered:
try:
backend = select_sandbox(list_backends(), policy=policy, environment=env)
except SandboxSelectionError as exc:
print("no eligible backend; attempted:", exc.attempted)
Configuration¶
| Knob | Default | Controls |
|---|---|---|
policy.override | None | Force a named backend. Missing credentials raise SandboxSelectionError. |
policy.allow_paid | False | When False, only worktree and docker are considered. |
policy.required_capabilities | {FILE_RW, EXEC} | Capabilities the chosen backend must advertise. |
policy.required_credentials | frozenset() | Env-var names the backend must have. |
policy.precedence | DEFAULT_PRECEDENCE | Custom ordering; unmentioned backends are appended alphabetically. |
Limitations¶
- The selector does not run health checks. A docker daemon that is installed but offline still counts as available; the spawner reports the failure once it tries to connect.
- Budget-driven escalation (e.g., switch from
worktreetoe2bonce a CPU budget is exceeded) is not in this slice. - The selector is a pure function. Composition with the registry is the caller's responsibility; a caller that ignores the returned
nameand resolves a different backend defeats the determinism guarantee.
Pools as a declared selector input¶
A named sandbox pool composes with the selector without changing it. pool_to_sandbox_policy maps a merged pool placement into a SandboxPolicy (required capabilities from the merge, precedence from the pool backend allowlist, an exposed backend override as the policy override), and select_pool_backend filters the candidate backends to the allowlist before delegating to select_sandbox. The selector stays a pure function; the pool is just another declared input, so with no pool configured selection is byte-identical to today.
Related¶
- Source:
src/bernstein/core/sandbox/selector.py - Pool placement:
src/bernstein/core/sandbox/pool_placement.py - Registry:
src/bernstein/core/sandbox/registry.py - Named sandbox pools
- Sandbox backends