Skip to content

Monty

CICoveragePyPINPMcrates.iolicenseJoin Slack

A minimal, secure Python sandbox written in Rust for code written by AI.

Monty avoids the latency, complexity and cost of a container based sandbox for running LLM generated code. It comes in two forms: OSS Monty, the MIT licensed Python 3.14 sandbox you install as a package, and Full Monty, the commercial server that runs the same sandbox behind a WebSocket as a service.

Latency

Time to get a new sandbox and run 10 REPL commands

SandboxNew sandboxšAgent run²Combined³Execution env⁴
OSS Monty0.80 ms0.40 ms1.20 mslocal
Full Monty (WebSocket)1.70 ms5.30 ms7.00 msremote
WASI / wasmtime16 ms180 ms200 mslocal
local Docker195 ms700 ms900 mslocal
Sandboxing service1500 ms400 ms1900 msremote
Pyodide in Deno2700 ms35 ms2700 mslocal
  1. New sandbox: the time to get a fresh sandbox and run 1 + 1 in it. For OSS Monty and Full Monty that is a checkout from a pool the application already created. The others have no pool, so each new sandbox starts from nothing.
  2. Agent run: 10 commands run in a REPL against a sandbox that already exists, as you might expect from a simple agent with code mode. OSS Monty and Full Monty keep the session, so each command is one feed; the others have no persistent interpreter, so command n re-runs commands 1 to n.
  3. Combined: the time to create the sandbox and perform the agent run: the two columns added together.
  4. Execution env: OSS Monty, WASI, local Docker and Pyodide run the code on the same machine as the application calling them. Full Monty and sandboxing services run it remotely, which reduces the blast radius of an escape and lets the sandboxes scale independently of the hosts calling them.

Learn more in the comparison to alternatives.

Why Monty

  1. Latency in milliseconds, not seconds. A new sandbox plus ten REPL commands takes 1.2 ms vs. 1900 ms for a sandboxing service, because a sandbox is a checkout from a pool of worker subprocesses, a command is one message each way, and the session persists so nothing is re-run. See start latency.
  2. Simple to deploy at massive scale. Because you’re not provisioning a new VM or container for every sandbox you can run thousands of workers with minimal cost and complexity.
  3. Suspend and resume from bytes. Monty lets you dump the whole sandbox state to bytes at an external function call or at the end of a repl snippet. This makes long external function calls and human-in-the-loop not only possible but very cheap. It also makes extremely long running REPL sessions easy to implement. See snapshots.
  4. Strict resource limits maximum memory and execution time are enforced by the VM itself so 'x' * 10**12 raises MemoryError before the allocation is attempted. See resource limits.
  5. Local package for development, commercial option for scale. OSS Monty provides packages for Python, JS and Rust, making it trivial to get started with Monty. For greater security guards and larger scale deployments, Full Monty runs the same workers behind a WebSocket as a container image, adding OS-level isolation and horizontal scaling.

Example

Installation

Terminal
uv add pydantic-monty

See getting started with Python.

The code string is what a model writes when asked how long a bar of chocolate could power a lightbulb. It calls a tool it was given, does arithmetic it should not do in its head, and prints the answer:

from pydantic_monty import Monty

code = """
kcal = nutrition('chocolate bar')['kcal']
hours = kcal * 4184 / (bulb_watts * 3600)
print(f'a chocolate bar could power a {bulb_watts}W bulb for {hours:.1f} hours')
"""

with Monty() as pool:
    with pool.checkout() as session:
        session.feed_run(
            code,
            inputs={'bulb_watts': 10},
            external_lookup={'nutrition': lambda food: {'kcal': 230}},
        )
        #> a chocolate bar could power a 10W bulb for 26.7 hours

Or in TypeScript:

import { Monty } from '@pydantic/monty'

const code = `
kcal = nutrition('chocolate bar')['kcal']
hours = kcal * 4184 / (bulb_watts * 3600)
print(f'a chocolate bar could power a {bulb_watts}W bulb for {hours:.1f} hours')
`

await using pool = await Monty.create()
await using session = await pool.checkout()
await session.feedRun(code, {
  inputs: { bulb_watts: 10 },
  externalLookup: { nutrition: (food: string) => ({ kcal: 230 }) },
})
// a chocolate bar could power a 10W bulb for 26.7 hours

nutrition ran on the host and the sandbox saw only its return value; the sandbox has no filesystem, environment or network with which to reach anything else. The Python, JavaScript and Rust quickstarts take it from here. Monty can do much more than this, see Examples.

Where the code comes from

LLMs are often faster, cheaper and more reliable when they write a short program that calls your tools, instead of making a sequence of individual tool calls: code mode from Cloudflare, programmatic tool calling and code execution with MCP from Anthropic, smolagents from Hugging Face. All of them need somewhere safe to run the generated code, and Monty is that place.

Next steps