Internals
Preview sandbox
How the agent's app runs on a virtual localhost, what the sandbox allows, how the console reaches the agent, and how other devices run the same preview.
When the agent calls serve, the project’s files are served on a virtual port, :5173 by default. Nothing binds a real port. The preview pane shows a tab per port (sandbox:5173/index.html) and runs the page in a sandboxed frame. A port’s state reads rev 3, loading…, stopped or hung.
The controls (Reload, Open, Share, Console) are described in Code mode. This page covers how the preview works.
Live reload
Section titled “Live reload”When the agent (or you) writes a file under a served folder, the port takes a new snapshot 250 ms after the last write and the preview reloads. Each snapshot is a new rev. After a write, the tool result tells the agent which rev is now served, so it checks the logs instead of serving again.
A served folder may hold up to 400 files, 2 MB per file and 8 MB in all. Dot folders, node_modules, dist, build and similar folders are skipped. Over a limit, serve fails with a message the agent can act on.
The console
Section titled “The console”The preview’s console output and uncaught errors are collected for each port. They show in the console panel under the preview, and the agent reads them:
servewaits up to 2 seconds for the page to load, then returns how long it took and the first errors, errors before warnings. Repeats of the same line fold into one, with a count.preview_logsreturns what was printed since a cursor. If the page’s first error has scrolled out of view, it adds afirst error:line, since the first error is usually the cause.
A file the page asks for that is not in the project shows as an error, like 404 sprites.png (referenced but not in the project).
When the console has errors, Send to agent puts “Fix the errors in the preview console” in the prompt box.
What an app can do in the sandbox
Section titled “What an app can do in the sandbox”The app is the model’s code, so it runs as untrusted code:
- The frame has
sandbox="allow-scripts"and nothing else. It gets an opaque origin: no access to the room’s page, storage, cookies or cached weights. No popups, no top-level navigation, no forms, no camera or microphone. - A content security policy blocks the network, except scripts from
cdn.jsdelivr.netandcdnjs.cloudflare.com, and stylesheets and fonts from Google Fonts. The agent can load a library from those CDNs. - Messages from the frame are shown as plain text.
Inside that box, the preview does its best to behave like a normal local server:
- Every file the page uses (scripts, ES module imports, stylesheets, images, fonts, media) is inlined into one document, so relative paths work.
fetch()andXMLHttpRequestof a project file by relative path return that file.localStorageandsessionStoragework, but in memory only: they are empty again after each reload.alert()is logged instead of shown.confirm()answers false andprompt()answers null, and both are logged.- A relative link to another page of the project loads that page.
- An app laid out wider or taller than the pane (a 300×600 game board and a side panel, on a phone) is scaled down to fit. Clicks, touches and keys still reach it. Apps that already fit are untouched.
Previews on other devices
Section titled “Previews on other devices”Everyone in the room who can see Code runs the same preview on their own screen, in the same sandbox. The host sends each port’s file list and each device fetches only the files it doesn’t hold yet, in 64 KB chunks. Files are addressed by a hash of their content (a SHA-256 prefix), so after a reload only the changed files travel.
A device trusts nothing it receives: it checks the file limits, the paths and every hash again before it runs anything. Previews on other devices start by themselves.
A separate site for previews
Section titled “A separate site for previews”Where the preview runs decides how much a bad app can hurt the room.
| Same tab (pooled.run today) | Separate site | |
|---|---|---|
| Where | A frame in the room’s tab, as a blob: page |
A relay page on another registrable domain, in its own process |
| An app that loops forever | Can freeze the room’s tab | Only its own process hangs. After 3 s with no heartbeat, the frame is removed and marked hung; the room keeps working |
run_js |
Off | On |
| Sandbox tip | “Runs in a sandboxed frame in this tab.” | “Runs in a sandboxed frame on a separate site, in a process of its own.” |
The room page points at the preview site with a meta tag. For example:
<meta name="preview-origin" content="pooled.run=https://pooled-preview.vercel.app pooled-dev.vercel.app=https://pooled-preview.vercel.app">The value is one origin, or host=origin entries so one page can send production and staging to different preview sites. The page checks it before using it. A value on the page’s own site (the same host, a subdomain or a sibling), plain http (allowed only for localhost or 127.0.0.1, from an http page), or anything that is not an http(s) URL is ignored with one console warning, and the preview stays in the tab. A subdomain would share the room’s process, so it could not isolate anything.
If the preview site doesn’t answer, the preview falls back to running in the tab and logs “the isolated preview host did not answer; running the preview in this tab”.
run_js
Section titled “run_js”run_js lets the agent check logic, not only “no console errors”. It runs a JS module in a fresh hidden sandbox, next to a page of the project if one is given, and returns what it printed. For example, “does clearLines() drop a full row?”. It can import project files, use top-level await, and runs for up to 3 seconds.
It always runs on the separate preview site, never in the room’s tab, so while (true) {} cannot freeze the room. Without a preview site, the agent is not offered the tool at all.