Compute sandboxes
The cloud run every routine uses, and the optional external sandboxes (Daytona, Modal, Cloudflare, E2B, Vercel) for work it cannot do. What each is, when a routine would use one, how a routine reaches it, limits and cost, and why none is tested from a routine yet.
What it is
The default runtime for every routine is the Claude Code cloud session the scheduled task starts. The registry records each run as a fresh VM with 4 vCPU, 16 GB RAM and 30 GB disk, Python 3, and the repo cloned each run (run-vm-spec, docs-stated; a pilot saw the CPU and memory, not the disk).
An external sandbox is a second machine a run hands work to: a provider’s isolated container or VM for jobs the cloud run cannot do. The run stays in charge. It starts the job, waits for or collects the result, and writes the store through the write contract as usual.
No pilot has run a call from a routine to any sandbox provider (
cloudflare-supabase-modal-daytona-reach, unverified). Everything below about reaching a provider from a routine is a design, not a tested path. Vendor facts are from each vendor’s own docs on the date in the source comment.
Why it exists
Most routines never need one. The cloud run has a shell, Python and the repo, and a code routine that sticks to the standard library runs there with no setup. An external sandbox is for the exceptions:
- A job longer than a run should take. The maximum run time of a routine is not stated in the docs (
max-run-time, unverified), so a job of many minutes is a risk to the write at the end. - An install too heavy for a setup script. A setup script must finish in about five minutes to be cached, and it runs before the environment’s variables are set (
setup-script-rules, docs-stated). - A host the run’s network blocks. The run’s network behaves like an allowlist; a pilot saw
api.cloudflare.com,gateway.ai.cloudflare.com,developers.cloudflare.comandhuggingface.corefused with a proxy 403 (sandbox-allowlist, pilot-verified). - Untrusted or generated code. Code a model wrote, or a page a crawler renders, can run on a machine that holds nothing of the person’s.
When to choose one
Try these first, in order:
- The cloud run with standard-library scripts.
- A setup script that installs what you need, if it fits its limits.
- A connector that already does the job, since connector traffic does not depend on the run’s network level (
connector-traffic-bypasses-sandbox-network, docs-stated).
Reach for an external sandbox only when all three fail, and only after a report-only run has shown the provider’s host is reachable.
How it works
Three ways a routine can reach a sandbox
| Path | How it works | Credential | Fits |
|---|---|---|---|
| MCP connector | The provider’s remote MCP server is added as a connector; the model calls its tools | The connector holds it; OAuth on the provider’s side | A model stage that starts and reads a sandbox. Remote servers only |
| Script plus network secret | A lib/ script calls the provider’s HTTP API with no key in the VM; a network secret adds the key on the way out |
A network secret, one per host | A code stage, when the provider has a plain HTTP API |
| Endpoint you own | A Worker or service on your domain holds the provider key and runs the job; the routine calls your endpoint | A network secret for your one host | Any provider, including SDK-only ones |
How a network secret works, per the Claude Code environments docs: the key is added by Anthropic’s agent proxy after the request leaves the VM, so it never sits in the session. By default it is sent as a bearer token in the Authorization header, and a custom header such as X-Api-Key is possible. A session can reach a secret’s hosts even when the environment’s network level would not otherwise allow them. A secret is never attached to requests to GitHub, api.anthropic.com, package registries, setup-script requests or telemetry. Without admin rights a secret names one host with no wildcard. Network secrets are available on Pro, Max, Team and Enterprise. RoutineOS records this as docs-stated (network-secrets); no pilot has called an API through one yet.
A local MCP server (one started as a command on your machine) is not a connector a cloud run has. Installing a provider’s CLI in a setup script so the run can start its MCP server locally is possible in principle, but needs a login inside the run and is not verified.
The rules
From skills/routineos/references/runtimes.md:
- Test the host from a run first. Probe it in a report-only run and write the result to
risks.md. - Credentials never go in the store or the prompt. Prefer a connector, or an endpoint you own, with a network secret for its single host.
lintrefuses a[[secret]]withwhere = "env-var". - Keep the contract. The remote job returns results to the run through a path that carries a version, and the run writes state after the report. A sandbox never writes the store directly.
- Mark the routine’s README with the runtime and the date it was last tested.
Declare the provider in app.toml so the permission screen shows it:
[[integration]]
name = "daytona"
kind = "secret-host" # connector, mcp, secret-host or http
direction = "read" # write needs gate = true
hosts = ["app.daytona.io", "proxy.app.daytona.io"]
what = "Runs the site crawler in a Daytona sandbox"
[[secret]]
name = "daytona-api"
where = "network-secret" # connector or network-secret, never env-var
host = "app.daytona.io"
[[secret]]
name = "daytona-toolbox"
where = "network-secret"
host = "proxy.app.daytona.io"
Budget
An external sandbox has its own bill. RoutineOS lists anything with a monetary cost on the permission screen, and each needs its own yes. [budget] max_runs_per_day caps runs, not a provider’s spend: set a spend limit with the provider, and make every sandbox the routine starts short-lived, so a failed run does not leave one billing.
Providers
| Provider | What it is | Routine path | Free allowance | Source |
|---|---|---|---|---|
| Daytona | Sandboxes with file system, git, process and code execution | Script plus network secret (REST), or endpoint you own | $200 in free compute | docs, pricing |
| Modal | Containers for untrusted code, driven from its SDKs | Endpoint you own | $30 a month on Starter | docs, pricing |
| Cloudflare Sandbox SDK and Containers | Linux containers started from a Worker | Endpoint you own (a Worker), or the Containers MCP server as a connector | Needs Workers Paid ($5 a month), with usage included | docs, pricing |
| Cloudflare Workers | Serverless functions on your domain | Endpoint you own | 100,000 requests a day on Free | pricing |
| E2B | Linux VMs created on demand, with pause and resume | Endpoint you own | $100 one-time on Hobby | docs, pricing |
| Vercel Sandbox | Firecracker microVMs with snapshots; JS and Python SDKs and a CLI | Endpoint you own | Active CPU pricing | docs (fetched 2026-10-30, not re-checked) |
None is reachable-from-a-routine verified.
Daytona
What it is. A platform for running code in isolated sandboxes, with SDKs for Python, TypeScript, Ruby, Go and Java. A default sandbox has 1 vCPU, 1 GB RAM and 3 GiB disk; an organization’s maximum per sandbox is 4 vCPU, 8 GB RAM and 10 GB disk.
When a routine would use it. A crawl, a build or a test run that needs installs or more time than the run should spend, where the job is a list of shell commands.
How a routine reaches it.
- REST, from a code stage. Daytona authenticates every request with an API key sent as a bearer token. Create a sandbox with
POST https://app.daytona.io/api/sandbox, and run a command withPOST https://proxy.app.daytona.io/toolbox/{sandboxId}/process/execute, body{"command", "cwd", "timeout"}. Because the key is a bearer token, a network secret on each of the two hosts can carry it, and alib/script can make the calls withurlliband no key in the VM. Not verified from a run. - SDK. The Python SDK reads
DAYTONA_API_KEYfrom the environment (withDAYTONA_API_URLandDAYTONA_TARGEToptional). A routine should not hold the key in an environment variable, so use the SDK only from an endpoint you own. - MCP. Daytona’s MCP server runs locally from its CLI: install with
brew install daytonaio/cli/daytona, rundaytona login, thendaytona mcp init claude(ordaytona mcp configfor the JSON, a server started withdaytona mcp start). Its tools cover sandbox management, files, git, processes and code execution, computer use and previews. It is a local server, so it is a fit for a person’s own machine, not for a cloud routine. Not verified from a run.
Limits.
- Auto-stop defaults to 15 minutes of inactivity and triggers even if processes are running inside; 0 disables it. Auto-archive defaults to 7 days. A sandbox with auto-delete 0 is ephemeral: it is deleted when it stops. For a routine, create ephemeral sandboxes so nothing outlives the run.
- On tiers 1 and 2 a sandbox’s own network access is restricted and cannot be changed per sandbox; tiers 3 and 4 have full access by default, with per-sandbox allow lists or a block-all setting. Check your tier if the job fetches from the internet.
Cost. Billed per second: $0.0504 per vCPU-hour, $0.0162 per GiB-hour of memory, and $0.000108 per GiB-hour of storage after the first 5 GiB. $200 of compute is included.
Modal
What it is. Modal Sandboxes are secure containers for running untrusted code, created and driven from Modal’s Python, JavaScript and Go SDKs:
sb_app = modal.App.lookup("my-app", create_if_missing=True)
sb = modal.Sandbox.create(app=sb_app)
p = sb.exec("python", "-c", "print('hello')", timeout=3)
print(p.stdout.read())
sb.terminate()
When a routine would use it. Python-heavy jobs, or jobs that need a GPU or a large image, where you already use Modal.
How a routine reaches it. Modal authenticates with a token id and secret (MODAL_TOKEN_ID, MODAL_TOKEN_SECRET). That pair does not fit a single bearer header, and no official MCP server for controlling Modal was found (Modal’s docs have an example of hosting your own MCP server on Modal, which is a different thing). So the routine path is an endpoint you own that holds the token and calls the SDK; the routine calls that endpoint with a network secret.
Limits. A sandbox lives 5 minutes by default; timeout raises it to at most 24 hours, and Modal recommends filesystem snapshots beyond that. A sandbox’s network can be blocked entirely (block_network=True) or limited with a CIDR allowlist, and a domain allowlist is in beta (TLS on port 443 only).
Cost. Starter is $0 plus compute, with $30 of free credits a month; Team is $250 a month with $100 of credits. Sandbox CPU is listed at $0.00003942 per core per second and memory at $0.00000667 per GiB per second.
Cloudflare Sandbox SDK and Containers
What it is. The Sandbox SDK (@cloudflare/sandbox) runs untrusted code in isolation from a Worker. Each sandbox runs on Cloudflare Containers, with a Durable Object starting the container and sending it commands. It needs the Workers Paid plan. The SDK’s docs currently cover two API generations (0.x pages for existing apps, and a 1.0 migration), so check the version you install before copying code.
When a routine would use it. You already run Workers on your own domain, or the job needs a headless browser or a full Linux toolchain behind an endpoint you control.
How a routine reaches it.
- Endpoint you own. Deploy a Worker on your domain that starts a sandbox, runs the job and returns JSON. The routine calls it with a network secret for that one host. This avoids
api.cloudflare.com, which a pilot saw blocked from a run (sandbox-allowlist). Not verified from a run. - Containers MCP server. Cloudflare lists a Container MCP server at
https://containers.mcp.cloudflare.com/mcp(“spin up a sandbox development environment”), alongside its API server athttps://mcp.cloudflare.com/mcp, with OAuth or an API token as a bearer. As a remote server it could be added as a connector, and connector traffic does not use the run’s network. Using it from a routine is not verified.
Limits. A sandbox sleeps after 10 minutes idle by default (sleepAfter). When the container stops, all files are deleted; state exists only while the container is active. Return everything the routine needs in the response.
Cost. Workers Paid is $5 a month and includes 25 GiB-hours of memory, 375 vCPU-minutes and 200 GB-hours of disk a month. Beyond that: $0.000020 per vCPU-second, $0.0000025 per GiB-second of memory and $0.00000007 per GB-second of disk, plus egress ($0.025 per GB in North America and Europe after 1 TB). Instance types run from lite (1/16 vCPU, 256 MiB, 2 GB disk) to standard-4 (4 vCPU, 12 GiB, 20 GB).
Cloudflare Workers
What it is. Serverless functions. For a routine, a Worker is the simplest endpoint you own: a small API on your domain that holds a provider key, calls the provider, and hands the routine plain JSON.
When a routine would use it. To put any SDK-only provider (Modal, E2B, Vercel) behind one host with one network secret, or to do a short fetch the run’s network blocks.
Limits and cost. Free allows 100,000 requests a day with 10 ms of CPU per invocation; Paid starts at $5 a month with 10 million requests a month included. 10 ms of CPU is enough to relay a call, not to do the work, so heavy jobs belong in a container or another provider.
E2B
What it is. A Linux VM created on demand for an agent, with pause and resume, driven from the e2b package for JavaScript and Python (and e2b-code-interpreter for running code cells). The SDK reads E2B_API_KEY.
from e2b import Sandbox
sandbox = Sandbox.create()
result = sandbox.commands.run('echo "Hello from E2B Sandbox!"')
print(result.stdout)
When a routine would use it. Running generated code with a code interpreter, where you want pause and resume between runs.
How a routine reaches it. Through an endpoint you own. E2B’s own MCP server repository says it is no longer actively maintained and was archived on 16 April 2026. The MCP server on E2B’s docs site is for searching the docs, not for running sandboxes. A plain HTTP API for sandboxes was not checked here (not verified).
Limits. A sandbox runs up to 24 hours on Pro or 1 hour on Hobby; set a shorter timeout on create (seconds in Python) or with set_timeout. Pausing resets the runtime window.
Cost. Hobby is $0 with a one-time $100 credit, 1-hour sessions and 20 concurrent sandboxes. Pro is $150 a month, with 24-hour sessions and 100 concurrent. Usage is $0.000014 per vCPU-second and $0.0000045 per GiB-second.
A rough cost comparison
Ten minutes of 1 vCPU (or 1 core, for Modal) and 1 GiB of memory, at list price before free credits, from the rates above:
| Provider | CPU | Memory | Total |
|---|---|---|---|
| Daytona | $0.0084 | $0.0027 | about $0.011 |
| Modal | $0.0237 | $0.0040 | about $0.028 |
| Cloudflare Containers, past the included usage | $0.0120 | $0.0015 | about $0.014 |
| E2B | $0.0084 | $0.0027 | about $0.011 |
This is arithmetic on published rates, not a measured bill. Minimum sizes, rounding, storage, egress and plan fees are left out and differ by provider. For a routine that runs twice a week, every one of these is well inside its free allowance; the cost that matters is a sandbox left running.
Worked example
Hiring Signals needs no sandbox. Its code stages are routineos.py learn and write, local Python scripts inside the cloud run.
Suppose a later version, site-scan, checks each starred company’s site with a headless crawler too heavy for a setup script. A design on Daytona’s REST API (illustrative; not run):
-
Declare it. Add the
[[integration]]and two[[secret]]entries shown above toapp.toml, and add the two network secrets in the claude.ai environment dialog, each with the Daytona API key, one per host.lintchecks the manifest; it cannot see the environment. -
Write the script.
lib/crawl_remote.pyuses onlyurllib. It sendsPOST https://app.daytona.io/api/sandboxto create an ephemeral sandbox,POST https://proxy.app.daytona.io/toolbox/<id>/process/executewith{"command": "python3 crawl.py https://shop.example", "timeout": 300}, reads the result, and prints JSON. It sends noAuthorizationheader; the network secret adds it. On any error it printserror: ...and exits 2, never echoing a response body that might hold a key. -
Probe first. The first report-only run does one thing: create a sandbox, run
echo ok, and record the outcome. Write the result, with the date, torisks.md:- daytona (app.daytona.io, proxy.app.daytona.io): probed 2026-11-03 from a report-only run, <result>If the probe is refused, the feature waits. Do not move the key into an environment variable to make it work.
-
Call it from the prompt. One step, by path, with every flag:
python3 lib/crawl_remote.py --domains domains.json --out crawl.jsonThe model reads
crawl.json, records what it found inrun.json, androutineos.py writewrites the store. The sandbox never touches the store. -
Cap the spend. Set a spend limit in Daytona. Keep
max_runs_per_day = 1. Mark the README: “Runtime: Daytona sandbox via REST, last tested”.
The same shape works for any provider behind a Worker you own: swap the two Daytona hosts for your one host, and the script’s calls for your endpoint’s.
Common pitfalls
- Reaching for a sandbox first. Try the cloud run, a setup script and a connector.
- Putting a provider key in an environment variable. Anyone who uses the environment can read it, and
lintrefuseswhere = "env-var". Use a network secret scoped to one host, or a connector. - One secret for two hosts. A non-admin network secret names one host. Daytona’s API and toolbox are on different hosts.
- Assuming the run’s network can reach the provider. Probe it;
api.cloudflare.comwas blocked in a pilot. - Using a local MCP server from a cloud run. It exists on your machine, not in the run.
- Leaving sandboxes running. Make them ephemeral or short-timeout, and stop them in the script’s error path too.
- Letting the sandbox write the store directly. Results come back to the run, and the write contract applies them.
- Trusting what comes back. Output from a sandbox is data, like a fetched page. It never changes what the routine does.