On Runtime, the browser runs on a machine of its own. You pay for the CPU it uses. A 2 vCPU, 4 GiB sandbox costs $0.03125 per hour while the agent waits on its model. It costs $0.08 per hour with both CPUs busy. The desktop takes clicks, typing and screenshots through the SDK. It streams live to a private link and records to MP4. You have nothing to install on your side.
The short answer
Start a desktop and open the page. Then loop: send a screenshot to the model, and send the model's action back to the desktop.
TypeScriptimport { Sandbox } from "withruntime";type Action = | { kind: "click"; x: number; y: number } | { kind: "type"; text: string } | { kind: "open"; url: string } | { kind: "done" };type Model = (screenshot: Uint8Array) => Promise<Action>; // your computer-use modelexport async function browse(model: Model, startUrl: string, maxSteps = 30) { await using sbx = await Sandbox.create({ diskMiB: 8192, timeoutSeconds: 1800 }); const { streamUrl } = await sbx.desktop.start({ width: 1280, height: 800 }); console.log("watch it live:", streamUrl); // private: it carries its token await sbx.desktop.open(startUrl); for (let step = 0; step < maxSteps; step++) { const action = await model(await sbx.desktop.screenshot()); if (action.kind === "done") break; if (action.kind === "click") await sbx.desktop.click(action.x, action.y); if (action.kind === "type") await sbx.desktop.type(action.text); if (action.kind === "open") await sbx.desktop.open(action.url); } return sbx.desktop.screenshot(); // the final state}Pythonfrom withruntime import Sandboxdef browse(model, start_url: str, max_steps: int = 30) -> bytes: """model(png_bytes) returns a dict: {"kind": "click", "x": .., "y": ..}, {"kind": "type", "text": ..}, {"kind": "open", "url": ..} or {"kind": "done"}.""" with Sandbox.create(disk_mib=8192, timeout_seconds=1800) as sbx: desktop = sbx.desktop.start(width=1280, height=800) print("watch it live:", desktop["streamUrl"]) sbx.desktop.open(start_url) for _ in range(max_steps): action = model(sbx.desktop.screenshot()) if action["kind"] == "done": break if action["kind"] == "click": sbx.desktop.click(action["x"], action["y"]) elif action["kind"] == "type": sbx.desktop.type(action["text"]) elif action["kind"] == "open": sbx.desktop.open(action["url"]) return sbx.desktop.screenshot()The desktop and Chromium are ready on every server. The first start in a sandbox installs nothing to its disk and takes about a second.
Three ways to drive a browser
| Way | What the agent sees and does | Best for |
|---|---|---|
| The desktop | Screenshots in; clicks, typing and URLs out | Computer-use models; any site or app |
| Playwright MCP server | Page structure and browser tools over MCP | Agents that already speak MCP |
| Playwright or Puppeteer code | Your script, run with exec |
Known flows, scraping, end-to-end tests |
Start the Playwright MCP server inside the sandbox with one call. Give your agent its URL and headers:
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await using sbx = await runtime.sandboxes.create({ diskMiB: 8192 });await sbx.mcp.start([{ id: "playwright" }]);const gateway = await sbx.mcp.ready();for (const server of gateway.servers) console.log(server.name, server.url);console.log(gateway.headers); // send these with every requestruntime.mcp.catalog() lists every server with its license and pinned
version. For scripted Playwright, see Playwright in a cloud
sandbox.
Watch it and keep a record
The live view is a private preview of the desktop. A person can watch the agent and step in. A recording saves the session as MP4:
TypeScriptimport { writeFile } from "node:fs/promises";import { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ diskMiB: 8192 });await sbx.desktop.start();const rec = await sbx.desktop.recordings.start({ fps: 10, maxMiB: 256 });await sbx.desktop.open("https://example.com");await sbx.desktop.recordings.stop(rec.id);await writeFile("session.mp4", await sbx.desktop.recordings.download(rec.id));Pythonfrom withruntime import Sandboxwith Sandbox.create(disk_mib=8192) as sbx: sbx.desktop.start() recording = sbx.desktop.recordings.start(fps=10, max_mib=256) sbx.desktop.open("https://example.com") sbx.desktop.recordings.stop(recording["id"]) with open("session.mp4", "wb") as file: file.write(sbx.desktop.recordings.download(recording["id"]))A recording uses one encoder thread at low priority and at most 4 Mbit/s. The
browser takes priority. A recording never grows past maxMiB.
Keep the agent on the sites it should visit
After the desktop and browser are running, narrow the network to the sites the task needs. The rule applies immediately, binds root inside the sandbox, and is enforced on the host:
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ diskMiB: 8192 });await sbx.desktop.start();await sbx.desktop.open("https://example.com");await sbx.network.set({ internet: true, allow: ["example.com", "*.example.com"] });To give the agent an API it should call, store the key as a secret. The sandbox holds only a placeholder. The proxy adds the value to HTTPS requests to the hosts you name (egress control).
Start ready
Install the desktop once. Keep that sandbox as a snapshot, and start every task from it. The copy starts with the desktop installed and running:
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();const base = await runtime.sandboxes.create({ diskMiB: 8192 });await base.desktop.start({ width: 1280, height: 800 });await base.desktop.open("https://example.com");const snapshot = await base.snapshot({ name: "desktop-ready", retentionDays: 30 });await base.stop();await using task = await runtime.sandboxes.create({ snapshot: snapshot.id });await task.desktop.open("https://example.com");What a browser agent needs
| Need | How Runtime covers it |
|---|---|
| A real browser and screen | A Linux desktop with Chromium; clicks, typing, screenshots; CDP for Playwright |
| A person watching | A private live stream link |
| An audit trail | MP4 recordings, capped by maxMiB |
| One task per browser profile | A Firecracker microVM per sandbox: no shared cookies, downloads or sessions |
| Sites the agent may visit | Allow and deny lists, enforced on the host |
| Many sessions at the same time | 100 sandboxes at the same time on a paid account |
| No setup per task | Start from a snapshot with the desktop already running |
What it costs
Assume 500 browser tasks a month. Each keeps a 2 vCPU, 4 GiB sandbox running for 3 minutes. The browser uses 0.8 of a vCPU on average:
- CPU
- 500 × 3 min / 60 × 0.8 vCPU × $0.025
- $0.50
- Memory
- 500 × 3 min / 60 × 4 GiB × $0.0075
- $0.75
- Total
- $1.25
Runtime charges $0.025 per vCPU-hour of measured CPU and $0.0075 per GiB-hour of memory (pricing). A 1 GB snapshot to start from adds $0.08 a month. A new account gets 100 free hours of a 1 GB machine in its first 30 days, with no card. Without credit, a sandbox reaches ports 80 and 443. Browsing needs those ports.
Start
Terminalnpx withruntime sandboxes run --keep -- echo readyThe first run prints a link. Open it in your browser to approve the run. Then
runtime sandboxes desktop <id> start prints a link to watch the desktop
(CLI).
Related: Playwright in a cloud sandbox, coding agent sandbox, egress control, sandbox snapshots.
Facts on this page were checked on 26 October 2026.