---
title: Python
description: Choose the interpreter that runs python3 inside the workspace, pyodide (default) or the sandboxed monty runtime.
icon: /images/pydantic-logo.svg
---

Shell lines like `python3 script.py` need an interpreter. Mirage calls that
interpreter a **runtime**, and you pick it per workspace. The TypeScript
packages ship two:

| Runtime | Engine | Filesystem | Default |
| --- | --- | --- | --- |
| `pyodide` | CPython compiled to WebAssembly ([Pyodide](https://pyodide.org)) | Workspace mounts, preloaded into the WASM filesystem and lazily backfilled | Yes |
| `monty` | [Pydantic Monty](https://github.com/pydantic/monty), a sandboxed Python interpreter written in Rust | Workspace mounts only, bridged through Mirage | No |

## Pyodide (default)

Pyodide is full CPython in WebAssembly: real stdlib, `sys.argv`, stdin,
and auto-loaded scientific packages. Mirage mirrors the workspace mounts
into Pyodide's in-memory filesystem, so `open()` and `os.listdir` work on
mounted paths.

```ts
import { MountMode, RAMResource, Workspace } from '@struktoai/mirage-node'

const ws = new Workspace({ '/data': new RAMResource() }, { mode: MountMode.EXEC })
await ws.execute('echo hello > /data/a.txt')
const io = await ws.execute(`python3 -c "print(open('/data/a.txt').read().strip())"`)
```

## Monty

Monty runs each execution in a crash-isolated worker with microsecond
startup and no host filesystem, environment, or network access. `pathlib`
I/O and `os.getenv` route through the workspace mounts:

```ts
const ws = new Workspace(
  { '/data': new RAMResource() },
  { mode: MountMode.EXEC, runtimes: ['monty', 'vfs'] },
)
const io = await ws.execute(
  `python3 -c "from pathlib import Path; print(Path('/data/a.txt').read_text())"`,
)
```

Monty requires the optional `@pydantic/monty` package:

```bash
pnpm add @pydantic/monty
```

Without it, selecting monty makes `python3` exit with code 127 and an
install hint.

### Differences from CPython

- Command-line arguments are the `argv` global; `sys.argv` does not exist.
- The builtin `open()` is not bridged yet in the JS binding; use
  `pathlib` (`Path(...).read_text()`, `.write_text()`, `.iterdir()`).
- No `sys.stdin` and no third-party imports.
- `os.environ` reflects the session env only.

## Selecting in YAML

Server workspace config files take a top-level `runtimes` list:

```yaml
runtimes:
  - monty         # or: pyodide
  - name: pyodide # options live flat on the entry
    home: https://assets.example.com/pyodide/
  - vfs

mounts:
  /data:
    resource: ram
    command_safeguards:
      python3:
        timeout_seconds: 30
```

Options live flat on the runtime entry. The `home` option locates the
runtime's interpreter or distribution, in the spirit of JAVA_HOME. For
`pyodide` that is where the distribution loads from: it defaults to
the installed package in Node and the pinned CDN in the browser; point
it at self-hosted assets to pin or air-gap the runtime (falls back to
the MIRAGE_PYODIDE_HOME environment variable). `monty` embeds its
interpreter and has no options yet. Python-only names (`wasi`,
`local`) fail loud with a cross-language hint. In application code the
entries are the `runtimes` workspace option, instances carrying their
own options:

```ts
const ws = new Workspace(resources, {
  runtimes: [new PyodideRuntime({ home: 'https://assets.example.com/pyodide/' }), 'vfs'],
})
```

## Resource limits

`python3` is a command like any other: the same `command_safeguards`
blocks that guard `cat` or `grep` guard it, enforced at the same
central point. A run that exceeds `timeout_seconds` answers with exit
124 and `python3: timed out after Ns` on stderr, exactly like any
other command; `max_bytes` and `max_lines` cap its output the same
way. There is no python3-specific limit surface.

One caveat: the guard bounds how long a run may *answer*, not the
interpreter itself. A run that ignores the deadline keeps its
interpreter busy in the background until it finishes (monty runs on a
crash-isolated worker; pyodide shares the event loop, so prefer monty
for untrusted code). Give untrusted code a finite workload, or run it
behind a sandboxed deployment.
