Stateful Python code-interpreter sandboxes for AI agents
O Runbox dá a um agente de IA um interpretador Python isolado e com estado: o usuário anexa uma planilha, o modelo sobe o arquivo, roda pandas/matplotlib em turnos (as variáveis persistem entre execuções) e recebe gráficos e arquivos de volta como URLs públicas — nunca base64 no contexto do modelo.
Runbox gives an AI agent an isolated, stateful Python interpreter: upload the user's spreadsheet, run pandas/matplotlib across turns (variables persist between runs), and get charts/files back as public URLs — never base64 in the model context.
Base: https://runbox.api.insyde.one — autenticada /
authenticated.
· Playground
· llms.txt (cole numa IA / paste into an AI)
Token por tenant no formato tenant:hash, via header (ou ?key=):
Per-tenant token in tenant:hash form, via header (or ?key=):
Authorization: Bearer cnn:55abc47c…
404 uniforme (a existência nunca vaza).
404 (existence never leaks).
# 1. uma sessão por conversa (estado persiste entre turnos)
POST /sessions → { "sessionId": "mdl_sb-…", "expiresAt": "…" }
# 2. anexos do usuário entram no filesystem do sandbox
POST /sessions/{id}/files → { "path": "/work/data.csv" }
# 3. cada turno roda código; df definido no turno 1 existe no turno 2
POST /sessions/{id}/run → stdout, result, error, displays, artifacts
# 4. fim da conversa (opcional — a sessão expira sozinha por idle)
DELETE /sessions/{id}
POST /sessions
{ "conversationId": "conv-123", "idleTtlSec": 120, "maxLifetimeSec": 3600 }
| Campo / Field | Descrição / Description |
|---|---|
idleTtlSec | Sessão morre após esse tempo sem runs — janela deslizante, cada run renova (60–300, padrão 120). / Sliding idle window, each run renews it. |
maxLifetimeSec | Teto absoluto (300–7200, padrão 3600). / Hard ceiling. |
conversationId | Metadado livre para mapear sessão ↔ conversa. / Free metadata to map session ↔ conversation. |
409 se o tenant exceder a quota de sessões simultâneas (padrão 5). / if the tenant exceeds the concurrent-session quota (default 5).
# multipart
curl -X POST .../sessions/{id}/files -H "Authorization: Bearer …" -F "file=@dados.csv"
# ou por URL (o servidor baixa)
{ "url": "https://exemplo.com/dados.xlsx", "name": "dados.xlsx" }
Resposta { "path": "/work/dados.csv" } — use exatamente esse path no código. Máx 25MB. / Use this exact path in code. Max 25MB.
POST /sessions/{id}/run
{ "code": "import pandas as pd\ndf = pd.read_csv('/work/dados.csv')\ndf.describe()",
"timeoutMs": 30000 }
Resposta com semântica Jupyter / Response with Jupyter semantics:
{
"ok": true,
"stdout": "…", // print(), cap de 64KB (flag truncated)
"result": "42", // repr da ÚLTIMA EXPRESSÃO (ou null)
"error": null, // ou {ename, evalue, traceback[]} estruturado
"displays": [ // representações ricas (DataFrame → tabela HTML)
{ "mimeType": "text/html", "data": "<table>…" }
],
"artifacts": [ // capturados AUTOMATICAMENTE neste run
{ "name": "figure-1-1.png", "mimeType": "image/png",
"url": "https://runbox.api.insyde.one/a/…", "sizeBytes": 9981 }
],
"durationMs": 776
}
outputs/ vira artifact com URL — o modelo não chama savefig().
As URLs são públicas e não-adivinháveis: passe direto pro chat renderizar. Válidas por
7 dias (ou até o DELETE da sessão).
outputs/ becomes an artifact URL — the model never calls savefig().
URLs are public and unguessable: hand them straight to the chat UI. Artifact URLs stay
valid for 7 days (or until the session is DELETEd).
200 com
ok:false e traceback limpo em error — o modelo conserta o código e
roda de novo na mesma sessão (o estado sobrevive ao erro).
200 with
ok:false and a clean traceback in error — the model fixes the code
and reruns in the same session (state survives the error).
/work. Filesystem destruído com a sessão. / Filesystem destroyed with the session.requests.get() falha; não tente pip install. / No network — don't try to pip install.Pacotes pré-instalados (nome pip → nome de import quando diferem): / Preinstalled packages (pip name → import name where they differ):
| Categoria / Category | Pacotes / Packages |
|---|---|
| Análise de dados / Data analysis | pandas, numpy, scipy, statsmodels |
| Gráficos / Plotting | matplotlib, seaborn, squarify (treemaps) |
| Estilo D3 / Vega | altair (gramática Vega-Lite) + vl-convert (render offline em SVG/PNG, sem navegador) |
| Mapas / Geo | geopandas, shapely, pyproj, pyogrio (ler shp/geojson/gpkg), mapclassify (faixas de coroplético) |
| Parlamento | parliamentarch (hemiciclo em SVG), cairosvg (rasterizar SVG→PNG) |
| Planilhas / Spreadsheets | openpyxl (ler/escrever xlsx), xlsxwriter (escrever xlsx), pyarrow (parquet/feather) |
pdfplumber (extrair texto/tabelas), pypdf (dividir/juntar/metadados), reportlab (gerar PDF diagramado) | |
| Documentos / Documents | python-docx → docx, Pillow → PIL |
| Parsing | beautifulsoup4 → bs4, lxml, unidecode (tirar acento), tabulate (tabela em texto) |
| Texto & locale | charset-normalizer → charset_normalizer (detecção de encoding), babel (formatação de número/data/moeda) |
| HTTP | requests (inutilizável — rede bloqueada / unusable — network blocked) |
#0f0051, verde #00ff80, azul #3cc5f5,
fonte DM Sans, decimais com vírgula pt-BR). É o default — só plote; não chame
plt.style.use(...) nem sns.set_theme(). /
Charts are pre-styled with the insyde.one brand — it's the matplotlib
default, just plot.
fig.savefig("outputs/grafico.svg") (vira artifact com URL). Para
o visual declarativo estilo D3, use Altair — renderiza offline via
vl-convert (sem navegador); aplique o tema da marca com uma linha:
import insyde_theme e depois chart.save("outputs/c.svg"). /
Save vector SVG for broadcast; use Altair (offline via vl-convert) for the D3-style look.
/opt/runbox/data/geo/ —
leia com geopandas.read_parquet (EPSG:4326): br_estados.parquet,
br_municipios.parquet, mundo_paises.parquet,
mundo_continentes.parquet. Faça o join dos seus dados na coluna-chave e
gdf.plot(column=…, scheme="quantiles", legend=True) para um coroplético.
Tudo abaixo saiu do sandbox sem estilização manual — o padrão insyde.one é o default do matplotlib, e as malhas vêm da imagem. / Everything below came straight from the sandbox with no manual styling — the insyde.one look is the matplotlib default and the boundaries ship in the image.
squarify treemap.
O coroplético de SP, ponta a ponta: / The SP choropleth, end to end:
import geopandas as gpd
g = gpd.read_parquet("/opt/runbox/data/geo/br_municipios.parquet")
sp = g[g.abbrev_state == "SP"].copy()
sp["idh"] = meus_dados # join your column here
ax = sp.plot(column="idh", scheme="quantiles", k=5, legend=True,
edgecolor="white", linewidth=0.15)
ax.set_axis_off() # figura capturada automaticamente / auto-captured
Para o visual declarativo e polido do tipo D3, o sandbox traz
Altair (gramática Vega-Lite). Ele renderiza offline via
vl-convert (motor em Rust, sem navegador) — então chart.save("outputs/x.png")
ou .svg funciona normalmente. Aplique o tema da marca com uma linha:
import insyde_theme. /
Altair (Vega-Lite) gives the polished, D3-style declarative look, rendered offline via
vl-convert. One line for the brand theme: import insyde_theme.
from altair.datasets import data (Altair 6 — antes era vega_datasets,
e os nomes de coluna passaram de under_score para com espaço). Mas
altair.datasets baixa os dados da internet — e a sandbox é
sem rede, então não funciona aqui. Use o seu próprio
DataFrame (o caso real — o usuário sobe a planilha), ou o pacote
vega_datasets que traz os clássicos localmente (atenção: colunas
com under_score). /
The official gallery's altair.datasets fetches over the network and won't work
offline — bring your own DataFrame, or use the bundled vega_datasets (underscore
column names).
mark_bar() com tema da marca.
mark_line(point=True) multi-série.
mark_circle() por categoria.
mark_rect() heatmap (escala viridis).Um gráfico Altair, ponta a ponta (tema da marca + render offline): / An Altair chart, end to end:
import insyde_theme, altair as alt, pandas as pd
df = pd.read_csv("/work/vendas.csv") # seu DataFrame / your own data
chart = (alt.Chart(df, title="Receita por praça")
.mark_bar()
.encode(x="praca:N", y=alt.Y("valor:Q", title="R$ mi"))
.properties(width=360, height=240))
chart.save("outputs/receita.png", scale_factor=2) # ou .svg (vetor) — vira artifact
Duas vias: parliamentarch (gera o SVG das cadeiras → rasterize com
cairosvg.svg2png), ou ~25 linhas de matplotlib (arcos concêntricos de pontos —
já sai no padrão da marca, em PNG). /
Either parliamentarch (seat SVG + cairosvg) or a small matplotlib version.
| Rota / Route | Descrição / Description |
|---|---|
GET /sessions/{id} | Status: {alive, createdAt, expiresAt, conversationId} |
GET /sessions/{id}/files | Lista arquivos do sandbox (?path= opcional) / list sandbox files |
GET /sessions/{id}/files/{path} | Baixa um arquivo / download a file |
DELETE /sessions/{id} | Mata o sandbox e apaga seus artifacts / kill + purge artifacts |
GET /a/{…}/{…}/{nome} | Público — download de artifact (URL não-adivinhável, cache imutável via CDN) |
GET /health | Sem auth / no auth |
| HTTP | Significado / Meaning | O que fazer / What to do |
|---|---|---|
401 | Token ausente/inválido / missing or bad token | Confira o tenant:hash |
404 | Sessão inexistente, expirada ou de outro tenant / unknown, expired or cross-tenant | Crie nova sessão e re-suba os arquivos / create a new session, re-upload files |
409 | Quota de sessões / session quota | Reuse ou DELETE uma sessão viva |
413 | Arquivo > 25MB / file too large | Reduza o arquivo |
422 | Entrada inválida / bad input | code obrigatório; só language=python |
502/504 | Falha do provider / run perdido | Tente de novo; persistindo, nova sessão / retry; then new session |
Três funções bastam: runbox_create_session, runbox_upload_url e
runbox_run — cada uma é um fetch com o Bearer token. Os schemas
completos (OpenAI tools + Anthropic input_schema), prontos para copiar, estão no
runbox-llms.txt — feito para colar inteiro no
contexto de outra IA.
Three functions are enough — each is one fetch with the Bearer token. The full
copy-paste schemas (OpenAI tools + Anthropic input_schema) live in
runbox-llms.txt, written to be pasted whole
into another AI's context.
// executor mínimo / minimal executor
const BASE = 'https://runbox.api.insyde.one';
const H = { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' };
async function runboxRun(sessionId, code) {
const r = await fetch(`${BASE}/sessions/${sessionId}/run`,
{ method: 'POST', headers: H, body: JSON.stringify({ code }) });
return r.json(); // devolva stdout/result/error ao modelo; URLs vão pro chat
}
404, recrie a sessão e re-suba os anexos · nunca traga
bytes de artifact para o prompt — só as URLs.
404 recreate and re-upload · never pull artifact bytes into the prompt.