{"slug": "monty-go-pure-go-wrapper-for-pydantic-s-monty-python-interpreter", "title": "monty-go: Pure-Go wrapper for Pydantic's Monty Python Interpreter", "summary": "Fugue Labs released monty-go, a pure-Go wrapper for Pydantic's Monty Python interpreter that runs LLM-generated Python code safely in a WebAssembly sandbox via wazero, with sub-millisecond startup and no containers, CGO, or subprocesses. The library, available via `go get github.com/fugue-labs/monty-go`, embeds a 2.9MB WASM binary and supports external function calls, allowing Go agents to handle pauses when Python code invokes declared functions, plus configurable limits for duration, memory, allocations, and recursion depth.", "body_md": "**Run LLM-generated Python safely from Go — no containers, no CGO, no subprocess.**\n\nA pure-Go wrapper around [Pydantic's Monty](https://github.com/pydantic/monty) Python interpreter, compiled to WebAssembly and loaded via [wazero](https://wazero.io). Your Go agent writes Python code, monty-go executes it in a sandboxed WASM instance with sub-millisecond startup, and pauses whenever the code calls an external function so your Go code can handle it.\n\n```\ngo get github.com/fugue-labs/monty-go\n```\n\nLLMs work faster, cheaper, and more reliably when they write code instead of making sequential tool calls. Instead of:\n\n```\nAgent → tool_call(\"search\", {query: \"weather london\"}) → result\nAgent → tool_call(\"search\", {query: \"weather tokyo\"})  → result\nAgent → tool_call(\"compare\", {a: result1, b: result2}) → result\n```\n\nThe LLM writes:\n\n```\nlondon = search(query=\"weather london\")\ntokyo = search(query=\"weather tokyo\")\ncompare(a=london, b=tokyo)\n```\n\nOne model call instead of three. The Python code calls your Go functions, Monty pauses at each call, your Go code executes it, and Monty resumes. No containers. No sandbox services. No `exec()`\n\n. Just a 2.9MB WASM binary embedded in your Go binary.\n\nFor motivation, see:\n\n[Programmatic Tool Calling](https://platform.claude.com/docs/en/agents-and-tools/tool-use/programmatic-tool-calling)from Anthropic[Code Execution with MCP](https://www.anthropic.com/engineering/code-execution-with-mcp)from Anthropic[Code Mode](https://blog.cloudflare.com/code-mode/)from Cloudflare[Smol Agents](https://github.com/huggingface/smolagents)from Hugging Face\n\n```\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"log\"\n\n    montygo \"github.com/fugue-labs/monty-go\"\n)\n\nfunc main() {\n    runner, err := montygo.New()\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer runner.Close()\n\n    result, err := runner.Execute(context.Background(),\n        \"x * 2 + y\",\n        map[string]any{\"x\": 10, \"y\": 5},\n    )\n    if err != nil {\n        log.Fatal(err)\n    }\n    fmt.Println(result) // 25\n}\n```\n\nThe real power is external function calls. Monty pauses execution whenever Python code calls a function you've declared, your Go callback handles it, and Monty resumes with the return value:\n\n```\nresult, err := runner.Execute(ctx,\n    `\nlondon = get_weather(\"London\")\ntokyo = get_weather(\"Tokyo\")\nf\"{london['city']}: {london['temp']}°C, {tokyo['city']}: {tokyo['temp']}°C\"\n    `,\n    nil,\n    montygo.WithExternalFunc(func(ctx context.Context, call *montygo.FunctionCall) (any, error) {\n        city, _ := call.Args[\"city\"].(string)\n        // Your real implementation here — HTTP call, database query, anything.\n        return map[string]any{\"city\": city, \"temp\": 22}, nil\n    }, montygo.Func(\"get_weather\", \"city\")),\n)\n// result: \"London: 22°C, Tokyo: 22°C\"\n```\n\nMultiple functions work the same way — register them all and dispatch by name:\n\n```\nresult, err := runner.Execute(ctx, code, nil,\n    montygo.WithExternalFunc(func(ctx context.Context, call *montygo.FunctionCall) (any, error) {\n        switch call.Name {\n        case \"search\":\n            return doSearch(call.Args)\n        case \"calculate\":\n            return doCalculate(call.Args)\n        case \"store\":\n            return doStore(call.Args)\n        default:\n            return nil, fmt.Errorf(\"unknown function: %s\", call.Name)\n        }\n    },\n        montygo.Func(\"search\", \"query\"),\n        montygo.Func(\"calculate\", \"expression\"),\n        montygo.Func(\"store\", \"key\", \"value\"),\n    ),\n)\n```\n\nPrevent runaway code with memory, time, allocation, and recursion limits:\n\n```\nresult, err := runner.Execute(ctx, code, inputs,\n    montygo.WithLimits(montygo.Limits{\n        MaxDuration:       5 * time.Second,\n        MaxMemoryBytes:    10 * 1024 * 1024, // 10 MB\n        MaxAllocations:    100000,\n        MaxRecursionDepth: 100,\n    }),\n)\n```\n\nInfinite loops, memory bombs, and deep recursion all terminate cleanly with a `*MontyError`\n\n. Go's `context.Context`\n\ndeadlines are also respected — cancel the context and the WASM instance stops.\n\nCapture Python `print()`\n\noutput:\n\n``` js\nvar output strings.Builder\n_, err := runner.Execute(ctx, `print(\"step 1 done\")`, nil,\n    montygo.WithPrintFunc(func(s string) { output.WriteString(s) }),\n)\nfmt.Print(output.String()) // \"step 1 done\\n\"\n```\n\nPython filesystem and environment access routes through your Go callback:\n\n``` python\nresult, err := runner.Execute(ctx,\n    `\nfrom pathlib import Path\ndata = Path(\"/config/settings.json\").read_text()\ndata\n    `,\n    nil,\n    montygo.WithOsCallFunc(func(ctx context.Context, call *montygo.OsCall) (any, error) {\n        switch call.Function {\n        case \"Path.read_text\":\n            path, _ := call.Args[0].(string)\n            return readFromYourStorage(path)\n        case \"Path.exists\":\n            path, _ := call.Args[0].(string)\n            return existsInYourStorage(path), nil\n        default:\n            return nil, fmt.Errorf(\"blocked: %s\", call.Function)\n        }\n    }),\n)\n```\n\nNo filesystem access happens unless your callback allows it.\n\nmonty-go is designed to power **code-mode** in [Gollem](https://github.com/fugue-labs/gollem), the production agent framework for Go. Instead of sequential tool calls, the LLM writes Python that calls your tools as functions — Monty executes it safely, and Gollem orchestrates the whole thing.\n\nHere's what this looks like with Gollem:\n\n```\nimport (\n    \"github.com/fugue-labs/gollem\"\n    \"github.com/fugue-labs/gollem/provider/anthropic\"\n    montygo \"github.com/fugue-labs/monty-go\"\n)\n\n// Your existing Gollem tools — search, calculate, store, whatever.\nsearchTool := gollem.FuncTool[SearchParams](\"search\", \"Search the knowledge base\", doSearch)\ncalcTool := gollem.FuncTool[CalcParams](\"calculate\", \"Run calculations\", doCalc)\n\n// Create a code-mode tool that wraps your toolset with Monty.\n// The LLM writes Python code, Monty executes it, external function calls\n// route to your Go tools.\ncodeMode := NewCodeModeTool(runner, searchTool, calcTool)\n\nagent := gollem.NewAgent[Analysis](anthropic.New(),\n    gollem.WithTools[Analysis](codeMode),\n    gollem.WithSystemPrompt[Analysis](`You have a code execution tool.\nWrite Python code to call the available functions. Available functions:\n- search(query: str) -> dict: Search the knowledge base\n- calculate(expression: str) -> float: Evaluate math expressions\nWrite code that calls these functions and returns the result.`),\n)\n\nresult, _ := agent.Run(ctx, \"Compare Q3 and Q4 revenue and calculate the growth rate\")\n```\n\nWith one model call, the LLM writes:\n\n```\nq3 = search(query=\"Q3 revenue\")\nq4 = search(query=\"Q4 revenue\")\ngrowth = calculate(expression=f\"({q4['revenue']} - {q3['revenue']}) / {q3['revenue']} * 100\")\n{\"q3\": q3, \"q4\": q4, \"growth_rate\": growth}\n```\n\nMonty pauses three times (two searches, one calculation), your Go functions handle each one, and the final result flows back through Gollem's typed output pipeline. Three tool calls in one LLM round-trip.\n\nWhy Gollem + monty-go:\n\n| Traditional tool calling | Code-mode with monty-go | |\n|---|---|---|\nLLM calls |\nOne per tool use | One for all tools |\nLatency |\nN × model round-trip | 1 × model round-trip + μs execution |\nCost |\nN × input/output tokens | 1 × input/output tokens |\nLogic |\nLLM reasons step by step | LLM writes the logic once |\nControl flow |\nNone (sequential only) | Loops, conditionals, variables |\nError handling |\nLLM must react to each failure | try/except in Python |\nSecurity |\n✅ (tools are Go functions) | ✅ (WASM sandbox + your callbacks) |\n\nGollem gives you compile-time type safety, structured output, guardrails, cost tracking, middleware, and multi-provider support. monty-go gives you secure embedded Python execution. Together, your agents do more work per model call.\n\n** github.com/fugue-labs/gollem** — The production agent framework for Go.\n\n```\n┌─────────────────────────────────────────────────────────┐\n│  Your Go Application                                    │\n│                                                         │\n│  runner, _ := montygo.New()                             │\n│  result, _ := runner.Execute(ctx, code, inputs, opts)   │\n│       │                                                 │\n│       ▼                                                 │\n│  ┌──────────────────────────────────┐                   │\n│  │  wazero (pure Go WASM runtime)  │                    │\n│  │                                 │                    │\n│  │  ┌───────────────────────────┐  │                    │\n│  │  │  monty.wasm (2.9 MB)      │  │  ◄── go:embed      │\n│  │  │  Monty Python Interpreter │  │                    │\n│  │  │  compiled to wasm32-wasi  │  │                    │\n│  │  └──────────┬────────────────┘  │                    │\n│  │             │                   │                    │\n│  │     pause on external call      │                    │\n│  │             │                   │                    │\n│  └─────────────┼───────────────────┘                    │\n│                │                                        │\n│                ▼                                        │\n│  ExternalFunc callback ──► your Go code ──► resume      │\n│  OsCallFunc callback   ──► your Go code ──► resume      │\n│  PrintFunc callback    ──► your Go code                 │\n└─────────────────────────────────────────────────────────┘\n```\n\n**No CGO.** wazero is a pure-Go WebAssembly runtime.**No subprocess.** The WASM binary is embedded via`go:embed`\n\nand compiled once at startup.**Fresh instance per call.** Each`Execute()`\n\ngets an isolated WASM instance. No state leaks between calls.**JSON at the boundary.** All data crossing the Go↔WASM boundary is JSON. Go types map naturally:`int`\n\n→`float64`\n\n,`string`\n\n→`string`\n\n,`bool`\n\n→`bool`\n\n,`nil`\n\n→`None`\n\n,`[]any`\n\n→`list`\n\n,`map[string]any`\n\n→`dict`\n\n.\n\n```\n// Create a reusable runner. Compiles the WASM module once.\nrunner, err := montygo.New()\ndefer runner.Close()\n\n// Execute Python code with inputs and options.\nresult, err := runner.Execute(ctx, code, inputs, opts...)\n\n// Options:\nmontygo.WithExternalFunc(fn,                     // register callable functions\n    montygo.Func(\"search\", \"query\", \"limit\"),    // with named parameters\n    montygo.Func(\"calculate\", \"expression\"),\n)\nmontygo.WithOsCallFunc(fn)                       // handle filesystem/env access\nmontygo.WithLimits(montygo.Limits{...})          // resource limits\nmontygo.WithPrintFunc(fn)                        // capture print output\n\n// FunctionCall provides named args (positional mapped by param name):\ncall.Args[\"query\"].(string)    // access by parameter name\ncall.ArgsJSON()                // pre-serialized JSON string\n```\n\n| Python | Go (result) | Go (input) |\n|---|---|---|\n`int` |\n`float64` |\n`int` , `float64` |\n`float` |\n`float64` |\n`float64` |\n`str` |\n`string` |\n`string` |\n`bool` |\n`bool` |\n`bool` |\n`None` |\n`nil` |\n`nil` |\n`list` , `tuple` |\n`[]any` |\n`[]any` |\n`dict` |\n`map[string]any` |\n`map[string]any` |\n`set` |\n`[]any` |\n— |\n\nPython exceptions become `*montygo.MontyError`\n\n:\n\n``` js\nresult, err := runner.Execute(ctx, \"1 / 0\", nil)\nvar me *montygo.MontyError\nif errors.As(err, &me) {\n    fmt.Println(me.Message) // \"Traceback... ZeroDivisionError: division by zero\"\n}\n```\n\nTracks upstream [Monty v0.0.11](https://github.com/pydantic/monty/releases/tag/v0.0.11).\n\n- Arithmetic, string operations, f-strings, slicing\n- Functions, lambdas, closures, generators\n`for`\n\n/`while`\n\nloops,`if`\n\n/`elif`\n\n/`else`\n\n,`break`\n\n/`continue`\n\n`try`\n\n/`except`\n\n/`finally`\n\n/`else`\n\n,`raise`\n\n, exception hierarchy- List/dict/set comprehensions, dict/set view operators\n`range`\n\n,`len`\n\n,`sum`\n\n,`min`\n\n,`max`\n\n,`sorted`\n\n,`reversed`\n\n,`enumerate`\n\n,`zip`\n\n,`map`\n\n,`filter`\n\n,`all`\n\n,`any`\n\n,`getattr`\n\n`isinstance`\n\n,`type`\n\n,`int()`\n\n,`float()`\n\n,`str()`\n\n,`bool()`\n\n,`abs()`\n\n`print()`\n\nwith`sep`\n\nand`end`\n\nkwargs- PEP 448 generalized unpacking (\n`*args`\n\n,`**kwargs`\n\nin calls, literals, etc.) - Nested and augmented subscript assignment (\n`a[i][j] = v`\n\n,`a[i] += 1`\n\n) - Tuple comparison (\n`<`\n\n,`>`\n\n,`<=`\n\n,`>=`\n\n) - Multi-module imports (\n`import a, b, c`\n\n) - Stdlib modules:\n`math`\n\n(all functions),`re`\n\n,`datetime`\n\n,`json`\n\n, and`sys`\n\n/`typing`\n\n/`asyncio`\n\nsubsets `import os`\n\n,`from pathlib import Path`\n\n(routed through OsCallFunc)- Dataclass\n*instances*flow through external function calls (args, returns, and method calls surface with`method_call=true`\n\n) - Resource limits: time, memory, allocations, recursion depth\n\n- Class definitions (only dataclass instances via external I/O; upstream Monty flags class\n`def`\n\nas \"coming soon\") `match`\n\nstatements (coming soon upstream)- Context managers (\n`with ...`\n\n) - Rest of stdlib and all third-party libraries\n`float('inf')`\n\n/`float('nan')`\n\n(JSON serialization limitation in this bridge)\n\n97 end-to-end tests covering every testable scenario from Monty's core test suite:\n\n```\nmake test\n```\n\nCovers: basic expressions, print variants, all exception types, data type round-tripping, external functions (args, kwargs, mixed, complex types, chaining, loops), input handling and scoping, resource limits (timeout, recursion, memory, allocations), OS calls, builtins, control flow, lambdas/closures, and execution isolation.\n\nRequires Rust with `wasm32-wasip1`\n\ntarget and Go 1.23+:\n\n```\nrustup target add wasm32-wasip1\nmake build  # compiles Rust → WASM, copies to monty.wasm\nmake test   # builds and runs Go tests\n```\n\nmonty-go exists because of [Monty](https://github.com/pydantic/monty), created by [Samuel Colvin](https://github.com/samuelcolvin) and the [Pydantic](https://github.com/pydantic) team. Monty is a genuinely novel piece of engineering — a minimal, secure Python interpreter written from scratch in Rust, purpose-built for AI agents. The insight that LLMs should write code instead of making sequential tool calls, and that you need a safe interpreter (not a container) to execute it, is what makes code-mode possible.\n\nSamuel and the Pydantic team have a track record of building foundational tools that the whole ecosystem builds on — [Pydantic](https://github.com/pydantic/pydantic), [Pydantic AI](https://github.com/pydantic/pydantic-ai), [Logfire](https://github.com/pydantic/logfire), and now Monty. This project is a Go bridge to their work, and we're grateful they built it.\n\nMIT", "url": "https://wpnews.pro/news/monty-go-pure-go-wrapper-for-pydantic-s-monty-python-interpreter", "canonical_source": "https://github.com/fugue-labs/monty-go", "published_at": "2026-08-30 12:02:09+00:00", "updated_at": "2026-08-30 12:21:46.377827+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-agents"], "entities": ["Fugue Labs", "Pydantic", "Monty", "wazero", "Anthropic", "Cloudflare", "Hugging Face"], "alternates": {"html": "https://wpnews.pro/news/monty-go-pure-go-wrapper-for-pydantic-s-monty-python-interpreter", "markdown": "https://wpnews.pro/news/monty-go-pure-go-wrapper-for-pydantic-s-monty-python-interpreter.md", "text": "https://wpnews.pro/news/monty-go-pure-go-wrapper-for-pydantic-s-monty-python-interpreter.txt", "jsonld": "https://wpnews.pro/news/monty-go-pure-go-wrapper-for-pydantic-s-monty-python-interpreter.jsonld"}}