Runtime

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:

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  );  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();  }}
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:        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 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). 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:

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 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:

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 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).

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:

TextCPU:     500 × 180 s / 3,600 × $0.025          = $0.63Memory:  500 × 20 min / 60 × 4 GiB × $0.0075    = $5.00Total:                                           $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). Model calls are paid to your model provider, not to Runtime.

Start

Terminalnpx 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 or Python SDK.

Related: long-running agents, coding agent sandbox, Claude Code in a sandbox, egress control.

Facts on this page were checked on 25 September 2026.