> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mirage.strukto.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Python

> Choose the interpreter that runs python3 inside the workspace, monty (sandboxed, default), wasi (sandboxed full CPython), sandlock (the host interpreter, confined), or the host interpreter.

Shell lines like `python3 script.py` need an interpreter. Mirage calls that
interpreter a **runtime**, and you pick it per workspace. Four runtimes
ship today:

| Runtime                                        | Engine                                                                                                                          | Filesystem                                                                    | Default |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------- |
| `monty`                                        | [Pydantic Monty](https://github.com/pydantic/monty), a sandboxed Python interpreter written in Rust                             | Workspace mounts only, bridged through Mirage                                 | Yes     |
| `wasi`                                         | Full CPython compiled to WebAssembly, run in-process on [wasmtime](https://wasmtime.dev)                                        | Workspace mounts only, bridged through Mirage; no host filesystem, no network | No      |
| [`sandlock`](/python/runtime/sandbox/sandlock) | The host `python` interpreter, confined by [sandlock](https://github.com/multikernel/sandlock) (Landlock + seccomp). Linux only | Only the host paths the config grants                                         | No      |
| `local`                                        | The host `python` interpreter, as a subprocess                                                                                  | The host filesystem                                                           | No      |

The first two bridge file I/O through the workspace, so code sees your
mounts and nothing else. The last two run real CPython on the host and see
the host filesystem instead — `sandlock` bounded to the paths you name,
`local` unbounded.

## Monty (default)

Monty is a minimal, secure Python interpreter built for running
AI-generated code: microsecond startup, no host filesystem, environment,
or network access. Mirage wires Monty's OS interface to the workspace
dispatch, so code running under `python3` sees the workspace mounts, and
nothing else:

```python theme={null}
from mirage import MountMode, Workspace
from mirage.resource.ram import RAMResource

ws = Workspace({"/data": RAMResource()}, mode=MountMode.EXEC)
await ws.execute("echo hello > /data/a.txt")

# Reads a virtualized file; /etc/passwd would raise FileNotFoundError.
io = await ws.execute(
    "python3 -c \"print(open('/data/a.txt').read().strip())\"")
```

Reads, writes, `pathlib` traversal, and `os.environ` all route through the
mounts. A `python3` write is immediately visible to `cat`, and works the
same whether the mount is RAM, Redis, S3, or a virtual backend like
MongoDB's `database.json`.

Monty requires the `monty` extra:

```bash theme={null}
pip install mirage-ai[monty]
```

### Concurrency

Runs execute on Monty's own worker pool and are fully async: the event
loop stays free, and cancelling a run halts the interpreter. One
ceiling to know about when a server runs many I/O-heavy scripts at
once: each run waiting on workspace I/O holds one pool worker (the
Python binding only takes synchronous OS callbacks,
[pydantic/monty#560](https://github.com/pydantic/monty/issues/560)),
and the pool defaults to the machine's core count. Set
`TOKIO_WORKER_THREADS` to lift it; waiting workers cost kilobytes of
stack and no CPU, so 100 parallel I/O-bound scripts on one core is a
`TOKIO_WORKER_THREADS=100` away.

Without it, `python3` exits with code 127 and an install hint; every other
command keeps working.

### Differences from CPython

Monty implements a Python subset. Notably:

* Command-line arguments are the `argv` global (`argv[0]` is the script
  name); `sys.argv` does not exist.
* No `sys.stdin`: piping into `python3` needs the `wasi` or `local` runtime.
* No third-party imports (`numpy`, `requests`, ...).
* File handles are not iterable (`for line in f` raises `TypeError`);
  `f.read()`, `f.readline()`, and `f.readlines()` all work.
* `os.environ` reflects the session env only, never the host's.

## WASI

The `wasi` runtime runs a real CPython, compiled to WebAssembly, inside
your process on wasmtime. It fills monty's gaps (classes, the complete
stdlib, `sys.argv`, `sys.stdin`) while staying sandboxed: host files
and network sockets are invisible, and the run sees the interpreter's
own build directory plus the workspace mounts.

Mirage intercepts the sandbox's filesystem calls and bridges them
through the workspace dispatch, so `open('/data/f.txt')` inside the
code reads and writes the mount live — RAM, Redis, S3, or a virtual
backend — with the same cache, write modes, and per-session mount
narrowing as `cat`. Guest writes are immediately visible to every
command, a read-only mount (or a session narrowed to read) answers
`PermissionError` at `open()`, and none of this needs FUSE, a config
key, or any setup. The interpreter's own build directory is served
read-only from the host; a mount at `/` coexists with it (mount
prefixes win for their subtrees, the build serves the rest).

It needs the `wasi` extra plus a CPython WASI build (download and
unzip a release from
[cpython-wasi-build](https://github.com/brettcannon/cpython-wasi-build/releases)):

```bash theme={null}
pip install mirage-ai[wasi]
```

Point mirage at the build directory with the config `home` on the
runtime entry (or the MIRAGE\_WASI\_HOME environment variable):

```python theme={null}
from mirage.runtime.python import WasiRuntime

ws = Workspace({"/data": RAMResource()},
               mode=MountMode.EXEC,
               runtimes=[WasiRuntime(config={"home": "/path/to/unzipped/build"}), "vfs"])
```

```yaml theme={null}
runtimes:
  - name: wasi
    config:
      home: /path/to/unzipped/build
  - vfs
```

The first run compiles `python.wasm` (a few hundred milliseconds) and
caches the compilation as `python.cwasm` next to it; runs after that
boot in milliseconds. The build directory is read-only inside the
sandbox: guest writes land in the workspace or answer `PermissionError`,
never in the bundle.

## Local

The `local` runtime runs a host interpreter as a subprocess: full
CPython, host filesystem, host environment. Use it when the code needs
stdin, third-party packages, or host files:

```python theme={null}
ws = Workspace({"/data": RAMResource()},
               mode=MountMode.EXEC,
               runtimes=["local", "vfs"])
```

It defaults to the interpreter running mirage; point the config
`home` on the runtime entry (or the MIRAGE\_LOCAL\_HOME environment
variable) at another binary, e.g. a project venv whose packages the
code needs:

```yaml theme={null}
runtimes:
  - name: local
    config:
      home: /path/to/venv/bin/python
  - vfs
```

Note that `local` code does **not** see workspace mounts; it sees the real
host filesystem.

For setup and its host-path security model, see the dedicated
[Sandlock page](/python/runtime/sandbox/sandlock) under **Sandbox**.

## Selecting in YAML

Workspace config files take a top-level `runtimes` list: the ordered
world of runtimes, each entry a name or a mapping with the uniform
runtime options (`captures`, `config`, `script`). The first entry that
captures a command binds it; `vfs` is the in-process catch-all:

```yaml theme={null}
runtimes:
  - monty         # or: wasi, local
  - name: wasi    # knobs live in the entry's config block
    config:
      home: /opt/cpython-wasi
  - vfs

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

Each runtime can have its own option block under its name. The `home`
option locates the runtime's interpreter or distribution, in the
spirit of JAVA\_HOME: a build directory for `wasi`, an interpreter path
for `local`, a distribution URL for `pyodide` (TypeScript); it also
falls back to a MIRAGE\_\<RUNTIME>\_HOME environment variable. Only
the selected runtime consumes its block and it rejects option keys it
does not know, so one config stays portable across runtimes and
languages. `monty` embeds its interpreter and has no options yet.

## Resource limits

`python3` is a command like any other: the same `command_limits`
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.

Firing the guard also cancels the run, and every runtime honors
cancellation: monty kills its worker process, wasi traps the run
through wasmtime's epoch interruption, and local kills the
subprocess. A timed-out `python3` run is reclaimed at the deadline,
not left burning in the background.
