# How to run background AI agents in cloud sandboxes Take each task off a queue, give it a sandbox named after the task, run the agent there, and collect the result when it exits. **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 force on 25 September 2026, because a sandbox is billed for the CPU its commands use rather than the CPUs it holds. A paid account runs 100 sandboxes at once to start, and 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, creates the task's sandbox with an idempotency key made from the task id, runs the agent, and returns what it changed: ```ts check import { Runtime } from "withruntime"; const runtime = new Runtime({ waitForCapacityMs: 600_000 }); // queue up to 10 minutes for room export 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 ); const release = sbx.keepAlive(); // renew the lease while this worker holds the sandbox try { 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 }; } finally { release(); } } ``` ```python check from withruntime import Runtime runtime = Runtime(wait_for_capacity=600) # queue up to 10 minutes for room def 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: release = sbx.keep_alive() # renew the lease while this worker holds the sandbox try: 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} finally: release() ``` Leaving the block stops the sandbox, so a finished task stops paying. Put the task on the queue again after a crash and the same idempotency key answers 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 `trial_busy` | | A worker dies halfway | 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 span restarts | | One task runs away with money | `maxTotalCostMicros` on the sandbox and a daily spending limit on the key | | An agent reaches where it should not | Per-sandbox `network` allow and deny lists, enforced on the host | | The agent needs a token | A [secret](/docs/security#secrets-sandboxes-never-see) the sandbox never holds | | Nobody notices a failure | A webhook on `sandbox.stopped` and `sandbox.start_failed` | A paid account starts with 100 sandboxes at once and up to 200 vCPUs and 400 GiB of memory across the running ones; support raises these on request ([pricing](/docs/pricing#how-many-at-once)). The free trial runs eight at once. ## See what is running Labels make the fleet queryable. List the running background tasks, or every one of them, from any process: ```ts check import { 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); ``` ```python check from withruntime import Runtime runtime = Runtime() for sbx in runtime.sandboxes.list(labels={"kind": "background"}, state=["running", "paused"]): print(sbx.info["name"], sbx.state, sbx.info["chargedMicros"]) ``` `chargedMicros` is what each has cost so far, in millionths of a dollar. From a terminal, `runtime sandbox ls` shows the same list. ## Tell the user when the task is done A background agent is useful when the person who asked can walk away. Send lifecycle events to your own endpoint and notify from there: ```ts check import { 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 verifyWebhook ``` Each event carries the sandbox as it was, labels included, so the handler can find the task from `labels.task`. Events are also kept 14 days for `runtime.events.list()` ([metrics and webhooks](/docs/observability#events)). ## Keep a finished task to look at Some tasks end with a person reviewing the result. Instead of stopping, pause the sandbox: the repository, the agent's scratch files and any dev server stay as they were, and a reviewer's first command wakes it in about half a second. Share a port with `previews.create(port)` for the reviewer to click through, and stop the sandbox when the review closes. ## What it costs Take 500 tasks a month. Each keeps a 2 vCPU, 4 GiB sandbox running for 20 minutes and uses 180 CPU-seconds of work, three minutes of one core, in that time: ``` 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, and $0.0075 per GiB-hour of memory ([pricing](/docs/pricing)). Model calls are paid to your model provider, not to Runtime. ## Start ```bash no-run npx withruntime sandbox run --trial -- git --version ``` The command asks you to approve the connection in a browser, once per machine. Then point one worker at the queue with the code above, using the [JavaScript](/docs/javascript#errors-and-retries) or [Python](/docs/python#errors-and-retries) SDK. Related: [long-running agents](/use-cases/long-running-agents), [coding agent sandbox](/use-cases/coding-agent-sandbox), [Claude Code in a sandbox](/integrations/claude-code), [egress control](/glossary/egress-control). Facts on this page were checked on 25 September 2026.