<Sandbox> is a task boundary for work that runs outside the parent task process. It’s provider-first: pass an injectable provider object or a registered provider id; runtime covers only the built-in legacy local transports.
SandboxProvider, SandboxProviderRequest,
SandboxProviderResult, ExecuteSandboxOptions) are public exports from
"smthrs/sandbox". Import them with import type when writing adapters.
Egress controls
Useegress when the sandbox itself should own outbound-network configuration. Smithers doesn’t mutate the parent harness environment for this path; it serializes the egress contract into the request and passes it to the selected provider or local transport.
request.egress; the raw serializable value stays in request.config.egress for adapters needing provider-specific fields. The JSX prop is typed unknown; at execution time Smithers accepts a provider object directly or a string id only after registering a provider with registerSandboxProvider({ id, run }). The unknown type lets provider packages supply their own shapes, so TypeScript will not reject a bad provider at the JSX call site; invalid provider values fail during execution with INVALID_INPUT. Smithers core doesn’t hardcode an iron-proxy adapter. Local transports merge the generated proxy environment into the sandbox handle environment, after env, so HTTP_PROXY, HTTPS_PROXY, NO_PROXY, and NODE_EXTRA_CA_CERTS are visible inside the sandbox process.
When caCertPem is provided, Smithers writes it into the request bundle at .smithers/egress/ca.crt and points local transports at /workspace/.smithers/egress/ca.crt. If the CA is already installed by the sandbox image or sidecar, use caCertPath instead. Persisted sandbox config redacts egress env values, CA PEM, and secret-binding entries.
Basic usage
workflow takes a SmithersWorkflow: the same value a workflow file’s default
export holds. smithers((ctx) => ...) returns one, so the child is just a
createSmithers module you import, no extra wrapping needed:
run() result is what the parent receives: minimally
{ status: "finished", output, remoteRunId?, workspaceId? }, where output
matches the output schema (outputs.result above). Add diffBundle when the
sandbox changed files (see Result bundles).
Complete one-file provider and child workflow
The smallest complete pattern: a concrete provider object,allowNetwork={true},
and a child workflow in one file. Real providers usually create a VM or
container but return the same structured SandboxProviderResult.
Execution model
- Smithers renders
<Sandbox>as one scheduler task; children don’t become parent-run tasks. - At execution time Smithers writes a request bundle under
.smithers/sandboxes/<run>/<sandbox>/request-bundle. - A provider receives the request, runs work remotely, and returns a local bundle path or a structured result.
- Smithers validates the result bundle, records sandbox lifecycle events, enforces diff review policy, applies accepted
diffBundles, and returnsoutputsto the parent task output table.
SandboxProviderRequest and return a SandboxProviderResult:
Result bundles
A provider can return a path to a bundle it created:diffBundle directly. Use bundlePath when the remote side already
wrote a full Smithers result bundle, you need to preserve larger artifacts, or
the provider owns bundle materialization.
Bundle limits are enforced before the result is accepted: 100 MB total, 5 MB manifest file (README.md), 1,000 patch files, bounded JSON output, and no path traversal or symlinks in bundle paths.
Diff review
reviewDiffs defaults to true. If the sandbox returns patch files or a diffBundle, Smithers records SandboxDiffReviewRequested. When autoAcceptDiffs is false, changed bundles fail closed until reviewed; when it’s true, or reviewDiffs is false, Smithers applies diffBundle via the engine diff-bundle applier. Legacy patch files are still collected and review-gated, but the apply path is diffBundle.
Nested sandboxes
Nested sandbox execution is disabled by default (a sandbox running inside another must setallowNested). Use it only when the provider and diff policy are designed for nesting. Hard cases:
- Diff base conflicts: an inner sandbox can generate a
diffBundleagainst a different base than the outer sandbox. - Cleanup ordering: an outer provider cleanup can delete the workspace before the inner provider finishes.
- Quotas and concurrency: nested remote VMs can multiply resource usage quickly.
- Network and secrets: inherited remote credentials may be broader than intended.
- Event lineage: parent run, outer sandbox run, and inner sandbox run need clear ids for debugging.
Parallel or MergeQueue instead of nesting.
Built-in local transports
Whenprovider is omitted, Smithers uses the legacy local transport path: runtime is "bubblewrap" (the default), "docker", or "codeplane". "cloudflare" is provider-backed, via createCloudflareSandboxProvider() from smthrs/cloudflare. Unknown runtimes fail closed; Docker is no longer silently replaced by bubblewrap when unavailable.
First-class providers
Smithers ships a local microVM provider and five cloud providers, each a separate optional package mapping the provider contract onto a runtime SDK:<Sandbox provider={createDaytonaSandboxProvider({...})}> runs the child
workflow on that backend with no runtime change. See
Sandbox Providers for the shared contract,
provider-kit, selection precedence, and a comparison table.
- Microsandbox Provider: id
microsandbox(local microVM) - Daytona Sandbox Provider: id
daytona-sandbox - Vercel Sandbox Provider: id
vercel-sandbox - AWS Sandbox Provider: id
aws-sandbox(Fargate or CodeBuild, S3 transport) - GCP Sandbox Provider: id
gcp-sandbox(Cloud Run Jobs, GCS transport) - Cloudflare: id
cloudflare-sandbox