placing then ready Create
Ask for a sandbox with a GPU, an image, a network mode and a TTL.
fx.sandboxes.create(gpu="L40S", ttl="15m") 03Data and network
Private Preview · Prices reviewed 2026-10-10
DevelopSandboxes Early access
Sandboxes are isolated CPU and GPU environments for AI agents: one per RL episode, eval case or CI run. Your code creates them, runs untrusted code inside, and each one ends when its time-to-live does. Metering stops with it.
One call creates 24 sandboxes with a 15-minute TTL. At 12 minutes, 9 are running and 15 have exited. Five more exit before 15 minutes, when the TTL stops the last 4. From 15 minutes on, nothing runs and nothing is billed. 4 sandboxes were switched from closed to restricted networking while running. Billed in total: 14,025 sandbox-seconds.
Give every rollout its own environment. Create one sandbox per episode, step it from your trainer, and let the TTL clean up after it.
import fantasti
fx = fantasti.Client() # reads FANTASTI_API_KEY
def rollout(policy, seed):
# one sandbox per episode; its TTL cleans up after it
sb = fx.sandboxes.create(image="ghcr.io/acme/env:latest",
ttl="15m", network="closed")
obs = sb.exec(f"python env.py reset --seed {seed}").stdout
total = 0.0
for _ in range(MAX_STEPS):
step = sb.exec(f"python env.py step {policy.act(obs)}")
obs, reward, done = parse(step.stdout)
total += reward
if done:
break
return total # nothing to tear down $ fantasti sandbox create --image ghcr.io/acme/env:latest \
--ttl 15m --network closed
$ fantasti sandbox exec sb_41c9 -- python env.py reset --seed 7 Output · illustrative
sb_41c9 placing cpu · ttl 15m · network closed
sb_41c9 ready
sb_41c9 running python env.py reset --seed 7
sb_41c9 exit 0 {"obs": [0.02, -0.41, 0.03, 0.27]} Run each eval case in a clean environment with the network closed, so nothing leaks or caches between cases.
import fantasti
fx = fantasti.Client() # reads FANTASTI_API_KEY
cases = load_cases("evals/swe-tasks.jsonl")
# one clean sandbox per case: nothing leaks or caches between
boxes = fx.sandboxes.create(n=len(cases), gpu="L40S",
image="ghcr.io/acme/eval:latest",
ttl="20m", network="closed")
results = [box.exec(case.command)
for box, case in zip(boxes, cases)]
passed = sum(run.exit_code == 0 for run in results)
print(f"{passed} of {len(cases)} cases passed") $ fantasti sandbox create --gpu L40S --ttl 20m \
--network closed --image ghcr.io/acme/eval:latest
$ fantasti sandbox exec sb_7q2m -- python grade.py --case 0412 Output · illustrative
sb_7q2m placing L40S:1 · ttl 20m · network closed
sb_7q2m ready
sb_7q2m running python grade.py --case 0412
sb_7q2m exit 1 case 0412 · 3 of 5 checks passed Run GPU tests on every pull request without keeping a GPU machine on. The sandbox ends when the job does.
import sys
import fantasti
fx = fantasti.Client() # reads FANTASTI_API_KEY
# a fresh GPU for this pull request; a hung job ends at the TTL
with fx.sandboxes.create(
gpu="L40S", image="ghcr.io/acme/ci:latest", ttl="30m",
network="restricted",
allow_domains=["pypi.org", "files.pythonhosted.org"],
) as sb:
run = sb.exec("pytest -q tests/gpu", timeout="20m")
print(run.stdout[-2000:])
sys.exit(run.exit_code) # the sandbox has already ended $ fantasti sandbox create --gpu L40S --ttl 30m --network closed
$ fantasti sandbox exec sb_41c9 -- pytest -q tests/gpu
$ fantasti sandbox delete sb_41c9 Output · illustrative
sb_41c9 placing L40S:1 · ttl 30m · network closed
sb_41c9 ready
sb_41c9 running pytest -q tests/gpu
sb_41c9 exit 0 212 passed
sb_41c9 ended disk deleted · metering stopped Execute code your agent wrote with the network closed and a short TTL, on nodes isolated from other accounts.
import fantasti
fx = fantasti.Client() # reads FANTASTI_API_KEY
with fx.sandboxes.create(
gpu="L40S", ttl="15m", network="closed",
) as sb:
sb.files.write("/work/patch.py", patch)
run = sb.exec("python /work/patch.py --check", timeout="5m")
print(run.exit_code, run.stdout[-2000:])
# ends here, or at its TTL, whichever comes sooner {
"mcpServers": {
"fantasti": {
"type": "http",
"url": "https://mcp.fantasti.ai/mcp"
}
}
} Tools · early access
sandboxes_create
sandboxes_exec
sandboxes_read_file
sandboxes_write_file
sandboxes_delete asks for confirmation curl -X POST https://api.fantasti.ai/v1/sandboxes \
-H "Authorization: Bearer $FANTASTI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "gpu": "L40S", "ttl": "15m", "network": "closed" }'
# { "id": "sb_41c9…", "status": "placing" } A sandbox moves through four steps, each visible in the API and on your statement.
placing then ready Ask for a sandbox with a GPU, an image, a network mode and a TTL.
fx.sandboxes.create(gpu="L40S", ttl="15m") running Run commands and stream their output.
sb.exec("pytest -q") running Write inputs in and read results out.
sb.files.read("/work/out.json") ended The TTL ends it, or you delete it sooner. Its disk is deleted with it.
sb.delete() 0 seconds metered after the time-to-live.
In the illustrative run at the top of this page, the clock stops 4 sandboxes at 15:00. Nothing runs and nothing is metered from then on.
Sandboxes run on a node pool reserved for your account, inside your isolated cluster. The Fantasti Orchestrator places each sandbox on a node with the GPU it asks for and keeps the pool sized to your request rate. Sandboxes from different accounts never share a node.
One call asks for 12 sandboxes with one L40S each, a 15m time-to-live and a closed network. The Fantasti Orchestrator checks the request against the limits your admins set (200 concurrent sandboxes, a maximum time-to-live of 60m) and places the sandboxes on 3 nodes of the node pool reserved for your account, 4 on each. One of them, sb_41c9, sits on node-1 with its own kernel and a closed network; it ends at its time-to-live and its disk is deleted with it. The pool gains a node when your request rate rises. Another account's pool never runs your sandboxes. Connections: Orchestrator to Another account's pool: Never placed here; policy.yaml to Orchestrator; fx.sandboxes.create to Orchestrator; Orchestrator to node-1; Orchestrator to node-2; Orchestrator to node-3; node-1 to sb_41c9.
n=12,gpu="L40S",image="acme/eval:latest",ttl="15m",network="closed"Added as your request rate rises.
Its nodes run only its ownsandboxes. Yours are neverplaced there.
limits: sandboxes: concurrent: 200 max_ttl: 60mn=12,gpu="L40S",image="acme/eval:latest",ttl="15m",network="closed"Added as your request raterises.
limits: sandboxes: concurrent: 200 max_ttl: 60mIts nodes run only its own sandboxes.Yours are never placed there.
n=12,gpu="L40S",image="acme/eval:latest",ttl="15m",network="closed"Added as your request rate rises.
limits: sandboxes: concurrent: 200 max_ttl: 60mIts nodes run only its own sandboxes. Yoursare never placed there.
The SDK your harness imports.
import fantasti
fx = fantasti.Client()
with fx.sandboxes.create(
gpu="L40S", ttl="15m", network="closed",
) as sb:
sb.files.write("/work/patch.py", patch)
run = sb.exec("python /work/patch.py")
out = sb.files.read("/work/out.json")
# ended: by exit, or by its TTL fx = fantasti.Client() Any language, any runtime.
curl -X POST \
https://api.fantasti.ai/v1/sandboxes \
-H "Authorization: Bearer $FANTASTI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "gpu": "L40S", "ttl": "15m",
"network": "closed" }' Response · illustrative
{ "id": "sb_41c9…",
"status": "placing" } POST /v1/sandboxes {
"mcpServers": {
"fantasti": {
"type": "http",
"url": "https://mcp.fantasti.ai/mcp"
}
}
} Session · illustrative
> Run my evals on an L40S, no network.
sandboxes_create sb_7q2m ready
sandboxes_write_file /work/cases.jsonl
sandboxes_exec pytest -q exit 0
sandboxes_read_file /work/out.json
sandboxes_delete confirm? yes ended "mcpServers": { "fantasti": … } sandboxes_createsandboxes_execsandboxes_read_filesandboxes_write_filesandboxes_deletesandboxes_delete asks for confirmation.Each sandbox has one of three network modes. Closed blocks all outbound traffic. Restricted allows the domains you list and nothing else. Open allows outbound traffic except to private ranges and the metadata address.
| Destination | |||
|---|---|---|---|
| pypi.org | Allow | Allow | Block |
| files.pythonhosted.org | Allow | Allow | Block |
| github.com | Allow | Block | Block |
| ghcr.io | Allow | Block | Block |
| 10.0.0.0/8 private | Block | Block | Block |
| 169.254.169.254 cloud metadata | Block | Block | Block |
| 0.0.0.0/0 anything else | Allow | Block | Block |
network="open"allow_domains=["pypi.org", "files.pythonhosted.org"]network="closed"A sandbox is metered per second from start to end, at the rate of the CPU or GPU it uses. When the TTL ends it, metering ends with it. Sandboxes appear on the same statement as the rest of your usage.
| Granularity | Seconds charged |
|---|---|
| Per hour | 3,600 s |
| Per minute | 300 s |
| Per second | 250 s |
Fantasti meters by the second and bills hourly (Terms §3). A 4 min 10 s task is charged 250 seconds.
| Question | Sandboxes Early access | Workspaces Early access | Serverless Early access | GPU Instances |
|---|---|---|---|---|
| You bring | An agent or test harness | Your editor and code | A container | An image, or your own stack |
| Fantasti runs | Short-lived isolated environments | A dev environment that scales to a cluster | One job or one endpoint | One machine with 1 or 8 GPUs |
| Ends | At its time-to-live | When you stop it | When the job exits or you stop the endpoint | When you stop it or its term ends |
| Keeps | Nothing; copy results out | Your files | Nothing on the container disk | Its volumes |
| Capacity | On-demand | On-demand head; workers on-demand or spot | On-demand or spot | On-demand, spot, reserved |
| Use it when | Code is untrusted or disposable | You are writing or debugging | The job is one container | You need a GPU and root |
Until the TTL you set. Early-access accounts have a maximum TTL set per account.
Yes. Request a GPU type from the instance catalog when you create it.
No. Your sandboxes run on nodes that belong to your account.
The sandbox stops and its disk is deleted. Copy results out before then.
Yes. It calls the same API you do, or the tools of the MCP server, inside the limits your admins set. Deleting a sandbox over MCP asks for confirmation.
No. Sandboxes run on on-demand capacity. Spot is for work that checkpoints and can wait.
Sandboxes is in early access. Request access with your use case.
Access opens in cohorts. Companies can request a place in the first, and places are limited. Tell us about your RL, eval or agent workload.
A request reads200 × sandbox · L40S · ttl 15m · network closed
Received
request r_
Request received.
We reply to a reviewed request, usually within one business day. The owner reads every request for the first cohort, and an approved request is invited by a sign-up link that is tied to the email address, valid for 72 hours and works once.
You are on the waitlist.
Requests from personal email addresses join the waitlist for the next phase. We will write when a place opens.
Message received.
We reply by email.
Dev preview: no endpoint is configured, so nothing left this browser. Production keeps the form and says that nothing was sent.