Runtime

Run Jupyter notebooks in a sandbox for an AI agent

Upload the .ipynb, run it with papermill or nbconvert --execute inside a microVM, and download the executed notebook and its outputs.

On Runtime, a notebook runs in its own Firecracker microVM with pandas, NumPy and matplotlib already installed, so a notebook an agent wrote, or one a user uploaded, executes away from your servers and your keys. The sandbox bills the CPU the kernel uses: $0.03125 an hour for 2 vCPU and 4 GiB while cells wait, $0.08 with both CPUs busy (pricing). On 25 September 2026 PyPI's current releases were JupyterLab 4.6.4, Notebook 7.6.3, nbconvert 7.17.1, papermill 2.7.0 and ipykernel 7.3.0.

Three ways to run notebook code

You have Use Needs an install
Cells an agent writes one at a time The sandbox's code interpreter No
A whole .ipynb to run, maybe with inputs papermill or jupyter nbconvert --execute pip install one package
A person who wants to open the notebook JupyterLab behind a private preview pip install jupyterlab

The interpreter keeps variables between calls and returns charts as PNG and data frames as tables, in Python, JavaScript, TypeScript, R, Java, Bash or Go. The rest of this page is the other two rows.

Execute a notebook with parameters

papermill runs every cell and writes a new notebook with the outputs. Values passed with -p go into a cell it inserts after the one tagged parameters.

TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ timeoutSeconds: 1800 });await sbx.exec("pip install --quiet papermill==2.7.0 nbconvert==7.17.1 ipykernel==7.3.0", {  check: true,  timeoutMs: 300_000,});await sbx.files.upload("./report.ipynb", "/workspace/report.ipynb");await sbx.files.upload("./sales.csv", "/workspace/sales.csv");const run = await sbx.exec(  "mkdir -p out && papermill report.ipynb out/report.ipynb -p region west -p year 2026",  { timeoutMs: 1_200_000, onStderr: (text) => process.stderr.write(text) },);await sbx.exec("jupyter nbconvert --to html out/report.ipynb", { check: true, timeoutMs: 120_000 });await sbx.files.download("/workspace/out", "./out");process.exitCode = run.exitCode ?? 1;
Pythonimport sysfrom withruntime import Sandboxwith Sandbox.create(timeout_seconds=1800) as sbx:    sbx.exec("pip install --quiet papermill==2.7.0 nbconvert==7.17.1 ipykernel==7.3.0",             check=True, timeout_ms=300_000)    sbx.files.upload("report.ipynb", "/workspace/report.ipynb")    sbx.files.upload("sales.csv", "/workspace/sales.csv")    run = sbx.exec("mkdir -p out && papermill report.ipynb out/report.ipynb -p region west -p year 2026",                   timeout_ms=1_200_000, on_stderr=sys.stderr.write)    sbx.exec("jupyter nbconvert --to html out/report.ipynb", check=True, timeout_ms=120_000)    sbx.files.download("/workspace/out", "out")    print("exit", run.exit_code)

The exit code says whether every cell ran. The out folder comes back in one call: the executed .ipynb, its HTML rendering, and any file a cell saved there. Progress streams to your terminal while it runs.

For a notebook with no parameters, nbconvert alone executes it:

Terminaljupyter nbconvert --to notebook --execute report.ipynb --output report.done.ipynb \  --ExecutePreprocessor.timeout=600

nbconvert's documentation sets its per-cell timeout at 30 seconds by default; -1 removes it. Set the sandbox's own timeoutMs above the whole run either way, since a command's default is 60 seconds.

Open JupyterLab in your browser

Start the server with spawn so it keeps running, and share its port as a preview. A preview is private: a browser needs its one-time link, and a script needs the token header. That link is the lock here, so Jupyter's own token is turned off.

TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ timeoutSeconds: 3600, idlePauseSeconds: 900 });await sbx.exec("pip install --quiet jupyterlab==4.6.4", { check: true, timeoutMs: 300_000 });await sbx.spawn(  "jupyter lab --no-browser --ip=127.0.0.1 --port=8888 " +    "--IdentityProvider.token='' --ServerApp.allow_remote_access=True",);const preview = await sbx.previews.create(8888);console.log("open:", preview.urlWithToken);
Pythonfrom withruntime import Sandboxsbx = Sandbox.create(timeout_seconds=3600, idle_pause_seconds=900)sbx.exec("pip install --quiet jupyterlab==4.6.4", check=True, timeout_ms=300_000)sbx.spawn("jupyter lab --no-browser --ip=127.0.0.1 --port=8888 "          "--IdentityProvider.token='' --ServerApp.allow_remote_access=True")preview = sbx.previews.create(8888)print("open:", preview["urlWithToken"])
  • allow_remote_access lets Jupyter answer requests whose Host header is the preview's address rather than localhost, per Jupyter Server's options.
  • Kernels talk over WebSockets, which previews carry.
  • With idlePauseSeconds, the sandbox pauses after 15 quiet minutes. A pause keeps memory and processes, so the kernel and its variables are there when you come back, and the next visit wakes it with a short "Waking up" page (pause and resume).

To use the notebook from VS Code instead, connect over SSH to <id>.runtime and run Jupyter there (SSH and editors).

Start every sandbox with Jupyter ready

A custom image installs the packages once:

TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.images.build({  name: "notebooks",  recipe: { pip: ["jupyterlab==4.6.4", "papermill==2.7.0", "scikit-learn"] },});await using sbx = await runtime.sandboxes.create({ image: "notebooks" });
Pythonfrom withruntime import Runtimeruntime = Runtime()runtime.images.build(name="notebooks",                     recipe={"pip": ["jupyterlab==4.6.4", "papermill==2.7.0", "scikit-learn"]})sbx = runtime.sandboxes.create(image="notebooks")

Building is free. The free trial stores three images free; after that an image is charged on its size.

Keep an untrusted notebook contained

  • No internet: create the sandbox with network: { internet: false } after packages are installed, or baked into an image, and a notebook cannot send data anywhere (turn off sandbox internet).
  • No keys inside: pass what a notebook needs as env on the command, or store API keys as secrets the sandbox never sees (security).
  • Memory: a sandbox gets the memory you ask for with memoryMiB; the free trial allows up to 4 GiB.
  • Disk: the default 4 GiB disk had about 2.5 GiB free on 24 September 2026. Large datasets want diskMiB, or a volume that outlives the sandbox.

For an agent that answers questions about uploaded data, see a data analysis agent; for charts and tables in a chat product, see a code interpreter for chatbots.

Sources

Facts on this page were checked on 25 September 2026.