How to give an AI research agent a sandbox to fetch pages and run code
Give the agent one sandbox per research question, with fetch and search tools inside it, a shell for analysis and files for its notes.
On Runtime a research agent's machine costs $0.03125 an hour while the model reads and writes, for 2 vCPUs and 4 GiB, and a new one ran its first Python command 351 ms after the request at the median on 24 September 2026 (speed). Web tools start inside the sandbox with one call, so whatever a fetched page tries to make the agent do happens on a disposable machine, not on your server.
What a research agent does that a chatbot does not
A research agent answers a question by working: it searches, opens sources, downloads a dataset, computes something from it, checks a claim, and writes up what it found with citations. That means three things run on its behalf:
- Fetching arbitrary URLs, chosen by a model, from pages written by anyone.
- Running code to parse, count and chart what it downloaded.
- Keeping state across dozens of steps: downloaded files, notes, a draft.
Each is a risk on your own infrastructure. A fetch from your API server can reach internal addresses. Code the agent writes after reading a hostile page can do what that page told it. A sandbox holds all three in a Firecracker microVM with its own kernel, where private and internal addresses are refused by the host and the machine can be thrown away at the end.
The core loop
Create the sandbox, start the fetch server in it, and hand the agent two kinds of tool: the MCP server's URL for reading the web, and shell and file tools for everything else.
TypeScriptimport { Sandbox } from "withruntime";import { sandboxTools } from "withruntime/tools";await using sbx = await Sandbox.create({ timeoutSeconds: 3600, idlePauseSeconds: 600, labels: { agent: "research", question: "q-311" },});// An MCP server for the web, running inside the sandbox.await sbx.mcp.start([{ id: "fetch" }]);const gateway = await sbx.mcp.ready();const fetchServer = gateway.servers[0]; // give its url and gateway.headers to your MCP client// Shell and files for analysis and notes.const tools = sandboxTools(sbx); // runtime_exec, runtime_read_file, runtime_write_file, runtime_list_filesconsole.log( fetchServer?.url, tools.map((t) => t.name),);// ... the model's tool-calling loop runs here ...const report = await sbx.files.readText("/workspace/report.md");console.log(report);Pythonfrom withruntime import Sandboxfrom withruntime.tools import sandbox_toolswith Sandbox.create( timeout_seconds=3600, idle_pause_seconds=600, labels={"agent": "research", "question": "q-311"},) as sbx: sbx.mcp.start([{"id": "fetch"}]) gateway = sbx.mcp.ready() fetch_server = gateway["servers"][0] # its url and gateway["headers"] go to your MCP client run, read, write, ls = sandbox_tools(sbx) print(fetch_server["url"]) # ... the model's tool-calling loop runs here ... print(sbx.files.read_text("/workspace/report.md"))Tell the model in its system prompt to save every source it uses under
/workspace/sources/ and to write its answer to /workspace/report.md. Your
code then reads the report and can check that every cited file exists before
it shows anything to a user.
Which web tools can run inside
runtime.mcp.catalog() lists the servers Runtime can start in a sandbox, each
with its licence, pinned version and the hosts it calls. For research the
relevant ones are fetch, Brave Search, Exa, Firecrawl, a Playwright browser,
Context7 and memory (MCP servers in a sandbox).
Server (id) |
What the agent gets |
|---|---|
fetch |
A web page by URL, returned as Markdown |
brave-search |
Web, news, image and local search via Brave's API |
exa |
Exa's search and page contents |
firecrawl |
Scraping, crawling and structured extraction |
playwright |
A headless Chromium the agent drives |
memory |
A knowledge graph kept in a file in the sandbox |
A search provider's API key is stored once as a Runtime secret and named when
the server starts, as in { id: "brave-search", secrets: { BRAVE_API_KEY: "BRAVE_API_KEY" } }.
The server in the sandbox sees only a placeholder, and the host's proxy adds the real value
to requests bound for that provider's own hosts, so a page that talks the model
into printing its environment gets nothing it can use
(secrets sandboxes never see).
Research that takes hours or days
Long questions do not fit one lease. Two settings make that cheap:
idlePauseSecondspauses the sandbox after that many seconds with no request, keeping files, memory and processes. The next tool call wakes it, usually in about half a second, and a paused sandbox pays no compute.- A name.
Sandbox.getOrCreate("q-311")returns the same sandbox from any process tomorrow, woken if it was paused, so a queue worker can pick the question up where another left it.
TypeScriptimport { Sandbox } from "withruntime";const sbx = await Sandbox.getOrCreate("research-q-311", { idlePauseSeconds: 900 });const notes = await sbx.exec("ls sources | wc -l", { cwd: "/workspace" });console.log(sbx.info.reused, notes.stdout.trim(), "sources so far");A paused sandbox is billed as storage at $0.08 per decimal GB per 30-day month (pricing).
Narrowing what it can reach
By default a paid sandbox reaches any public host. For a research agent that
should read only certain sites, such as a company's own docs, a standards body
or a statistics office, pass an allow list at create, or change it mid-task
with sbx.network.set({ internet: true, allow: [...] }). A rule applies at
once, to open connections too, and root in the guest cannot lift it
(the sandbox environment).
What the agent's sandbox covers
| Need | How Runtime covers it |
|---|---|
| Fetching URLs a model picked | Private and internal addresses refused on the host, for every sandbox |
| Analysis of what it downloaded | Python 3.12 with pandas, NumPy and matplotlib; Node.js 24; sudo apt |
| Web tools without your servers | Catalog MCP servers started inside the sandbox, behind a bearer token |
| Search keys that cannot leak | Secrets injected by the host's proxy, never present in the guest |
| Pauses for human review | Pause keeps memory and processes, 1 to 365 days |
| Many questions at once | 100 sandboxes at once on a paid account to start |
| Knowing what it did | Lifecycle events and per-minute CPU and memory readings |
What it costs
Take 500 research tasks a month. Each keeps a 2 vCPU, 4 GiB sandbox running for 20 minutes, and its fetches, parsing and charts use 90 CPU-seconds in all:
TextCPU: 500 × 90 s / 3,600 × $0.025 = $0.31Memory: 500 × 1,200 s / 3,600 × 4 GiB × $0.0075 = $5.00Total: $5.31About a cent each, at Runtime's rates of $0.025 per vCPU-hour of measured CPU and $0.0075 per GiB-hour (pricing). Model tokens are paid to your model provider, not to Runtime. A new account gets 50 free sandbox hours, about 150 such tasks, with no card.
Start
Terminalclaude mcp add --scope user runtime -- npx -y withruntime mcpThat gives Claude Code, or any MCP client, Runtime's own tools; approve the link it shows once in your browser. For your own agent, install the JavaScript or Python SDK and start from the loop above.
Related: browser automation agent, what MCP is, egress control, pause and resume a sandbox, AI data pipelines.
Facts on this page were checked on 25 September 2026.