Run AI-generated code safely
The model writes Python. A VM of its own runs it, keeps its variables, draws the charts.
Shelley handles the setup. About 10 minutes.
Prefer a terminal?
curl -fsSL https://exe-staging.dev/deploy/code-interpreter/prompt.txt | ssh exe.dev new --prompt=/dev/stdinA code interpreter lets a model answer with code: it writes Python, a sandbox runs it, and the model reads the output and goes on.
This recipe builds one on exe.dev. A small chat app on one VM reaches the model through the LLM gateway, with no API key, and never runs the model's code itself. Each conversation gets a sandbox VM of its own with a Jupyter kernel: variables carry over between cells, charts come back as images, and you can upload files and download what the model saves. The code is three short files: app.py (the app and the tool-calling loop), sandbox.py (a sandbox VM with a kernel, using exe.dev's Python SDK), and index.html.
What you get#
- The code interpreter at https://my-code-interpreter.exe.xyz/, private behind exe.dev login, as code-interpreter.service, restarted on failure and after a reboot
- The exe.dev API key in /etc/code-interpreter.env, mode 600, and on no other VM
curl -s localhost:8000/api/healthon the VM answers"exe":"ok"and"llm":"ok"- A test question answered by code run in a sandbox VM, and that test conversation and its sandbox deleted
What's running, and where
https://my-code-interpreter.exe.xyz/
the chat, for you and people you share the VM with
Deploy with Shelley#
Click Deploy and Shelley will set up Code interpreter on a new VM, then check that it works. No config-file wrangling required.
Have these ready; Shelley asks for them:
EXE_TOKEN: the exe.dev API key thatssh exe.dev ssh-key generate-api-keyprinted,exe1....
Prefer a terminal?
curl -fsSL \
https://exe-staging.dev/deploy/code-interpreter/prompt.txt \
| ssh exe.dev new --prompt=/dev/stdinWhat Shelley is told
EXE_TOKEN=<fill-this-in>
Set up Code interpreter on this VM by following the exe.dev recipe at https://exe-staging.dev/deploy/code-interpreter.md from top to bottom.
The values at the top of this message are mine:
- EXE_TOKEN: the exe.dev API key that `ssh exe.dev ssh-key generate-api-key` printed, `exe1....`
Ask me for any that still say <fill-this-in> before you need them.
The recipe's example VM name is my-code-interpreter. This VM's name is the output of `hostname`: use it wherever the recipe writes my-code-interpreter. Skip the step that creates the VM; you are on it.
You are done when all of these are true. Check each one yourself:
- The code interpreter at https://my-code-interpreter.exe.xyz/, private behind exe.dev login, as code-interpreter.service, restarted on failure and after a reboot
- The exe.dev API key in /etc/code-interpreter.env, mode 600, and on no other VM
- `curl -s localhost:8000/api/health` on the VM answers `"exe":"ok"` and `"llm":"ok"`
- A test question answered by code run in a sandbox VM, and that test conversation and its sandbox deleted
Run "Set up by hand" from "On the VM", skipping VM creation and SSH.
EXE_TOKEN is a secret: put it only in /etc/code-interpreter.env, and
never print it. If the health check shows 401 or `invalid token`, tell
the owner the key was refused and how to make one ("Before you start").
Then run "Check it from the VM". Finish by pointing the owner to "Ask
it something".
Finish with the URLs I should open.
Also at /deploy/code-interpreter/prompt.txt. The recipe it follows is /deploy/code-interpreter.md.
Before you start#
The app makes sandbox VMs with an exe.dev API key that can touch nothing else. On your computer:
ssh exe.dev ssh-key generate-api-key --label=code-interpreter --tag=code-sandbox \
--cmds=new,ls,rm,ssh,cp,pause,resume,comment --exp=1y
It prints a key that starts with exe1.. Every VM it creates is tagged
code-sandbox, and it can see and touch no VM without that tag, including
the app's own. To revoke it, ssh exe.dev ssh-key remove code-interpreter.
Set it up#
Deploy sets it up on a new VM and asks for the key. To do it yourself:
Set up by hand
On your computer, create the VM (any free name) and connect:
ssh exe.dev new --name=my-code-interpreter
ssh my-code-interpreter.exe.xyz
On the VM, download the app:
mkdir -p ~/code-interpreter && cd ~/code-interpreter
for f in app.py sandbox.py index.html ask.py; do
curl -fsSLO https://exe-staging.dev/deploy/code-interpreter/$f
done
Put the key in a file only root can read, filling in yours:
sudo install -m 600 /dev/null /etc/code-interpreter.env
sudo tee /etc/code-interpreter.env > /dev/null <<'EOF'
EXE_TOKEN=exe1....
EOF
Run it as a service on port 8000:
sudo tee /etc/systemd/system/code-interpreter.service > /dev/null <<'EOF'
[Unit]
Description=Code interpreter (a sandbox VM per conversation)
Wants=network-online.target
After=network-online.target
[Service]
User=exedev
WorkingDirectory=/home/exedev/code-interpreter
EnvironmentFile=/etc/code-interpreter.env
ExecStart=/usr/local/bin/uv run --script app.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now code-interpreter
The first start downloads the app's packages, which takes a few seconds. Then check that both the key and the model work:
sleep 15; curl -s localhost:8000/api/health
Expect {"sandboxes":0,"exe":"ok","llm":"ok"}.
Check it from the VM#
This asks one question in a new conversation, which starts the base VM and a sandbox, then deletes the conversation and its sandbox:
ID=$(curl -s -X POST localhost:8000/api/conversations | jq -r .id)
curl -sN localhost:8000/api/conversations/$ID/messages \
-H 'Content-Type: application/json' \
-d '{"text": "What is the 1000th prime? Compute it."}' | jq -c '{type, text, code, stdout}'
curl -s -X DELETE localhost:8000/api/conversations/$ID
Expect lines of code and output, and an assistant line that says
7919. The first question takes about 20 seconds, because the base VM is
set up first; after that, a new conversation's sandbox is ready in about 6.
Ask it something#
Open https://my-code-interpreter.exe.xyz/ and sign in with exe.dev. Try "Simulate 10,000 rolls of two dice and plot the distribution of the sums", or click Upload to send a CSV and ask about it. You see each cell the model runs, its output, and its charts as they happen, and the files it saves as download links. Follow-ups carry on with the data already loaded.
Only you can open the page until you share the VM, and everyone it's shared with can run code on your account.
How it works#
- The first cell of a conversation creates its sandbox, a copy of a base VM that the app sets up once with Python packages. It's ready in about 6 seconds.
- Each cell runs in the sandbox's IPython kernel in about half a second
plus the code's own time. The model sees the output, the last value, and
any error; charts go to you. A cell past
CELL_TIMEOUTis interrupted, as with Ctrl-C, and the kernel keeps its variables. - Idle sandboxes pause after 30 minutes, keeping the kernel's memory, and resume at the next cell. After 3 idle days, or when you delete the conversation, the sandbox is deleted.
- Conversations are JSON files in
~/code-interpreter/data.
Use it in your own code#
sandbox.py works without the app. Put it beside your code and
uv add exedev:
import exedev
from sandbox import Sandbox
exe = exedev.Client() # reads EXE_TOKEN
with Sandbox.create(exe) as sb: # a new VM, deleted at the end of the block
sb.run_code("import pandas as pd\ndf = pd.DataFrame({'x': [1, 2, 3]})")
print(sb.run_code("df.x.sum()").text) # np.int64(6)
print(len(sb.run_code("df.x.plot()").pngs)) # 1, a base64 PNG
ask.py is the whole tool-calling loop in 50 lines, with a sandbox for one question. On an exe.dev VM, beside sandbox.py:
EXE_TOKEN=exe1.... uv run ask.py "What is the 1000th prime, and the sum of its digits?"
Lock it down#
A sandbox is private like any VM and can't reach the app's VM or the key. It can reach:
- The internet.
SANDBOX_EGRESS=integrationscuts new sandboxes off (see Egress policy);%pip installthen fails, so add packages toPACKAGESin sandbox.py first. - Integrations attached to
auto:all, such as the LLM gateway (billed to you), even withSANDBOX_EGRESS=integrations. To keep them off sandboxes, attach them by tag to your own VMs instead; see Attaching integrations.
Settings#
In /etc/code-interpreter.env, then sudo systemctl restart code-interpreter:
| Setting | Default | What it does |
|---|---|---|
MODEL |
gpt-5.5 |
The model, from curl https://llm.int.exe.xyz/v1/models |
SANDBOX_EGRESS |
(all) | integrations cuts new sandboxes off the internet |
PAUSE_AFTER_MINUTES |
30 | Pause a sandbox after this long idle |
DELETE_AFTER_DAYS |
3 | Delete a sandbox after this long idle |
CELL_TIMEOUT |
300 | Seconds before a cell is interrupted |
Back up and update#
Conversations are in ~/code-interpreter/data; back them up with
tar czf ~/code-interpreter-$(date +%F).tgz -C ~/code-interpreter data and
copy the file off the VM. Sandboxes aren't backed up.
To update, on the VM:
cd ~/code-interpreter
for f in app.py sandbox.py index.html ask.py; do
curl -fsSLO https://exe-staging.dev/deploy/code-interpreter/$f
done
sudo systemctl restart code-interpreter
After changing PACKAGES, delete the base VM (ssh exe.dev ls shows it
with the comment code-interpreter-base), and the next sandbox sets up a
new one.
Troubleshooting#
journalctl -u code-interpreter is the app's log, and
curl -s localhost:8000/api/health checks the key and the model.
401orinvalid token: the key is wrong or expired. Make a new one and restart.command not allowed by ... permissions: the key is missing a command from the--cmdsin Before you start. Withoutpause, idle sandboxes keep running until they're deleted.- A VM limit error: delete old conversations, or other VMs.
no model gpt-5.5: setMODELto one the gateway lists.
Frequently asked questions#
How is it different from E2B?
E2B runs sandboxes on its servers and bills them by the second, with a time limit on each. Here each sandbox is an ordinary exe.dev VM on your plan: it lasts until you delete it, pauses with its kernel's memory intact, and your app and your sandboxes stay in one account. run_code() returns the same kind of result as E2B's: output, the last value, PNGs, and the error.
Can the code reach the internet?
Yes, by default, so %pip install and downloads work. Set SANDBOX_EGRESS=integrations to cut new sandboxes off; then only the preinstalled packages work.
What does it cost?
Sandboxes share your plan's CPU and memory rather than being billed one by one, but each counts toward the plan's VM limit (50 on Personal), paused or not. Model calls are billed to your exe.dev account through the LLM integration. Delete conversations you're done with, and their sandboxes go too.
Which models can it use?
Any model the LLM gateway serves through the OpenAI Chat Completions API with tool calls: gpt-5.5 by default, and others from curl https://llm.int.exe.xyz/v1/models, such as fireworks/glm-5p3. Set MODEL in /etc/code-interpreter.env.
Updated Oct 28, 2026 · Markdown · manifest.json · Something wrong? Tell us