On Runtime, 500 background tasks of 20 minutes each cost about $5.63 a month on 2 vCPU, 4 GiB sandboxes, at the rates in effect on 26 October 2026. The reason is that a sandbox is billed for the CPU its commands use rather than the CPUs it holds. A paid account runs 100 sandboxes at the same time. A create past that limit waits for room instead of failing. So a burst of tasks queues by itself.
The short answer
A worker takes a task. It creates the task's sandbox with an idempotency key made from the task id. Then it runs the agent and returns what the agent changed:
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime({ waitForCapacityMs: 600_000 }); // queue up to 10 minutes for roomexport async function runTask(task: { id: string; repo: string; prompt: string }) { await using sbx = await runtime.sandboxes.create( { name: `task-${task.id}`, labels: { kind: "background", task: task.id }, diskMiB: 8192, maxTotalCostMicros: 2_000_000, // at most $2 for this task }, { idempotencyKey: `task-${task.id}` }, // a retried worker never makes a second sandbox ); // No time limit: the sandbox runs while the agent works, however long. await sbx.exec(["git", "clone", "--depth", "1", task.repo, "repo"], { check: true, timeoutMs: 300_000, }); await sbx.files.upload("./agent", "/workspace/agent"); await sbx.files.write("/workspace/prompt.txt", task.prompt); const agent = await sbx.spawn("python3 /workspace/agent/main.py /workspace/prompt.txt", { cwd: "/workspace/repo", }); const done = await agent.wait(); const diff = await sbx.exec("git diff", { cwd: "/workspace/repo" }); return { exitCode: done.exitCode, diff: diff.stdout };}Pythonfrom withruntime import Runtimeruntime = Runtime(wait_for_capacity=600) # queue up to 10 minutes for roomdef run_task(task_id: str, repo: str, prompt: str) -> dict: with runtime.sandboxes.create( name=f"task-{task_id}", labels={"kind": "background", "task": task_id}, disk_mib=8192, max_total_cost_micros=2_000_000, # at most $2 for this task idempotency_key=f"task-{task_id}", # a retried worker never makes a second sandbox ) as sbx: # No time limit: the sandbox runs while the agent works, however long. sbx.exec(["git", "clone", "--depth", "1", repo, "repo"], check=True, timeout_ms=300_000) sbx.files.upload("./agent", "/workspace/agent") sbx.files.write("/workspace/prompt.txt", prompt) agent = sbx.spawn("python3 /workspace/agent/main.py /workspace/prompt.txt", cwd="/workspace/repo") done = agent.wait() diff = sbx.exec("git diff", cwd="/workspace/repo") return {"exit_code": done.exit_code, "diff": diff.stdout}Leaving the block deletes the sandbox, so a finished task stops incurring charges. If a crash puts the task back on the queue, the same idempotency key returns the same sandbox instead of starting a duplicate.
What goes wrong with background agents, and what stops it
| Risk | What handles it |
|---|---|
| More tasks arrive than can run | create waits and retries on quota_exceeded, no_capacity or no_credit_running_limit |
| A worker stops partway through | The agent process lives in the sandbox; processes.get(id) reattaches |
| A retry starts the task twice | Every write carries an idempotency key; pass your own to work across restarts |
| One task spends too much | maxTotalCostMicros on the sandbox and a daily spending limit on the key |
| An agent reaches somewhere it should not | Per-sandbox network allow and deny lists, enforced on the host |
| The agent needs a token | A secret the sandbox never holds |
| A failure goes unnoticed | A webhook on sandbox.stopped and sandbox.start_failed |
A paid account runs 100 sandboxes at the same time. It runs up to 200 vCPUs and 400 GiB of memory across the running sandboxes. Support raises these limits on request (pricing). An account without credit runs four at the same time.
See what is running
Labels make the fleet queryable. List the running background tasks, or all of them, from any process:
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();const page = await runtime.sandboxes.list({ labels: { kind: "background" }, state: ["running", "paused"],});for await (const sbx of page) console.log(sbx.info.name, sbx.state, sbx.info.chargedMicros);Pythonfrom withruntime import Runtimeruntime = Runtime()for sbx in runtime.sandboxes.list(labels={"kind": "background"}, state=["running", "paused"]): print(sbx.info["name"], sbx.state, sbx.info["chargedMicros"])chargedMicros is the cost of each sandbox so far, in millionths of a dollar.
From a terminal, runtime sandboxes ls shows the same list.
Tell the user when the task is done
A background agent is useful when the person who asked does not have to wait. Send lifecycle events to your own endpoint. Notify the user from there:
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();const hook = await runtime.webhooks.create({ url: "https://example.com/hooks/runtime", events: ["sandbox.stopped", "sandbox.start_failed"],});console.log(hook.secret); // shown once; verify each delivery with verifyWebhookEach event carries the sandbox as it was, labels included. The handler can find
the task from labels.task. Runtime keeps events for 14 days. Read them with
runtime.events.list() (metrics and webhooks).
Keep a finished task to look at
Some tasks end with a person reviewing the result. Instead of stopping the
sandbox, pause it. The repository, the agent's scratch files and any dev server
stay as they were. Share a port with previews.create(port) so the reviewer
can open it. Stop the sandbox when the review closes.
What it costs
Assume 500 tasks a month. Each keeps a 2 vCPU, 4 GiB sandbox running for 20 minutes. In that time, each uses 180 CPU-seconds of work, which is three minutes of one core:
- CPU
- 500 × 180 s / 3,600 × $0.025
- $0.63
- Memory
- 500 × 20 min / 60 × 4 GiB × $0.0075
- $5.00
- Total
- $5.63
That is a little over a cent a task. Runtime charges $0.025 per vCPU-hour of measured CPU, never less than a twentieth of a vCPU. It charges $0.0075 per GiB-hour of memory (pricing). You pay your model provider for model calls, not Runtime.
Start
Terminalnpx withruntime sandboxes run -- git --versionThe command asks you to approve the connection in a browser, once per machine. Then point one worker at the queue with the code above. Use the JavaScript or Python SDK.
Related: long-running agents, coding agent sandbox, Claude Code in a sandbox, egress control.
Facts on this page were checked on 26 October 2026.