Sandbox Providers
A<Sandbox> boundary runs a child workflow outside the
parent task process. Where it runs is decided by a provider. A provider is a
plain object that takes a request and returns a result. Smithers ships
first-class providers for five cloud backends and a shared kit so a new provider
is a thin adapter.
The built-in providers are Daytona, Vercel, AWS, GCP, and Cloudflare. Each is a
separate optional package with its own SDK as an optional dependency, so you
install only what you use.
The SandboxProvider contract
A provider implementsrun() and an optional cleanup():
run() receives the child workflow, the <Sandbox input> value, the request and
result bundle paths, the network and output-size limits, an abort signal, and a
heartbeat callback. It returns either a bundlePath to a bundle it wrote, or a
structured { status, output, remoteRunId?, workspaceId?, diffBundle? }. Smithers
validates the result, enforces diff-review policy, and returns the outputs to the
parent task. See <Sandbox> for the full request and
result types.
The provider-kit
Cloud providers do not implementrun() from scratch. They call
createCommandSandboxProvider from smithers-orchestrator/sandbox, which owns
the request/result-file protocol once so every provider behaves the same:
- Create a vendor session and emit a
session-createdheartbeat. - Write the request JSON, carrying only the safe fields (
runId,sandboxId,input,config,allowNetwork,maxOutputBytes, redacted egress). - Merge the command env:
options.env, the egress env, and the two path vars below. It never copies arbitraryprocess.env. - Run the entry command with the request’s
toolTimeoutMsand abort signal. - Parse the result from stdout if it starts with
{, otherwise read the result file. A nonzero exit with no valid result JSON throwsSANDBOX_EXECUTION_FAILEDwith stdout and stderr truncated tomaxOutputBytesand redacted. - On
cleanup, resolve the cached session and destroy it per policy.
SandboxSession seam, not the protocol:
SMITHERS_SANDBOX_REQUEST_PATH: where to read the request JSON.SMITHERS_SANDBOX_RESULT_PATH: where to write the result JSON.
SMITHERS_SANDBOX_RESULT_PATH. Providers with no shared filesystem (AWS, GCP)
transport those files through S3 or GCS and inject extra vars pointing the entry
at the object keys.
Selecting a provider
There are three ways to pick a provider, in precedence order:- Provider object (primary). Pass the factory result straight to the prop:
<Sandbox provider={createDaytonaSandboxProvider({...})} workflow={child} />. - Registered id. Register once, then reference by id:
registerSandboxProvider(createVercelSandboxProvider({...}))then<Sandbox provider="vercel-sandbox" />. The provider packages also exportregister<Provider>SandboxProvider(...)convenience wrappers. - Env default (planned, not yet shipped). A future
SMITHERS_SANDBOX_PROVIDERenv var will select a registered id when a<Sandbox>sets no explicitprovider, resolving a registered id only (never auto-creating a cloud client). It is not implemented yet; setting it today has no effect. The intended precedence, once shipped, mirrors the DB-backend chain: an explicitproviderprop wins, then workflow config, thenSMITHERS_SANDBOX_PROVIDER, then the local default. Until then, select a provider with method 1 or 2.
request.config.
Egress and secret redaction
Egress is configured on the<Sandbox egress> prop and reaches the provider as
the normalized request.egress. The kit projects it into the command env
through sandboxEgressEnv(), and when caCertPem is set it uploads the CA to
the workspace so NODE_EXTRA_CA_CERTS resolves inside the sandbox. See
<Sandbox> egress controls.
Providers with a real remote filesystem (Daytona, Vercel, Cloudflare) resolve
that CA path directly. The object-store providers (AWS, GCP) have no shared
filesystem, so they upload the CA to their bundle store and pass its location to
the container: AWS injects SMITHERS_SANDBOX_CA_S3_KEY and GCP injects
SMITHERS_SANDBOX_CA_GCS_OBJECT. The container entry command must download that
object and write it to the path named by NODE_EXTRA_CA_CERTS before it makes
outbound calls.
Credentials are used only to build the local SDK client. They are never placed
in the request JSON and never forwarded into the remote env unless you list them
explicitly in options.env. redactSandboxProviderValue() redacts keys
containing token, secret, key, password, credential, or
authorization from every thrown message, heartbeat payload, and the persisted
configJson audit row.
Provider comparison
| Provider | Package | Provider id | Compute | Bundle transport | Auth | Exit code |
|---|---|---|---|---|---|---|
| Daytona | smithers-orchestrator/daytona | daytona-sandbox | Daytona sandbox | Sandbox filesystem | DAYTONA_API_KEY | numeric |
| Vercel | smithers-orchestrator/vercel | vercel-sandbox | Vercel Sandbox | Sandbox filesystem | OIDC or token trio | numeric |
| AWS | smithers-orchestrator/aws | aws-sandbox | Fargate or CodeBuild | S3 bucket (BYO) | AWS credential chain | Fargate numeric, CodeBuild status |
| GCP | smithers-orchestrator/gcp | gcp-sandbox | Cloud Run Jobs | GCS bucket (BYO) | Application Default Credentials | task status (0/1) |
| Cloudflare | smithers-orchestrator/cloudflare | cloudflare-sandbox | Sandbox SDK container | Sandbox filesystem | Durable Object binding | numeric |