Zurück zu den Updates
New releaseJul 25, 2026

monty v0.0.19

Ein minimaler, sicherer Python-Interpreter, geschrieben in Rust, zur Verwendung durch KI.

Teilen

Monty

Ein minimaler, sicherer Python-Interpreter, geschrieben in Rust, für die Nutzung durch KI.

CI Codspeed Coverage PyPI versions license Join Slack

Experimentell – Dieses Projekt befindet sich noch in der Entwicklung und ist noch nicht bereit für den produktiven Einsatz.

Ein minimaler, sicherer Python-Interpreter, geschrieben in Rust, für die Nutzung durch KI.

Monty vermeidet die Kosten, Latenz, Komplexität und den allgemeinen Aufwand einer vollständigen containerbasierten Sandbox für die Ausführung von LLM-generiertem Code.

Stattdessen können Sie damit sicher Python-Code ausführen, der von einem LLM geschrieben wurde und in Ihrem Agenten eingebettet ist – mit Startzeiten im einstelligen Mikrosekundenbereich, nicht in Hunderten von Millisekunden.

Was Monty kann:

  • Eine vernünftige Teilmenge von Python-Code ausführen – genug, damit Ihr Agent ausdrücken kann, was er tun möchte
  • Den Zugriff auf die Host-Umgebung vollständig blockieren: Dateisystem, Umgebungsvariablen und Netzwerkzugriff werden alle über externe Funktionsaufrufe implementiert, die der Entwickler kontrollieren kann
  • Funktionen auf dem Host aufrufen – nur Funktionen, auf die Sie ihm Zugriff gewähren
  • Typprüfung ausführen – Monty unterstützt vollständige moderne Python-Typannotationen und enthält ty in einer einzigen Binärdatei, um die Typprüfung auszuführen
  • An externen Funktionsaufrufen in Bytes gesnapshotet werden, sodass Sie den Interpreterzustand in einer Datei oder Datenbank speichern und später fortsetzen können
  • Äußerst schnell starten (<1μs vom Code zum Ausführungsergebnis), und die Laufzeitleistung ist ähnlich wie bei CPython (in der Regel zwischen 5x schneller und 5x langsamer)
  • Aus Rust, Python oder Javascript aufrufbar sein – da Monty keine Abhängigkeiten von CPython hat, können Sie es überall dort einsetzen, wo Sie Rust ausführen können
  • Ressourcennutzung kontrollieren – Monty kann Speichernutzung, Stacktiefe und Ausführungszeit verfolgen und die Ausführung abbrechen, wenn sie voreingestellte Grenzwerte überschreitet
  • stdout und stderr sammeln und an den Aufrufer zurückgeben
  • Async- oder Sync-Code auf dem Host über Async- oder Sync-Code auf dem Host ausführen
  • Eine kleine Teilmenge der Standardbibliothek verwenden: sys, os, typing, asyncio, re, datetime, json, dataclasses (bald)

Was Monty nicht kann:

  • Den Rest der Standardbibliothek verwenden
  • Drittanbieterbibliotheken (wie Pydantic) verwenden; die Unterstützung externer Python-Bibliotheken ist kein Ziel
  • Klassen definieren (Unterstützung sollte bald kommen)
  • match-Anweisungen verwenden (auch hier sollte die Unterstützung bald kommen)

Kurz gesagt, Monty ist extrem begrenzt und für einen Anwendungsfall konzipiert:

Um von Agenten geschriebenen Code auszuführen.

Wenn Sie wissen möchten, warum Sie das tun sollten, siehe:

In einfachen Worten: Die Idee hinter all dem ist, dass LLMs schneller, kostengünstiger und zuverlässiger arbeiten können, wenn sie gebeten werden, Python- (oder Javascript-)Code zu schreiben, anstatt sich auf traditionelles Tool Calling zu verlassen. Monty macht das möglich, ohne die Komplexität einer Sandbox oder das Risiko, Code direkt auf dem Host auszuführen.

Hinweis: Monty wird (bald) zur Implementierung von codemode in Pydantic AI verwendet.

Verwendung

Monty kann aus Python, JavaScript/TypeScript oder Rust aufgerufen werden.

Python

Zur Installation:```bash uv add pydantic-monty

(Oder `pip install pydantic-monty` für die Boomer)

`pydantic-monty` ist ein Metapaket, das `pydantic-monty-client` (das
`pydantic_monty`-Modul) mit `pydantic-monty-runtime` (die `monty`-Worker-
Binärdatei) bündelt. Installiere `pydantic-monty-client` allein, wenn die Binärdatei bereits von
anderswo stammt.

Verwendung:```python
from typing import Any

import pydantic_monty

code = """
async def agent(prompt: str, messages: Messages):
    while True:
        print(f'messages so far: {messages}')
        output = await call_llm(prompt, messages)
        if isinstance(output, str):
            return output
        messages.extend(output)

await agent(prompt, [])
"""

type_definitions = """
from typing import Any

Messages = list[dict[str, Any]]

async def call_llm(prompt: str, messages: Messages) -> str | Messages:
    raise NotImplementedError()

prompt: str = ''
"""


Messages = list[dict[str, Any]]


async def call_llm(prompt: str, messages: Messages) -> str | Messages:
    if len(messages) < 2:
        return [{'role': 'system', 'content': 'example response'}]
    else:
        return f'example output, message count {len(messages)}'


async def main():
    async with pydantic_monty.AsyncMonty() as pool:
        async with pool.checkout(
            script_name='agent.py',
            type_check=True,
            type_check_stubs=type_definitions,
        ) as session:
            output = await session.feed_run(
                code,
                inputs={'prompt': 'testing'},
                external_lookup={'call_llm': call_llm},
            )
    print(output)
    #> example output, message count 2


if __name__ == '__main__':
    import asyncio

    asyncio.run(main())

Die Ausführung erfolgt in einem Pool von monty-Worker-Subprozessen, sodass selbst ein Speicherfehler, der durch adversarischen Code ausgelöst wird (Stack-Overflow, Allocator-Abbruch), niemals deinen Prozess zum Absturz bringen kann – der Worker stirbt, wirft MontyCrashedError und wird ersetzt. Es gibt auch eine vollständig synchrone API:```python import pydantic_monty

with pydantic_monty.Monty() as pool: with pool.checkout() as session: # session state persists between feed_run calls session.feed_run('x = 21') print(session.feed_run('x * 2')) #> 42

### JavaScript / TypeScript

Zur Installation:```bash
npm install @pydantic/monty

Das JS-Paket ist eine native (napi) Bindung über denselben Rust-Worker-Pool, den das Python-Paket verwendet — die Bindung und die monty-Worker-Binärdatei werden über plattformspezifische npm-Pakete ausgeliefert:```ts import { Monty } from '@pydantic/monty'

await using pool = await Monty.create() await using session = await pool.checkout()

// session state persists between feedRun calls await session.feedRun('x = 21') console.log(await session.feedRun('x * 2')) // 42

// external functions may be async const result = await session.feedRun('await fetch_data()', { externalLookup: { fetch_data: async () => 'data' }, })

Für Browser (oder überall dort, wo Subprozesse unmöglich sind) stellt dasselbe Paket einen In-Process-WebAssembly-Build unter dem `@pydantic/monty/wasm`-Unterpfad bereit (keine Crash-Isolation: Ein Sandbox-Absturz ist dort ein Host-Absturz).

### Rust

Für die Ausführung von nicht vertrauenswürdigem Code aus Rust empfehlen wir die
[`monty-pool`](https://crates.io/crates/monty-pool) Crate anstelle der unten beschriebenen In-Process-API.
`monty-pool` führt Code nur in `monty`-Worker-Subprozessen aus, was zusätzliche Schutzmaßnahmen bietet:
Ein Absturz, der durch bösartigen Code ausgelöst wird (Stack-Überlauf, Allocator-Abbruch), tötet nur den Worker —
der Pool erkennt den Tod und ersetzt den Worker — und ein Watchdog auf der Elternseite kann Worker beenden,
die ein hartes Timeout überschreiten. Es ist dieselbe Engine, auf der die oben genannten Python- und JavaScript-Pakete
basieren. Siehe [monty-pool README](https://github.com/pydantic/monty/tree/main/crates/monty-pool)
zur Verwendung.

Die `monty`-Crate selbst stellt den In-Process-Interpreter bereit:```rust
use monty::MontyRun;
use monty_types::{CompileOptions, ResourceTracker, MontyObject, PrintWriter, ResourceLimits};

let code = r#"
def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

fib(x)
"#;

let runner = MontyRun::new(code.to_owned(), "fib.py", vec!["x".to_owned()], CompileOptions::default()).unwrap();
let result = runner.run(vec![MontyObject::Int(10)], ResourceTracker::default(), PrintWriter::Stdout).unwrap();
assert_eq!(result, MontyObject::Int(55));

Serialisierung

Eine REPL-Sitzung kann mit dump() serialisiert und mit Dump::load() wiederhergestellt werden. Der Dump enthält die Sitzungsmetadaten (Skriptname, Typprüfungs-Stubs) zusammen mit dem Interpreterzustand, hinter einer Version, die der ladende Build prüft:```rust use monty::{Dump, MontyRepl, Session, SessionRef, dump}; use monty_types::{CompileOptions, MontyObject, PrintWriter, ResourceTracker};

// Snapshot a session between snippets let mut repl = MontyRepl::new("main.py", ResourceTracker::default(), CompileOptions::default()); repl.feed_run("x = 41", vec![], PrintWriter::Stdout).unwrap(); let bytes = dump("main.py", None, SessionRef::Idle(&repl)).unwrap();

// Later, restore and carry on feeding let Session::Idle(mut restored) = Dump::load(&bytes).unwrap().state else { panic!("dumped an idle session") }; let result = restored.feed_run("x + 1", vec![], PrintWriter::Stdout).unwrap(); assert_eq!(result, MontyObject::Int(42));

`MontyRun` und `RunProgress` haben kein eigenes Dump-Format, aber beide implementieren `serde::Serialize`/`Deserialize`, sodass ein Host geparsten Code oder einen pausierten Lauf mit dem Format serialisieren kann, das er bereits verwendet.

## Speichergrenzen in Workern

Das `max_memory` einer Session wird vom Allocator des Workers gemessen. Der Interpreter
meldet einen sauberen `MemoryError`, nachdem die weiche Grenze überschritten wurde; eine höhere harte
Grenze beendet und ersetzt den Worker, wenn eine einzelne Allokation zwischen Checkpoints zu weit springt.

Siehe [`limitations/resource_limits.md`](https://github.com/pydantic/monty/blob/HEAD/limitations/resource_limits.md) dafür, wie
sich das Überschreiten einer Grenze für einen Host bemerkbar macht, und `monty-alloc` für den Allocator, unter dem sowohl
die Subprocess- als auch die WebAssembly-Worker laufen.

## PydanticAI-Integration

Monty wird den Code-Modus in
[Pydantic AI](https://github.com/pydantic/pydantic-ai) antreiben. Anstatt
sequenzielle Tool-Aufrufe durchzuführen, schreibt das LLM Python-Code, der Ihre Tools
als Funktionen aufruft, und Monty führt ihn sicher aus.```python test="skip"
import asyncio
import json

import logfire
from httpx import AsyncClient
from pydantic_ai import Agent, RunContext
from pydantic_ai.toolsets.code_mode import CodeModeToolset
from pydantic_ai.toolsets.function import FunctionToolset
from typing_extensions import TypedDict

logfire.configure()
logfire.instrument_pydantic_ai()


class LatLng(TypedDict):
    lat: float
    lng: float


weather_toolset: FunctionToolset[AsyncClient] = FunctionToolset()


@weather_toolset.tool
async def get_lat_lng(
    ctx: RunContext[AsyncClient], location_description: str
) -> LatLng:
    """Get the latitude and longitude of a location."""
    # NOTE: the response here will be random, and is not related to the location description.
    r = await ctx.deps.get(
        'https://demo-endpoints.pydantic.workers.dev/latlng',
        params={'location': location_description},
    )
    r.raise_for_status()
    return json.loads(r.content)


@weather_toolset.tool
async def get_temp(ctx: RunContext[AsyncClient], lat: float, lng: float) -> float:
    """Get the temp at a location."""
    # NOTE: the responses here will be random, and are not related to the lat and lng.
    r = await ctx.deps.get(
        'https://demo-endpoints.pydantic.workers.dev/number',
        params={'min': 10, 'max': 30},
    )
    r.raise_for_status()
    return float(r.text)


@weather_toolset.tool
async def get_weather_description(
    ctx: RunContext[AsyncClient], lat: float, lng: float
) -> str:
    """Get the weather description at a location."""
    # NOTE: the responses here will be random, and are not related to the lat and lng.
    r = await ctx.deps.get(
        'https://demo-endpoints.pydantic.workers.dev/weather',
        params={'lat': lat, 'lng': lng},
    )
    r.raise_for_status()
    return r.text


agent = Agent(
    'gateway/anthropic:claude-sonnet-4-5',
    # toolsets=[weather_toolset],
    toolsets=[CodeModeToolset(weather_toolset)],
    deps_type=AsyncClient,
)


async def main():
    async with AsyncClient() as client:
        await agent.run('Compare the weather of London, Paris, and Tokyo.', deps=client)


if __name__ == '__main__':
    asyncio.run(main())

Community-Anbindungen

  • Go: gomonty - Go-Anbindungen für den Monty-Interpreter
  • Dart/Flutter: dart_monty (github) (pub.dev)- Dart/Flutter-Anbindungen für Monty

Alternativen

Es gibt im Allgemeinen zwei Reaktionen, wenn man Menschen Monty zeigt:

  1. Oh mein Gott, das löst so viele Probleme, das will ich.
  2. Warum nicht X?

Wobei X für eine alternative Technologie steht. Seltsamerweise werden diese Reaktionen oft kombiniert, was darauf hindeutet, dass die Leute noch keine Alternative gefunden haben, die für sie funktioniert, aber ungläubig sind, dass es wirklich keine gute Alternative dazu gibt, eine komplette Python-Implementierung von Grund auf neu zu erstellen.

Ich werde versuchen, die offensichtlichsten Alternativen durchzugehen und zu erklären, warum sie für das, was wir wollten, nicht das Richtige sind.

HINWEIS: Alle diese Technologien sind beeindruckend und haben weit verbreitete Anwendungen; diese Anmerkungen zu ihren Einschränkungen für unseren Anwendungsfall sollten nicht als Kritik verstanden werden. Die meisten dieser Lösungen wurden nicht mit dem Ziel entwickelt, eine LLM-Sandbox bereitzustellen, weshalb sie darin nicht unbedingt großartig sind.

TechLanguage completenessSecurityStart latencyFOSSSetup complexityFile mountingSnapshotting
Montyteilweisestreng0.06mskostenlos / OSSeinfacheinfacheinfach
Dockervollständiggut195mskostenlos / OSSmitteleinfachmittel
Pyodidevollständigschlecht2800mskostenlos / OSSmitteleinfachschwierig
starlark-rustsehr eingeschränktgut1.7mskostenlos / OSSeinfachnicht verfügbar?unmöglich?
WASI / Wasmerteilweise, fast vollständigstreng66mskostenlos *mitteleinfachmittel
Sandboxing-Dienstvollständigstreng1033msnicht kostenlosmittelschwierigmittel
YOLO Pythonvollständignicht vorhanden0.1ms / 30mskostenlos / OSSeinfacheinfach / beängstigendschwierig

Siehe ./scripts/startup_performance.py für das Skript, mit dem die Startleistungszahlen berechnet wurden.

Details zu jeder Zeile unten:

Monty

  • Sprachvollständigkeit: Noch keine Klassen, eingeschränkte Standardbibliothek, keine Drittanbieter-Bibliotheken
  • Sicherheit: Explizit kontrollierter Zugriff auf Dateisystem, Netzwerk und Umgebung, strenge Grenzen für Ausführungszeit und Speichernutzung
  • Startlatenz: Startet in Mikrosekunden
  • Einrichtungsaufwand: einfach pip install pydantic-monty oder npm install @pydantic/monty, ~4.5MB Download
  • Dateimounting: Streng kontrolliert, siehe #85
  • Snapshotting: Montys Pause- und Fortsetzungsfunktion mit dump() und load() macht es trivial, die Ausführung zu pausieren, fortzusetzen und zu forken

Docker

  • Sprachvollständigkeit: Vollständiges CPython mit jeder Bibliothek
  • Sicherheit: Prozess- und Dateisystem-Isolierung, Netzwerkrichtlinien, aber Container-Escapes existieren, Speicherbegrenzung ist möglich
  • Startlatenz: Container-Startoverhead (~195ms gemessen)
  • Einrichtungsaufwand: Erfordert Docker-Daemon, Container-Images, Orchestrierung - python:3.14-alpine ist 50MB groß - Docker kann nicht von PyPI installiert werden
  • Dateimounting: Volume-Mounts funktionieren gut
  • Snapshotting: Möglich mit Durable-Execution-Lösungen wie Temporal oder durch Erstellen eines Snapshots eines Images und Speichern als Docker-Image.

Pyodide

  • Sprachvollständigkeit: Vollständiges CPython, zu WASM kompiliert, fast alle Bibliotheken verfügbar
  • Sicherheit: Verlässt sich auf die Browser-/WASM-Sandbox - nicht für serverseitige Isolierung gedacht - Python-Code kann beliebigen Code in der JS-Laufzeit ausführen, nur deno ermöglicht Isolierung, Speichergrenzen sind mit deno schwer/unmöglich durchzusetzen
  • Startlatenz: Das Laden der WASM-Laufzeit ist langsam (~2800ms Kaltstart)
  • Einrichtungsaufwand: Man muss die WASM-Laufzeit laden und die asynchrone Initialisierung handhaben - das pyodide-NPM-Paket ist ~12MB, deno ist ~50MB - Pyodide kann nicht nur mit PyPI-Paketen genutzt werden
  • Dateimounting: Virtuelles Dateisystem über Browser-APIs
  • Snapshotting: Vermutlich möglich mit Durable-Execution-Lösungen wie Temporal, aber schwierig

starlark-rust

Siehe starlark-rust.

  • Sprachvollständigkeit: Konfigurationssprache, kein Python - keine Klassen, Ausnahmen, async
  • Sicherheit: Deterministisch und hermetisch per Design
  • Startlatenz: Läuft eingebettet im Prozess wie Monty, daher beeindruckende Startzeit
  • Einrichtungsaufwand: In Python nutzbar über starlark-pyo3
  • Dateimounting: Keine Dateibehandlung per Design, soweit ich weiß (AFAIK)?
  • Snapshotting: Unmöglich, soweit ich weiß (AFAIK)?

WASI / Wasmer

Python in WebAssembly über Wasmer ausführen.

  • Sprachvollständigkeit: Vollständiges CPython - reine Python-Externpakete funktionieren über Mounting, Externpakete mit C-Bindings funktionieren nicht
  • Sicherheit: Prinzipiell sollte WebAssembly starke Sandbox-Garantien bieten.
  • Startlatenz: Das wasmer-Python-Paket wurde seit 3 Jahren nicht aktualisiert, und ich konnte keine Dokumentation zum Aufrufen von Python in wasmer aus Python heraus finden, also habe ich es über einen Unterprozess (subprocess) aufgerufen. Die Startlatenz betrug 66ms.
  • Einrichtungsaufwand: Der wasmer-Download ist 100mb groß, das „python/python"-Paket ist 50mb.
  • FOSS: Ich habe dies als „kostenlos *" markiert, da die Kosten bei null liegen, aber nicht alles scheint Open Source zu sein. Stand 2026-02-10 hat das python/python-wasmer-Paket keine README, keine Lizenz, keinen Quellcode-Link und keinen Hinweis darauf, wie es erstellt wird - die kürzlich hochgeladenen Versionen zeigen die Größe als „0B" an, obwohl der Download ~50MB beträgt - der Build-Prozess für die Python-Binärdatei ist nicht klar und transparent. (Falls ich hier falsch liege, erstellt bitte ein Issue, um mich zu korrigieren)
  • Dateimounting: Unterstützt
  • Snapshotting: Unterstützt über Journaling

Sandboxing-Dienst

Dienste wie Daytona, E2B, Modal.

Es gibt ähnliche Herausforderungen, mehr Einrichtungsaufwand, aber geringere Netzwerklatenz, wenn man sein eigenes Sandbox-Setup mit k8s aufsetzt.

  • Sprachvollständigkeit: Vollständiges CPython mit jeder Bibliothek
  • Sicherheit: Professionell verwaltete Container-Isolierung
  • Startlatenz: Netzwerk-Roundtrip und Container-Startzeit. Ich habe mit Daytona EU von London aus eine Kaltstartzeit von ~1s erreicht - Daytona bewirbt eine Latenz von unter 90ms, vermutlich für einen bestehenden Container, unklar, ob die Netzwerklatenz enthalten ist
  • FOSS: Bezahlung pro Ausführung oder Rechenzeit, einige Implementierungen sind Open Source
  • Einrichtungsaufwand: API-Integration, Authentifizierungs-Tokens - für Startups in Ordnung, aber für Unternehmen in der Regel ein No-Go
  • Dateimounting: Hoch-/Herunterladen über API-Aufrufe
  • Snapshotting: Möglich mit Durable-Execution-Lösungen wie Temporal - die Dienste bieten auch einige Lösungen dafür an, ich glaube auf Basis von Docker-Containern

YOLO Python

Python direkt über exec() (~0.1ms) oder Unterprozess (~30ms) ausführen.

  • Sprachvollständigkeit: Vollständiges CPython mit jeder Bibliothek
  • Sicherheit: Keine - voller Zugriff auf Dateisystem, Netzwerk, Umgebungsvariablen, Systembefehle
  • Startlatenz: Nahezu null für exec(), ~30ms für Unterprozesse
  • Einrichtungsaufwand: Keiner
  • Dateimounting: Direkter Dateisystemzugriff (das ist das Problem)
  • Snapshotting: Möglich mit Durable-Execution-Lösungen wie Temporal

Teil des Pydantic-Stacks

Der Pydantic-Stack ist alles, was du brauchst, um produktionsreife KI-Agenten auszuliefern:

Kategorien