live on the edge — Python 3 · Elixir · WasmGC

A Python interpreter you can trust with untrusted code

pyex is a Python 3 interpreter written in Elixir, compiled to WebAssembly GC — built for running LLM-generated code. No processes, no ambient authority: effects are capabilities you hand in, execution is step-budgeted and deterministic, and every run returns an OpenTelemetry trace of what it did.

live — runs at the edge via POST /api/run ⌘↵

        
a fresh sandbox per request · step-budgeted
6,500+ tests differentially fuzzed vs CPython 0 processes spawned deterministic under a seed

sandboxed by construction

Not a jail bolted on.
An interpreter that can't reach out.

Isolation isn't a wrapper around the interpreter — it's the shape of the interpreter. There is no path to the host that you didn't hand in as a value.

Capability effects

Files, network, storage, and clocks exist only if the host passes them in — attenuate them, or swap in copy-on-write overlays that stage every effect for review before a commit.

Hard budgets

Steps, memory, and output are metered. A runaway while True dies with a clean Python error — never a hung host, never an OOM.

Deterministic

Seeded runs are byte-for-byte reproducible. The run you previewed is the run that commits — no time-of-check/time-of-use gap.

Observable

Every run returns a resource footprint and OpenTelemetry spans — including spans your Python emits itself with tracer.start_as_current_span.

one interpreter, three homes

Embed it in Elixir. Ship it to a browser.
Call it over HTTP.

One codebase, compiled two ways. The wasm running in this page's Cloudflare Worker is the same artifact the playground runs in your browser.

# Elixir — the source of truth (hex.pm/packages/pyex)
{:ok, result, ctx} = Pyex.run(code, filesystem: fs, limits: [max_steps: 5_000_000])

# HTTP — Python at the edge, JSON out; spans + footprint ride along
curl -s https://pyex.dev/api/run \
  -H 'content-type: application/json' \
  -d '{"code": "print(sum(range(101)))", "files": {"/data.json": "[1,2,3]"}}'
{"ok":true,"ms":0,"stdout":"5050\n","footprint":{":steps":1,…},"files":{…},"spans":[…]}