On Runtime, one fork call returns up to 100 running copies of a prepared
machine. Memory and running processes are included. The snapshot that the call takes for
itself costs nothing. The source pauses while the fork is taken. It runs again when
the call returns. Each copy is billed like a new sandbox of its size: $0.03125 an
hour while the agent waits on its model, at 2 vCPU and 4 GiB. These are Runtime's rates
as published on 26 October 2026 (pricing).
Why explore in parallel
A single agent run is one sample from a distribution. Ask again with another plan, another prompt or another model, and you get a different answer. Some of those answers are better than others. Scoring several attempts and keeping the best raises the odds that at least one succeeds. This works when you can do two things cheaply:
- Give every attempt the same starting point. Otherwise a better score may come from a luckier environment, not a better solution.
- Score every attempt in the same objective way, with tests, a benchmark, a type checker or a rubric, not by asking the agent whether it succeeded.
A fork provides the first. Your scorer runs inside each copy to provide the second.
Parallel exploration versus other patterns
| Pattern | Branches from | Suits | Page |
|---|---|---|---|
| Best of N (this page) | One prepared state | A task with a clear score: tests, speed, size | Here |
| Tree search | Every promising step | Long tasks where early choices matter | Tree search with forks |
| pass@k evaluation | A task image | Measuring a model, not shipping a fix | Agent evals |
| Candidate filtering | An installed project | Keeping only generated artifacts that pass | Test generation |
The core loop
The example asks an agent to make a slow function faster. Each copy gets a different strategy. The scorer runs the tests and a benchmark. The fastest correct copy wins.
TypeScriptimport { Sandbox } from "withruntime";const strategies = [ "Vectorise the inner loop with NumPy.", "Cache repeated lookups; change nothing else.", "Rewrite the parser as a single pass.", "Profile first, then fix the top hotspot only.",];// Your agent loop: sandbox tools bound to `copy`, the strategy in the prompt.async function runAgent(copy: Sandbox, strategy: string) { await copy.files.write("/workspace/strategy.txt", strategy);}await using base = await Sandbox.create({ diskMiB: 8192, timeoutSeconds: 3600 });await base.exec("git clone --depth 1 https://github.com/your-org/your-lib.git repo", { check: true, timeoutMs: 300_000,});await base.exec("cd repo && pip install -e . pytest", { check: true, timeoutMs: 600_000 });const copies = await base.fork({ count: strategies.length, labels: { task: "speed-up-parse" } });const scored = await Promise.all( copies.map(async (copy, i) => { await runAgent(copy, strategies[i]!); const tests = await copy.exec("python3 -m pytest -q", { cwd: "/workspace/repo", timeoutMs: 900_000, }); const bench = await copy.exec("python3 bench.py", { cwd: "/workspace/repo", timeoutMs: 300_000, }); const seconds = tests.exitCode === 0 ? Number(bench.stdout.trim()) : Infinity; return { copy, strategy: strategies[i]!, seconds }; }),);scored.sort((a, b) => a.seconds - b.seconds);const best = scored[0]!;const diff = await best.copy.exec("git diff", { cwd: "/workspace/repo" });console.log(best.strategy, best.seconds, diff.stdout.length);await Promise.all(scored.map((s) => s.copy.stop()));Pythonfrom concurrent.futures import ThreadPoolExecutorfrom withruntime import Sandboxstrategies = [ "Vectorise the inner loop with NumPy.", "Cache repeated lookups; change nothing else.", "Rewrite the parser as a single pass.", "Profile first, then fix the top hotspot only.",]def run_agent(copy, strategy): """Your agent loop: sandbox tools bound to copy, the strategy in the prompt.""" copy.files.write("/workspace/strategy.txt", strategy)def attempt(pair): copy, strategy = pair run_agent(copy, strategy) tests = copy.exec("python3 -m pytest -q", cwd="/workspace/repo", timeout_ms=900_000) bench = copy.exec("python3 bench.py", cwd="/workspace/repo", timeout_ms=300_000) seconds = float(bench.stdout.strip()) if tests.exit_code == 0 else float("inf") return {"copy": copy, "strategy": strategy, "seconds": seconds}with Sandbox.create(disk_mib=8192, timeout_seconds=3600) as base: base.exec("git clone --depth 1 https://github.com/your-org/your-lib.git repo", check=True, timeout_ms=300_000) base.exec("cd repo && pip install -e . pytest", check=True, timeout_ms=600_000) copies = base.fork(count=len(strategies), labels={"task": "speed-up-parse"}) with ThreadPoolExecutor(max_workers=len(copies)) as pool: scored = sorted(pool.map(attempt, zip(copies, strategies)), key=lambda s: s["seconds"]) best = scored[0] print(best["strategy"], best["seconds"], len(best["copy"].exec("git diff", cwd="/workspace/repo").stdout)) for s in scored: s["copy"].stop()The copies run at the same time. The total wall-clock time is roughly that of the slowest attempt.
What varies between attempts
Any input to your agent can be the axis:
- Plans, as above. Each copy gets one strategy, written by a person or proposed by a planning model.
- Models: the same prompt sent to two or three providers, scored on equal terms.
- Temperatures or seeds: the same model, sampled several times.
- Tool budgets: a short run and a long run, to find out whether more steps help this kind of task.
Record the axis in the copy's labels. Later, runtime.sandboxes.list({ labels })
finds every copy of one exploration. Lifecycle events for each copy are kept for
14 days (metrics and webhooks).
Past 100 copies
fork makes 1 to 100 copies per call. For more copies, keep the prepared
machine as a snapshot with base.snapshot({ name, retentionDays }). Then create
sandboxes from it by passing snapshot: id. The account limits how many
sandboxes run at the same time: 100 on a paid account. Creates beyond
that limit wait for room. An account without credit runs four sandboxes
at the same time. On such an account, a fork of three copies, plus the source, is
the most that fits. A kept snapshot is billed as storage at $0.08 per
decimal GB per 30-day month. The fork's own snapshot is deleted when the fork
ends (snapshots and forks).
What parallel exploration needs
| Need | How Runtime covers it |
|---|---|
| An identical start for every attempt | Forks copy files, memory and running processes |
| Attempts that cannot interfere | Each copy is its own Firecracker microVM with its own kernel and disk |
| Fast branching | About a second for a fresh sandbox. The time grows with the memory it holds |
| Cheap waiting on models | Billed on measured CPU: $0.03125 an hour when idle, at 2 vCPU, 4 GiB |
| A copy that fails to start | details.startedSandboxIds lists the copies that started; stop them |
| Knowing which attempt was which | Labels on each copy; runtime.sandboxes.list({ labels }) |
What it costs
Take 100 tasks a month. Each task is explored with ten copies. Each copy runs for 15 minutes on 2 vCPUs and 4 GiB. Its builds, tests and benchmark use 180 CPU-seconds:
- CPU
- 1,000 × 180 s / 3,600 × $0.025
- $1.25
- Memory
- 1,000 × 900 s / 3,600 × 4 GiB × $0.0075
- $7.50
- Total
- $8.75
Each task costs about 9 cents for ten attempts. This does not include the prepared base, which is one more sandbox per task (pricing). Your model provider bills model calls separately. Every account gets 100 free hours of a 1 GB machine in its first 30 days, with no card.
Related: tree search with forks, what a sandbox fork is, coding agent sandbox, dependency upgrades, RL environments.
Facts on this page were checked on 26 October 2026.