On Runtime, a setup that worked once becomes the starting point of every later task. A snapshot keeps the sandbox's files, memory and running processes while you have credit. The next task starts with dependencies installed, services running and the test suite known to pass. Onboarding 40 repositories a month costs $7.20 at Runtime's rates, snapshots included. The calculation is below (pricing, 26 October 2026).
What "onboarding a repository" means for an agent
A person new to a codebase reads the README, installs the toolchain, works through the one dependency that needs a system library, and runs the tests until they pass. An agent does the same work, faster and less carefully. Two things make it safe and useful:
- A machine of its own. The agent runs
curl | shinstallers,sudo apt-get, and whatever the README says. None of it should touch your laptop or CI runners. - A record of what worked. The commands that led to a passing test run are the setup script that nobody wrote down. Keep those commands, and keep the machine.
Every sandbox has Ubuntu 24.04 with Python 3.12, Node.js 24, Bun, git and gcc.
sudo works, so most projects need only their own dependencies
(the sandbox environment).
The core loop
Clone the repository and show it to the agent. Run the commands the agent chooses. Stop when it says the tests pass. Keep every command, then snapshot the result:
TypeScriptimport { Runtime } from "withruntime";// Your model: given everything so far, the next shell command, or null when the tests pass.type NextCommand = (transcript: string) => Promise<string | null>;const runtime = new Runtime();export async function onboard(repoUrl: string, next: NextCommand, maxSteps = 30) { const sbx = await runtime.sandboxes.create({ diskMiB: 16_384, timeoutSeconds: 3600, labels: { job: "onboard" }, }); await sbx.exec(["git", "clone", "--depth", "1", repoUrl, "/workspace/repo"], { check: true, timeoutMs: 300_000, }); const look = await sbx.exec("ls -a; head -c 6000 README* 2>/dev/null", { cwd: "/workspace/repo", }); let transcript = look.stdout; const steps: string[] = []; for (let i = 0; i < maxSteps; i++) { const command = await next(transcript); if (command === null) break; const run = await sbx.exec(command, { cwd: "/workspace/repo", timeoutMs: 900_000 }); if (run.exitCode === 0) steps.push(command); const tail = (run.stdout + run.stderr).slice(-4000); transcript += `\n$ ${command}\nexit ${run.exitCode}${run.timedOut ? " (timed out)" : ""}\n${tail}`; } await sbx.files.write( "/workspace/setup.sh", `set -e\ncd /workspace/repo\n${steps.join("\n")}\n`, { mode: 0o755, }, ); const snapshot = await sbx.snapshot({ name: "repo-ready", retentionDays: 30 }); await sbx.stop(); return { snapshotId: snapshot.id, steps };}Pythonfrom withruntime import Runtimeruntime = Runtime()def onboard(repo_url: str, next_command, max_steps: int = 30) -> dict: """next_command(transcript) returns the next shell command, or None when the tests pass.""" sbx = runtime.sandboxes.create(disk_mib=16_384, timeout_seconds=3600, labels={"job": "onboard"}) sbx.exec(["git", "clone", "--depth", "1", repo_url, "/workspace/repo"], check=True, timeout_ms=300_000) transcript = sbx.exec("ls -a; head -c 6000 README* 2>/dev/null", cwd="/workspace/repo").stdout steps = [] for _ in range(max_steps): command = next_command(transcript) if command is None: break run = sbx.exec(command, cwd="/workspace/repo", timeout_ms=900_000) if run.exit_code == 0: steps.append(command) tail = (run.stdout + run.stderr)[-4000:] transcript += f"\n$ {command}\nexit {run.exit_code}{' (timed out)' if run.timed_out else ''}\n{tail}" sbx.files.write("/workspace/setup.sh", "set -e\ncd /workspace/repo\n" + "\n".join(steps) + "\n", mode=0o755) snapshot = sbx.snapshot(name="repo-ready", retention_days=30) sbx.stop() return {"snapshot_id": snapshot["id"], "steps": steps}- The clone uses an array command, so a repository URL cannot carry shell
syntax. For a private repository, upload your local checkout with
sbx.files.upload("./repo", "/workspace/repo")instead. No token then enters the sandbox. - A command that runs longer than 60 seconds streams its output. Its result keeps everything it printed. The loop keeps only the end of the output, so a noisy install cannot flood the model's context.
- Only commands that exit with code 0 go into
setup.sh. Review the file before you run it anywhere else.
Start every later task from the working machine
The snapshot holds the repository, installed and tested. A coding task, an eval or a review creates a sandbox from it:
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();export async function taskFrom(snapshotId: string, testCommand: string) { await using sbx = await runtime.sandboxes.create({ snapshot: snapshotId, labels: { job: "task" }, }); await sbx.exec("git pull --ff-only", { cwd: "/workspace/repo", timeoutMs: 120_000 }); const tests = await sbx.exec(testCommand, { cwd: "/workspace/repo", timeoutMs: 900_000 }); return tests.exitCode === 0; // still green? then hand the sandbox to the agent}Several agents at the same time? fork({ count }) copies a running sandbox into 1 to 100 running sandboxes that it returns together. Each one has its own disk
(sandbox forks).
Snapshot or image?
| Keep it as | What it holds | Best when | Charged |
|---|---|---|---|
| A snapshot | Files, memory and running processes | Services should be up the moment a task starts | $0.08/GB/month on bytes it alone stores |
| A custom image | Files, rebuilt from a recipe or Dockerfile | The setup should be reviewed and versioned | $0.08/GB/month on its accounted storage |
| Neither | Nothing | One-off tasks on small repositories | Only the running time |
setup.sh is the bridge between the two. Once reviewed, its commands become the
RUN lines of a Dockerfile. runtime.images.build turns that Dockerfile into a
numbered image version (custom images).
What repository onboarding needs
| Need | How Runtime covers it |
|---|---|
| Freedom to install anything | sudo works inside the guest; root cannot change network, CPU, memory or cost |
| Room for dependencies | diskMiB up to what the project needs; the default 4 GiB has about 2.5 free |
| Databases the tests expect | sudo enable-docker, then Docker Compose in the same sandbox |
| Long installs | timeoutMs up to 24 hours per command |
| Keeping what worked | Snapshots kept while you have credit; setup.sh from the commands that exited 0 |
| Several repositories at the same time | 100 sandboxes at the same time on a paid account |
| Your code kept from other tenants | A Firecracker microVM with its own kernel per sandbox |
What it costs
Take 40 repositories a month. Each onboarding keeps a 2 vCPU, 4 GiB sandbox running for 20 minutes. Installs and tests use 1.2 vCPUs on average. Each snapshot keeps 2 GB of its own for the month:
- CPU
- 40 × 20 min / 60 × 1.2 vCPU × $0.025
- $0.40
- Memory
- 40 × 20 min / 60 × 4 GiB × $0.0075
- $0.40
- Snapshots
- 40 × 2 GB × $0.08
- $6.40
- Total
- $7.20
A snapshot is charged on the bytes it alone stores. A block that two of your snapshots share counts once (pricing). A new account gets 100 free hours of a 1 GB machine in its first 30 days, with no card. Without credit, a sandbox can reach only ports 80 and 443, so clone over HTTPS in that case.
Start
Terminalnpx withruntime sandboxes run --keep -- git --versionThe first run prints a link to approve in your browser. runtime sandboxes ssh <id> then opens a shell in the sandbox that the agent set up
(SSH and editors).
Related: a coding agent sandbox, legacy code migration, sandbox snapshots, per-user dev environments, agent evals and SWE-bench.
A stored image is charged for its whole file (image storage pricing).
Facts on this page were checked on 26 October 2026.