Sandboxes#

A sandbox is a persistent cloud computer for one agent: a full Firecracker microVM with its own kernel, disk, IP address and network namespace. It keeps its state between sessions and costs nothing for compute while paused.

TaskCloud is in private beta. Latency numbers on this page are targets, not guarantees.

What is inside#

Property Value
OS Ubuntu 24.04 LTS, x86_64, own Linux kernel
Users user (home /home/user, passwordless sudo); root is allowed
Init real systemd: services you enable come back after pause/resume and fork
Containers Docker inside the VM
Disk persistent, survives pause, resume and host restarts
GPU none (software rendering only); GPU is on the roadmap
Nested virtualization (/dev/kvm) not available; on the roadmap
Region EU only. auto (default) places the sandbox on any EU host; eu-de and eu-fi pin it to a location

Isolation: each sandbox runs in its own Firecracker jailer, cgroup and network namespace. Sandboxes cannot see each other or the host API. See Security.

Sizes#

size vCPU RAM Included disk Price per active hour
small 2 4 GB 20 GB $0.015
default 4 8 GB 50 GB $0.030
large 8 16 GB 100 GB $0.060

Billing is per second while the VM runs. Paused sandboxes cost $0 for compute. Set disk_gb (20–1000) for more disk; disk above the included amount is billed per GB-month. Details: Billing.

The desktop (desktop template) needs size default or larger.

Create a sandbox#

A sandbox starts from exactly one source:

  • a template (template, default base), see Templates;
  • a snapshot (snapshot_id), which restores RAM and disk, see Snapshots and fork;
  • an OCI image (image.ref).

With wait: true (the default) the call returns 201 once the sandbox is running (target p95 < 500 ms from a template). With wait: false it returns 202 in state creating.

import { Client } from "@taskcloud/sdk";

const client = new Client(); // reads TASKCLOUD_API_KEY

const sandbox = await client.sandboxes.create({
  template: "base",
  size: "default",
  name: "user-42",
  labels: { customer: "acme", user: "42" },
  env: { NODE_ENV: "development" },
  auto_pause: { enabled: true, idle_timeout_seconds: 300 },
});
console.log(sandbox.id, sandbox.state); // sbx_2fqxnt4bkq7m running
from taskcloud import Client

client = Client()  # reads TASKCLOUD_API_KEY

sandbox = client.sandboxes.create(
    template="base",
    size="default",
    name="user-42",
    labels={"customer": "acme", "user": "42"},
    env={"NODE_ENV": "development"},
    auto_pause={"enabled": True, "idle_timeout_seconds": 300},
)
print(sandbox.id, sandbox.state)
curl -sS -X POST https://api.task.cloud/v1/sandboxes \
  -H "Authorization: Bearer $TASKCLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: create-user-42" \
  -d '{"template":"base","size":"default","name":"user-42","labels":{"customer":"acme"}}'
taskcloud create --template base --size default --name user-42 --label customer=acme

Create options#

Field Default Notes
template base name, ID, or name:version
snapshot_id – start from a named snapshot
image – { "ref": "ghcr.io/acme/env:1.4", "registry_auth": {...} }
size default small, default, large
name – up to 120 chars
labels – up to 32 key/value pairs; filter with ?label=key=value
env – up to 100 variables, 64 KiB; write-only, never returned
auto_pause {enabled: true, idle_timeout_seconds: 300} see Lifecycle
always_on false never auto-pause; default size only, monthly price
priority standard batch = −50%, may wait in queued
region auto eu-de, eu-fi
ttl_seconds null delete automatically after N seconds
disk_gb size's included disk 20–1000
ports – up to 20 ports to expose at creation
metadata – free-form JSON up to 16 KiB, returned as-is
wait true ignored for priority: "batch"

Send an Idempotency-Key header to make retries safe: replaying the same key with the same body returns the original sandbox.

Inspect, list, update, delete#

const sbx = await client.sandboxes.get("sbx_2fqxnt4bkq7m"); // does not wake a paused sandbox

for await (const s of client.sandboxes.list({ state: ["running", "paused"], label: ["customer=acme"] })) {
  console.log(s.id, s.state, s.last_activity_at);
}

await sbx.update({ name: "user-42-main", labels: { customer: "acme", tier: "pro" } });
await sbx.delete();
sbx = client.sandboxes.get("sbx_2fqxnt4bkq7m")
for s in client.sandboxes.list(state=["running", "paused"], label=["customer=acme"]):
    print(s.id, s.state)
sbx.update(name="user-42-main")
sbx.delete()
  • GET /sandboxes is paginated (limit, cursor) and filters by state, label, name_prefix and template_id.
  • PATCH /sandboxes/{sandbox_id} changes name, labels, auto_pause, always_on, ttl_seconds and metadata.
  • DELETE /sandboxes/{sandbox_id} returns 202. The sandbox moves to deleting, then deleted. Its disk and automatic pause snapshot are removed. Named snapshots are kept.

The sandbox object#

Key fields of Sandbox (full schema in the API reference):

Field Meaning
id sbx_...
state queued, creating, running, pausing, paused, resuming, deleting, deleted, error
size, vcpus, memory_mb, disk_gb resources
template_id, snapshot_id, image, forked_from where it came from
auto_pause, always_on, priority, ttl_seconds, expires_at lifecycle settings
url_template https://a2b3c4d5e6f7-{port}.taskhost.cloud
ports exposed ports
desktop true if the sandbox has a desktop
error {code, message} when state is error
created_at, started_at, paused_at, last_activity_at timestamps

One sandbox per end user#

The most common pattern for agent platforms: give each of your users their own sandbox, label it with your user ID, and let auto-pause stop billing while the user is away. Create a restricted API key per user with restrict.labels so that a leaked key only reaches that user's sandboxes. See Security.