Code Interpreter
Code execution is one dispatcher across languages and backends. Send a language and a code string: the daemon routes it to a runtime and answers when the run finishes.
The runtime is a native REPL, an embedded IPython kernel, or an external Jupyter-compatible server. The dispatcher picks one per language and falls back through what it finds, so a request never has to name a runtime.
A code session keeps an interpreter process alive between calls: what one call defines, the next one sees. Without a session, each run is a one-off in a process that is discarded afterwards.
For a shell command rather than a code cell, use Commands. The v1 and v2 routes side by side are in Migration from 1.x.
What it needs
GET /v1/capabilities describes the plane under code_interpreter:
status—readyonce either interpreter answers,absentwhen neither doesbackend— the layer that runs Python:native,kernel, orendpointkinds—code,nodejs, andjupyterwhere a kernel serves Pythondefault_python,default_node— the binaries the native tier spawns
The native tier needs python3 or node on PATH; the kernel tier also needs an importable ipykernel. The daemon selects an execution tier based on the runtimes available.
Run a snippet
Request body:
Request body:
The body takes these fields:
language also accepts python3, node, nodejs, and js, and a field the route does not know is ignored. A user this daemon cannot become is not a rejection: the run answers HTTP 200 with status: "error" and a ProcessError output saying only root can change identity. A kernel ignores user and runs as the daemon's own account.
data carries:
language,code— the resolved language and the code that ranstatus—ok,error, ortimeoutoutputs— the notebook output list, one entry per thing the run producedstdout,stderr— each stream on its own; empty is""on v2,nullon v1exit_code—0when the code completed,1after an error or a timeoutexecution_count— the session's cell counter, from 1session_id— the session that ran it;nullfor a one-off
status is the outcome of the run, and success in the envelope follows it. An exception or a timeout inside the code is still an HTTP 200, with success: false.
An empty code string is the one field the surfaces read differently: v2 rejects it with a 422, v1 runs an empty cell and answers 200 with no outputs.
outputs is the notebook output list:
JavaScript is the same request with another language: console.log([1, 2, 3].reduce((a, b) => a + b, 0)); prints 6 the same way.
Two language-specific routes run the same code with their own session semantics:
POST /v1/jupyter/executeuses a real IPython kernel, with magics and rich output.POST /v1/nodejs/executeuses the JavaScript REPL.
Runtime info
Both surfaces answer with the same body — which backend, languages, and limits are in effect:
backend— the layer that runs Python here:native,kernel, orendpointkinds— the runtimes present:python,nodejs, plusjupyterwith a kernellanguages— whatlanguageacceptspython,node—{available, version}eachmax_sessions— how many sessions can be open at oncedefault_timeout,max_timeout—30and900prewarmed— how many warm processes are waiting;0by default
Keep variables across calls
A session_id the daemon has not seen is created on the spot, and stateful: true without one returns a generated id. Session state lives in memory: it survives calls within the session, not an aiod restart.
A session entry carries:
session_id,language,cwd— what it is and where it runscreated_at,last_used— milliseconds since the epochage_seconds— seconds since the last runmax_idle_time— milliseconds of idle time before it is closedstate—idleorexecuting
A kernel-backed Python session is listed here too, carrying its kernel_name instead of a max_idle_time.
The following example makes two calls in the same session. Aio is the envelope-aware helper from Examples:
Sessions are listed and ended per language, on the route that owns them:
Two calls sharing a session, through the SDK:
Which SDK calls reach these routes unchanged is listed in 1.x SDK compatibility.
Where a kernel serves Python, a code session and a Jupyter session with the same id are one namespace: {"session_id": "s1"} on the code route and on /v1/jupyter/execute reach the same kernel, and either side can end it.
Patterns for an agent
Most runs are one-offs.
Timeouts and limits
A run that exceeds its timeout returns status: "timeout". What happens next, and the resource cost, depend on the tier:
The native tier kills the interpreter five seconds past the deadline, so a run that ends inside that grace still returns its output; the session stays open and its namespace starts empty again. The kernel interrupts the cell instead, and keeps both the kernel and everything defined before it.
The two limits come from AIO_CODE_MAX_SESSIONS / AIO_CODE_SESSION_TIMEOUT_SECS and AIO_KERNEL_MAX_SESSIONS / AIO_KERNEL_SESSION_TIMEOUT_SECS. One session past the limit is a 429: Maximum number of sessions (20) reached, or Maximum number of kernel sessions (5) reached. Existing sessions are never evicted.
How it picks a backend
For Python, the daemon tries three backends in order:
- The external Jupyter-compatible endpoint at
AIO_JUPYTER_ENDPOINT, if reachable - An embedded kernel, if
ipykernelis installed - The native Python REPL
JavaScript always runs on the native REPL and needs only node on PATH. Nothing is warmed by default: AIO_CODE_PREWARM, which pools native JavaScript harnesses, and AIO_KERNEL_PREWARM are both 0.
AIO_CODE_BACKEND (auto, native, or kernel) pins one Python backend and skips this order.
More languages
language accepts python (also python3) and javascript (also node, nodejs, js); anything else is a 422. To support other languages, choose one of these approaches, in order of effort:
- Any interpreter, no state. Run it through Commands, as
ruby -e "puts 1". Nothing to configure; you give up only session state and the notebook-shaped output. - A different Python or Node.
PYTHON_VERSION/NODE_VERSIONpick whichpython3/nodeonPATHthe native tier spawns.kernel_nameon/v1/jupyterpicks among the installed kernels. See Jupyter. - Add a language to the code plane. The native tier is one small, stdlib-only harness per language, compiled into the binary (
harness.py,harness.js).
The daemon writes {"code", "timeout_ms"} lines to the harness's stdin and reads {"stdout", "stderr", "result", "error"} lines from its stdout, one process per session.
Adding a language means writing that harness for its interpreter and registering it in the daemon's Language list. The HTTP surface, sessions, timeouts, and limits are shared.
Error handling
An exception, a timeout, or a dead interpreter is an HTTP 200 with success: false and status error or timeout. Only a bad request carries an error status code:
A v2 refusal puts the reason in message: Unsupported language 'ruby'. Supported: python, javascript. A v1 refusal carries that same sentence in the errors list, one entry per rejected field, with location: ["body", "language"] and type: "enum".
See Error Handling for the cross-plane conventions.