How to add a code interpreter to a chatbot
Give each conversation its own sandbox with a stateful interpreter, run the model's code there, and show the text, tables and charts.
On Runtime a conversation's interpreter costs $0.03125 an hour while it waits for the next message. Runtime bills the CPU the code uses, not the CPUs the sandbox holds, so a chat that runs a cell every few minutes pays for the seconds of work and the memory. A new sandbox ran its first Python command 351 ms after the request at the median on 24 September 2026 (speed), so it can start when the conversation does.
The short answer
One sandbox per conversation, found again by name on every turn. The interpreter keeps variables between cells, and the sandbox pauses itself when the chat goes quiet.
TypeScriptimport { Sandbox } from "withruntime";export async function runCell(conversationId: string, code: string) { const sbx = await Sandbox.getOrCreate(`chat-${conversationId}`, { vcpu: 1, memoryMiB: 2048, idlePauseSeconds: 600, // pause after ten quiet minutes network: { internet: false }, }); const cell = await sbx.interpreter.run(code, { timeoutMs: 60_000 }); return { status: cell.status, // "ok", "error", "timeout" ... stdout: cell.stdout, error: cell.error?.value, results: cell.results.map((result) => result.data), // text/plain, image/png, tables };}Pythonfrom withruntime import Sandboxdef run_cell(conversation_id: str, code: str) -> dict: sbx = Sandbox.get_or_create( f"chat-{conversation_id}", vcpu=1, memory_mib=2048, idle_pause_seconds=600, # pause after ten quiet minutes network={"internet": False}, ) cell = sbx.interpreter.run(code, timeout_ms=60_000) return { "status": cell["status"], "stdout": cell["stdout"], "error": (cell["error"] or {}).get("value"), "results": [result["data"] for result in cell["results"]], }Expose runCell to the model as a tool, for example run_python(code), and
pass what it returns back as the tool result. The first call in a conversation
creates the sandbox; every later call gets the same one, woken if it paused.
What comes back
Each cell returns its status, its printed output, an error with its traceback if it raised, and a list of results. Each result is a bundle keyed by MIME type:
| You run | You get back |
|---|---|
A pandas data frame such as df |
A table (application/vnd.runtime.table+json) |
| A matplotlib chart | A PNG image (base64 in image/png) |
print(...) |
stdout |
| An exception | status: "error" and error with name, value, traceback |
| A cell that runs too long | status: "timeout" |
display of a file (.csv, .html) |
The file as a result |
Render the PNG in your chat UI as an image, the table as a table, and send the text to the model so it can read its own output.
Let users upload files
A user's CSV goes into the sandbox as a file. The next cell reads it:
TypeScriptimport { Sandbox } from "withruntime";const sbx = await Sandbox.getOrCreate("chat-42", { idlePauseSeconds: 600 });await sbx.files.write("/workspace/sales.csv", "month,revenue\nJan,120\nFeb,135\n");const cell = await sbx.interpreter.run( "import pandas as pd\ndf = pd.read_csv('sales.csv')\ndf.describe()",);console.log(cell.results[0]?.data["text/plain"]);Pythonfrom withruntime import Sandboxsbx = Sandbox.get_or_create("chat-42", idle_pause_seconds=600)sbx.files.write("/workspace/sales.csv", "month,revenue\nJan,120\nFeb,135\n")cell = sbx.interpreter.run("import pandas as pd\ndf = pd.read_csv('sales.csv')\ndf.describe()")print(cell["results"][0]["data"]["text/plain"])/workspace is the working directory, so relative paths land there. pandas,
NumPy and matplotlib are in the default image (the sandbox
environment). Large files go up in checked chunks;
there is no size limit beyond the disk.
What a chatbot interpreter needs
| Need | How Runtime covers it |
|---|---|
| State between turns | Variables persist between cells; a paused sandbox keeps its memory and processes |
| Several languages | Python, JavaScript, TypeScript, R, Java, Bash and Go |
| Charts and tables | matplotlib and R plots as PNG; pandas and R data frames as tables |
| One user cannot reach another | A Firecracker microVM with its own kernel for every conversation |
| Code that sends data out | network: { internet: false }, enforced on the host, root included |
| An infinite loop | timeoutMs ends the cell and reports timeout |
| Quiet conversations | idlePauseSeconds pauses; the next call wakes it, usually in about half a second |
| A runaway bill | maxCostMicros per create and a daily spending limit per key |
| Many chats at once | 100 sandboxes at once on a paid account to start, eight on the free trial |
R, Java and Go install the first time a sandbox uses them, about 35 to 90
seconds once; bake them into a custom image with apt so
every conversation starts with them.
When the model needs a package
Leave the internet off by default. To let a cell install from PyPI, allow only the registry, then close it again:
TypeScriptimport { Sandbox } from "withruntime";const sbx = await Sandbox.getOrCreate("chat-42", { network: { internet: false } });await sbx.network.set({ internet: true, allow: ["pypi.org", "*.pythonhosted.org"] });await sbx.exec("pip install --quiet tabulate", { check: true, timeoutMs: 120_000 });await sbx.network.set({ internet: false });A rule applies at once, to connections already open too (turn off sandbox internet).
What it costs
A conversation's sandbox of 1 vCPU and 2 GiB is billed while it runs: measured CPU at $0.025 per vCPU-hour, with a floor of a twentieth of a vCPU, and memory at $0.0075 per GiB-hour (pricing). Take 10,000 conversations a month, each keeping its sandbox running for 5 minutes and using 20 CPU-seconds of work:
TextCPU: 10,000 × 20 s / 3,600 × $0.025 = $1.39Memory: 10,000 × 300 s / 3,600 × 2 GiB × $0.0075 = $12.50Total: $13.89That is about $0.0014 a conversation. 20 CPU-seconds over 300 seconds is above the floor, so the floor adds nothing. Stop the sandbox when the conversation closes; a sandbox you keep paused instead is billed as paused storage at $0.08 per GB per 30-day month. New accounts get 50 free sandbox hours, no card.
Start
Terminalnpx withruntime sandbox run --trial -- python3 -c 'print(6 * 7)'The first run prints a link to approve in your browser; there is no API key to copy. Then use the JavaScript or Python SDK as above.
Related: what a code interpreter is, run untrusted LLM code safely, a data analysis agent, pause and resume a sandbox.
Facts on this page were checked on 25 September 2026.