Problem or use case
Code execution needs Docker today. otari_code_execution, and under auto (#1366) every provider-native code-execution declaration the dispatched model cannot run itself, is dispatched over the code-execution contract to a backend Otari was configured with, and the only OSS backend is otari-sandbox-container. A deployment without sandbox_url refuses the explicit type and forwards a provider declaration untouched, so the model-swap promise auto makes ("a request written for Claude's sandbox keeps working on an open model") is kept only for operators running a container. That is most people evaluating the project on a laptop, and everyone on a host without Docker.
Proposed solution
Ship an in-process code-execution adapter over pydantic/monty (pydantic-monty, MIT) and make it the default when no sandbox_url is set.
Monty is a Python interpreter in Rust built for model-written code: it starts in about 5 ms, has no filesystem, environment or network inside unless a directory is mounted or a host function is passed, enforces memory, time and recursion limits in the VM, keeps globals across feeds within one checked-out session (the per-request statefulness the contract requires), and hands out a pool of worker subprocesses. It is pre-V1 ("last round before Monty V1" as of 2026-09-18).
Shape:
- The
CodeExecutionPort the executor PR names as a follow-up, with the contract's five operations as its surface. Two adapters: http, wrapping today's SandboxBackend unchanged, and monty, in process. The tool loop, the file bridge, the tally and the policy code keep talking to the port.
- Binding:
sandbox_url set wins and binds http. Otherwise, when pydantic_monty is importable, sandbox_configured() answers true with monty. Standalone only; hybrid keeps http, because the platform proxy is what meters it.
- Optional extra:
otari[monty]. The OSS smoke gate (scripts/oss_edition_smoke.py) keeps running under --no-dev without it, so the core still boots with no sandbox at all.
- Honest advertisement. The default purpose hint promises numpy, pandas, scipy, sympy and matplotlib; monty runs a Python subset with no third-party packages, no generators, no class inheritance, and a partial stdlib (no
io, enum, csv, hashlib). The purpose hint and the GET /api/v1/tools description must therefore come from the bound adapter, so the model is told "stdlib only, use json and math" and the operator sees which backend answers. Monty's type checker rejects unsupported APIs before they run, which gives the model a usable retry signal in stderr.
- Files map onto
MountDir over a per-request temporary directory: seed by writing the staged uploads into it, collect by diffing it after each feed and handing new files to the existing SandboxFileBridge. Produced files are data files; nothing can render a chart.
- Limits and policy:
exec_timeout_s maps to monty's feed duration limit; add a memory ceiling setting. A workspace policy that pins an image is refused with a clear detail when the bound adapter is monty, since there is nothing to pin. The SERVED_TOOL_NAMES / tools allow-list keeps meaning code_execution.
auto treats a monty-bound gateway as having a sandbox for provider keywords too, not only for the explicit type. A stdlib-only run is a better outcome than today's provider error, provided item 4 is in place.
Later, not here: monty's snapshot (a paused interpreter serializes to kilobytes) is the natural basis for the cross-request container id follow-up.
Does this belong in Otari?
Yes. It is the "basic local sandbox in the core" that ARCHITECTURE.md already reserves next to a hardened one from an overlay, and it is a new adapter for a port the core owns, not a provider call. Pydantic's closed-source Full Monty Server (WebSocket, design-partner access, amd64 only, no authentication, always-on payload tracing) could be a third adapter on the overlay side one day; it is not this issue, and the contract is not replaced by monty's wire protocol.
Alternatives considered
- Implement the HTTP contract over monty as a separate backend. Keeps the gateway untouched, but the backend would still be a process to run, which is the friction this removes. Could be mounted inside the gateway app and dialed over loopback, but talking HTTP to yourself buys nothing the port does not.
- Pyodide or WASI CPython. Full language, but seconds of cold start per request and no snapshotting; monty's own comparison table is fair on this.
- Keep Docker as the requirement. Leaves the
auto promise unkept for the people most likely to be trying the project for the first time.
Related: #1366 (the executor, and the port follow-up it names), #896, #786.
Problem or use case
Code execution needs Docker today.
otari_code_execution, and underauto(#1366) every provider-native code-execution declaration the dispatched model cannot run itself, is dispatched over the code-execution contract to a backend Otari was configured with, and the only OSS backend isotari-sandbox-container. A deployment withoutsandbox_urlrefuses the explicit type and forwards a provider declaration untouched, so the model-swap promiseautomakes ("a request written for Claude's sandbox keeps working on an open model") is kept only for operators running a container. That is most people evaluating the project on a laptop, and everyone on a host without Docker.Proposed solution
Ship an in-process code-execution adapter over pydantic/monty (
pydantic-monty, MIT) and make it the default when nosandbox_urlis set.Monty is a Python interpreter in Rust built for model-written code: it starts in about 5 ms, has no filesystem, environment or network inside unless a directory is mounted or a host function is passed, enforces memory, time and recursion limits in the VM, keeps globals across feeds within one checked-out session (the per-request statefulness the contract requires), and hands out a pool of worker subprocesses. It is pre-V1 ("last round before Monty V1" as of 2026-09-18).
Shape:
CodeExecutionPortthe executor PR names as a follow-up, with the contract's five operations as its surface. Two adapters:http, wrapping today'sSandboxBackendunchanged, andmonty, in process. The tool loop, the file bridge, the tally and the policy code keep talking to the port.sandbox_urlset wins and bindshttp. Otherwise, whenpydantic_montyis importable,sandbox_configured()answers true withmonty. Standalone only; hybrid keepshttp, because the platform proxy is what meters it.otari[monty]. The OSS smoke gate (scripts/oss_edition_smoke.py) keeps running under--no-devwithout it, so the core still boots with no sandbox at all.io,enum,csv,hashlib). The purpose hint and theGET /api/v1/toolsdescription must therefore come from the bound adapter, so the model is told "stdlib only, usejsonandmath" and the operator sees which backend answers. Monty's type checker rejects unsupported APIs before they run, which gives the model a usable retry signal instderr.MountDirover a per-request temporary directory: seed by writing the staged uploads into it, collect by diffing it after each feed and handing new files to the existingSandboxFileBridge. Produced files are data files; nothing can render a chart.exec_timeout_smaps to monty's feed duration limit; add a memory ceiling setting. A workspace policy that pins animageis refused with a clear detail when the bound adapter ismonty, since there is nothing to pin. TheSERVED_TOOL_NAMES/toolsallow-list keeps meaningcode_execution.autotreats a monty-bound gateway as having a sandbox for provider keywords too, not only for the explicit type. A stdlib-only run is a better outcome than today's provider error, provided item 4 is in place.Later, not here: monty's snapshot (a paused interpreter serializes to kilobytes) is the natural basis for the cross-request
containerid follow-up.Does this belong in Otari?
Yes. It is the "basic local sandbox in the core" that
ARCHITECTURE.mdalready reserves next to a hardened one from an overlay, and it is a new adapter for a port the core owns, not a provider call. Pydantic's closed-source Full Monty Server (WebSocket, design-partner access, amd64 only, no authentication, always-on payload tracing) could be a third adapter on the overlay side one day; it is not this issue, and the contract is not replaced by monty's wire protocol.Alternatives considered
autopromise unkept for the people most likely to be trying the project for the first time.Related: #1366 (the executor, and the port follow-up it names), #896, #786.