Pydantic Monty: A Secure Python Interpreter for AI Agents
Pydantic has released Monty, a minimal Python interpreter written in Rust for safely executing LLM-generated code.
AI agents that generate code face a tradeoff: trust the generated code completely, or spin up containers for isolation. Containers are slow and resource-intensive. Trusting arbitrary code is risky. Monty sidesteps both by sandboxing execution at the interpreter level, blocking dangerous operations while running Python in microseconds.
How It Works
Monty provides a straightforward API:
import pydantic_monty as monty
# Simple execution
m = monty.Monty('x * y', inputs=['x', 'y'])
result = m.run(inputs={'x': 5, 'y': 3}) # Returns 15Monty also supports external functions. Code running inside the sandbox can call out to host functions:
m = monty.Monty(
'fetch_data(query).upper()',
inputs=['query'],
external_functions=['fetch_data']
)
result = m.run(
inputs={'query': 'users'},
external_functions={'fetch_data': lambda q: f"data for {q}"}
)
# Returns "DATA FOR USERS"Start/Resume Pattern
Monty can pause execution when an external call is needed, rather than requiring all external functions upfront:
code = """
data = fetch_data(query)
processed = data.upper()
save_result(processed)
len(processed)
"""
m = monty.Monty(code, inputs=['query'], external_functions=['fetch_data', 'save_result'])
# Start - pauses at first external call
state = m.start(inputs={'query': 'users'})
# state.function_name == 'fetch_data'
# Resume with result - pauses at next external call
state = state.resume(return_value="hello world")
# state.function_name == 'save_result'
# Resume again - completes
state = state.resume(return_value=None)
# state.output == 11Execution state can be serialized and restored, enabling durable agent workflows that survive restarts.
Security Model
Monty takes a deny-by-default approach. Functions like open(), __import__(), eval(), and exec() simply don’t exist. The os and sys modules are stubs that return safe values (sys.version returns “3.14.0 (Monty)”). Subprocess, file access, and environment variables are all blocked.
For cases where controlled file access is needed, Monty provides an in-memory file system abstraction:
from pydantic_monty import OSAccess, MemoryFile
mem_file = MemoryFile(path="/data.txt", content=b"Hello!")
os_access = OSAccess(files=[mem_file])Performance
Simple operations complete in under a microsecond. Recursive functions with list comprehensions run in single-digit microseconds. Monty instances can be reused: parse once, run many times with different inputs.
Current Limitations
At version 0.0.3, Monty doesn’t yet support class definitions, match statements, context managers, generators, or most standard library modules. It covers the core Python features needed for typical LLM-generated code but isn’t a complete Python implementation.
Installation
Monty is available via pip or uv:
uv add pydantic-montyBindings also exist for Rust and JavaScript/TypeScript.
Pydantic AI Integration
The Pydantic team building a secure interpreter suggests deeper integration with Pydantic AI is likely coming. Monty is lightweight enough for real-time code execution in conversational AI, where container startup time would break the interaction flow.