Skip to content
Runtime

How to build a data analysis agent that runs Python safely

Upload the user's data to a sandbox, run the model's pandas code in a stateful interpreter there, and return tables and charts.

On Runtime pandas, NumPy and matplotlib are already installed, and the analysis pays for the CPU it uses. A 2 vCPU, 4 GiB sandbox costs $0.03125 an hour while the model thinks, and $0.08 an hour with both CPUs busy. Every analysis can have a machine of its own.

The short answer

The agent loop has three parts: upload the data, run the model's cells, and return the results.

TypeScriptimport { writeFile } from "node:fs/promises";import { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ network: { internet: false }, timeoutSeconds: 1800 });await sbx.files.write("/workspace/orders.csv", "region,amount\nwest,120\neast,90\nwest,40\n");// Each cell is code the model wrote; variables carry over between cells.await sbx.interpreter.run("import pandas as pd\ndf = pd.read_csv('orders.csv')");const table = await sbx.interpreter.run("df.groupby('region')['amount'].sum()");console.log(table.results[0]?.data["text/plain"]);const chart = await sbx.interpreter.run(  "import matplotlib.pyplot as plt\ndf.groupby('region')['amount'].sum().plot.bar()\nplt.show()",);const png = chart.results.find((r) => r.data["image/png"])?.data["image/png"];if (typeof png === "string") await writeFile("chart.png", Buffer.from(png, "base64"));
Pythonimport base64from withruntime import Sandboxwith Sandbox.create(network={"internet": False}, timeout_seconds=1800) as sbx:    sbx.files.write("/workspace/orders.csv", "region,amount\nwest,120\neast,90\nwest,40\n")    # Each cell is code the model wrote; variables carry over between cells.    sbx.interpreter.run("import pandas as pd\ndf = pd.read_csv('orders.csv')")    table = sbx.interpreter.run("df.groupby('region')['amount'].sum()")    print(table["results"][0]["data"]["text/plain"])    chart = sbx.interpreter.run(        "import matplotlib.pyplot as plt\ndf.groupby('region')['amount'].sum().plot.bar()\nplt.show()"    )    for result in chart["results"]:        if "image/png" in result["data"]:            with open("chart.png", "wb") as file:                file.write(base64.b64decode(result["data"]["image/png"]))

Give the model one tool, run_python(code), that calls interpreter.run and returns the cell's stdout, its error and its text results. Keep the images for the user; send the model a line saying a chart was made.

Why the interpreter and not python3 script.py

A script starts with no saved state each time, so the agent reloads a large file on every step. The interpreter keeps the data frame in memory between cells, as a notebook does:

Step the agent takes Script per step Interpreter session
Load a 500 MB CSV Every step Once
Inspect columns, then clean them Two programs, two loads Two cells on the same df
Make a chart Save to a file, then read it Returned as a PNG result
Show a data frame Print text Returned as a table
An exception Exit code and stderr Error name, value and traceback

The interpreter also runs R, JavaScript, TypeScript, Java, Bash and Go. R plots come back as PNG, and R data frames come back as tables. R installs the first time a sandbox uses it, which takes about 90 seconds once per sandbox. To skip that wait, build R into an image.

Bring your own libraries

Build an image once with the packages your analyses need, then create every sandbox from it:

TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.images.build({  name: "analysis",  recipe: { pip: ["polars", "duckdb", "scikit-learn", "seaborn"] },});await using sbx = await runtime.sandboxes.create({  image: "analysis",  network: { internet: false },});const cell = await sbx.interpreter.run("import duckdb\nduckdb.sql('select 42').fetchall()");console.log(cell.results[0]?.data["text/plain"]);
Pythonfrom withruntime import Runtimeruntime = Runtime()runtime.images.build(name="analysis", recipe={"pip": ["polars", "duckdb", "scikit-learn", "seaborn"]})with runtime.sandboxes.create(image="analysis", network={"internet": False}) as sbx:    cell = sbx.interpreter.run("import duckdb\nduckdb.sql('select 42').fetchall()")    print(cell["results"][0]["data"]["text/plain"])

Building an image is free; a stored one is charged at $0.08 per GB per 30-day month, and an account without credit stores its first three images free (custom images).

Large and private data

  • Big files: files.write and files.upload send large files in parallel 1 MiB chunks, each checked by SHA-256, and resume after a dropped connection. Under /workspace there is no size limit beyond the disk, so ask for more with diskMiB.
  • Data in a bucket: mount your own S3, R2 or Google Cloud Storage bucket as a directory; the sandbox never holds the bucket's key (mount your own bucket).
  • A database the agent should query: store its API key as a secret. The sandbox sees a placeholder, and the host's proxy adds the value only on HTTPS requests to the hosts you name.
  • Nothing leaves: with network: { internet: false } the model's code cannot send the data anywhere, and root in the guest cannot turn it back on.

What a data analysis agent needs

Need How Runtime covers it
Python data tools NumPy, pandas and matplotlib in the default image; Python 3.12
State across steps Interpreter variables persist between cells
Charts and tables matplotlib as PNG; pandas data frames as tables; display for files
Isolation per user A Firecracker microVM with its own kernel for every sandbox
Keep the data in Internet off, or an allow list, enforced on the host
Code that runs too long timeoutMs per cell; timeoutSeconds bounds the sandbox
Coming back tomorrow Sandbox.getOrCreate(name) and idlePauseSeconds: pause keeps memory, so the data frame is still loaded

What it costs

Take 1,000 analyses a month, each keeping a 2 vCPU, 4 GiB sandbox running for 3 minutes, with the cells using 40 CPU-seconds:

CPU
1,000 × 40 s / 3,600 × $0.025
$0.28
Memory
1,000 × 180 s / 3,600 × 4 GiB × $0.0075
$1.50
Total
$1.78

Runtime charges $0.025 per vCPU-hour of measured CPU, with a floor of a twentieth of a vCPU, and $0.0075 per GiB-hour of memory (pricing). A new account gets 100 free hours of a 1 GB machine in its first 30 days without a card.

Start

Terminalnpx withruntime sandboxes run -- python3 -c 'import pandas; print(pandas.__version__)'

The first run prints a link to approve in your browser. From a shell, runtime sandboxes run-code <id> analysis.py --out-dir charts runs a file as a cell and saves its charts as PNG (CLI).

Related: code interpreter for chatbots, what a code interpreter is, egress control, run untrusted LLM code safely.

Facts on this page were checked on 26 October 2026.

Related.

100 free hoursin your first 30 days.

of a 1 GB machine

  • No credit card
  • Four sandboxes at once, 2 vCPU and 4 GiB each
  • Then prepaid credit from $10, no plan fee
Start free, no card
Start free, no card