Lifecycle#
A sandbox moves between a few well-defined states. The important ones for your bill are running (billed per second) and paused ($0 compute, full RAM and disk state kept).
States#
(priority: "batch" only)
┌─────────┐
│ queued │ waiting for capacity, not billed
└────┬────┘
▼
POST /sandboxes ──► ┌──────────┐
│ creating │ not billed
└────┬─────┘
▼
┌──────────┐ pause / auto-pause ┌─────────┐ ┌────────┐
┌──────────► │ running │ ───────────────────► │ pausing │ ────► │ paused │
│ └──────────┘ └─────────┘ └───┬────┘
│ │ resume /
│ ┌──────────┐ │ wake on request
└─────────── │ resuming │ ◄──────────────────────────────────────────┘
└──────────┘
any state ── DELETE / ttl expired ──► deleting ──► deleted
any state ── VM failure ──────────► error (see sandbox.error)
| State | Meaning |
|---|---|
queued |
priority: "batch" only; waiting for capacity |
creating |
VM is being started from a template, snapshot or image |
running |
VM is running; exec, files, ports, SSH and desktop work |
pausing |
RAM and disk are being snapshotted at the same instant |
paused |
VM is stopped; RAM and disk are kept in a snapshot |
resuming |
VM is being restored (target p95 < 800 ms) |
deleting, deleted |
sandbox and its data are being removed / gone |
error |
VM failed; sandbox.error has code and message |
Processes, open TCP connections, terminal sessions, desktop windows and browser tabs survive a pause: resume restores memory, it does not reboot.
Pause and resume#
await sandbox.pause(); // waits until "paused" (wait: true by default)
await sandbox.resume(); // waits until "running"
sandbox.pause()
sandbox.resume()
curl -sS -X POST https://api.task.cloud/v1/sandboxes/sbx_2fqxnt4bkq7m/pause \
-H "Authorization: Bearer $TASKCLOUD_API_KEY" -H "Content-Type: application/json" \
-d '{"wait": false}' # 202, state "pausing"
taskcloud pause sbx_2fqxnt4bkq7m
taskcloud resume sbx_2fqxnt4bkq7m
With wait: true (default) the call blocks until the transition finishes (max 60 s, then 202). Pausing a paused sandbox or resuming a running one is a no-op that returns 200.
Auto-pause#
Every sandbox pauses itself after auto_pause.idle_timeout_seconds without activity. Defaults: enabled: true, idle_timeout_seconds: 300 (5 minutes). Range: 60–86400 seconds.
Activity means any of:
- a running exec command or an open PTY session;
- inbound traffic on an exposed port or over SSH;
- a connected desktop viewer, or desktop input;
- CPU usage above a low threshold.
An open file watch (GET .../files/watch) does not count as activity.
await sandbox.update({ auto_pause: { enabled: true, idle_timeout_seconds: 900 } });
sandbox.update(auto_pause={"enabled": True, "idle_timeout_seconds": 900})
curl -sS -X PATCH https://api.task.cloud/v1/sandboxes/sbx_2fqxnt4bkq7m \
-H "Authorization: Bearer $TASKCLOUD_API_KEY" -H "Content-Type: application/json" \
-d '{"auto_pause":{"enabled":true,"idle_timeout_seconds":900}}'
taskcloud update sbx_2fqxnt4bkq7m --idle-timeout 15m
taskcloud update sbx_2fqxnt4bkq7m --no-auto-pause
Wake on request#
You rarely need to call resume. A paused sandbox wakes automatically when it receives:
- an HTTP request to one of its port URLs (
https://<short_id>-<port>.taskhost.cloud) or its desktop URL; - an SSH connection through
ssh.taskhost.cloud; - an API call that needs a running VM: exec, PTY, files, screenshot, input.
The request waits for the resume (target p95 < 800 ms) and then completes normally, without an error on the client side. GET /sandboxes/{sandbox_id} does not wake a sandbox.
To fail fast instead, pass auto_resume=false on API calls; a paused sandbox then returns 409 sandbox_paused:
curl -sS -X POST "https://api.task.cloud/v1/sandboxes/sbx_2fqxnt4bkq7m/exec?auto_resume=false" \
-H "Authorization: Bearer $TASKCLOUD_API_KEY" -H "Content-Type: application/json" \
-d '{"command":"uptime"}'
Always-on#
always_on: true disables auto-pause. Instead of hourly billing, the sandbox costs a flat $19.90 per month (prorated). Available for size default only.
await sandbox.update({ always_on: true });
taskcloud update sbx_2fqxnt4bkq7m --always-on
TTL#
ttl_seconds deletes the sandbox automatically N seconds after creation (min 60; null keeps it until you delete it). expires_at shows the deadline. Change it with PATCH. Expiry sends sandbox.deleted with reason: "ttl_expired".
Batch priority#
priority: "batch" is billed at −50%. In exchange the sandbox may wait in queued until capacity is available, and the create call always returns 202 (wait is ignored). Use it for evals, CI and other jobs that are not interactive. Work run during the EU night window (22:00–07:00 CET) gets the same −50% automatically.
const job = await client.sandboxes.create({ template: "base", priority: "batch", wait: false });
await job.waitFor("running");
job = client.sandboxes.create(template="base", priority="batch", wait=False)
job.wait_for("running")
taskcloud create --template base --batch --no-wait
Billing per state#
Compute is billed per second while the VM runs. Prices per active hour: small $0.015, default $0.030, large $0.060.
| State | Compute | Storage |
|---|---|---|
queued, creating |
not billed | – |
running |
per second at the size rate | disk above the included amount: $0.05/GB-month |
pausing, resuming |
per second, as running |
– |
paused |
$0 | pause snapshot free for 30 days, then $0.03/GB-month; extra disk as above |
deleting, deleted |
not billed | not billed |
error |
not billed | disk kept until you delete the sandbox |
always_on: true |
flat $19.90/month (default) instead of per second |
as above |
Batch priority and the off-peak window (22:00–07:00 CET) are billed at −50%. See Billing for plans and credit.
Checkpoints and host failures#
TaskCloud saves sandboxes to replicated object storage, encrypted with a key of your account:
- Pause: a paused sandbox's memory and disk are uploaded a few minutes after the pause. It can then resume on any host, even if its original host fails.
- Running sandboxes: after about 15 minutes of activity since the last resume or checkpoint, TaskCloud takes a disk checkpoint. The sandbox keeps running; there is no pause. The checkpoint is crash-consistent, like the disk after a power cut: files flushed to disk are in it, data still in the guest's page cache may not be.
If the host running a sandbox fails, TaskCloud restores the sandbox on another host from its newest saved state and sends sandbox.restored_from_checkpoint:
| Newest saved state | After the restore |
|---|---|
pause snapshot (kind: memory) |
memory, processes and files as of that pause |
disk checkpoint (kind: disk, ram_lost: true) |
files as of the checkpoint; the sandbox booted, so processes and /dev/shm restarted |
At most about 15 minutes of disk work is lost (data_loss_window_seconds in the webhook tells you how much). Long-running agents should keep their progress in files (and call sync after important steps) so they can continue from a disk checkpoint. Paused sandboxes are not affected: their pause snapshot is restored as is.
A sandbox paused less than a few minutes before its host failed may not be uploaded yet. It stays paused until the host returns.
Webhooks per transition#
| Event | Sent when | Typical reason |
|---|---|---|
sandbox.ready |
sandbox first becomes running after create or fork |
api |
sandbox.paused |
state becomes paused |
api, auto_pause, spend_limit, host_maintenance |
sandbox.resumed |
state becomes running after resuming |
api, wake_on_request |
sandbox.error |
state becomes error |
an error code |
sandbox.deleted |
state becomes deleted |
api, ttl_expired |
sandbox.restored_from_checkpoint |
sandbox runs again from an older saved state after a host failure | host_lost, restore_interrupted |
Each event carries previous_state, state, reason and the full sandbox. Setup and signature verification: Webhooks.