# Python on SandboxedJs — architecture

The goal is an operating-system compatibility layer with an owned Python
distribution on top of it, not an interpreter swap. Replacing Pyodide with a
differently-built CPython in the same lifecycle would inherit the same problems,
because the problems are not in the interpreter.

## What "our own Python" means here

A **runtime distribution**, not a new language implementation. SandboxedJs owns
the OS-facing ABI, the process model, the filesystem integration, the build
pipeline and the tests. Upstream CPython supplies Python semantics, adapted by a
maintained patch series. Building from source gives ownership and
reproducibility; stability comes from process isolation, consistent semantics
and systematic testing.

## Governing rules

1. One active Python process owns one interpreter instance.
2. One kernel authority owns shared mutable container resources.
3. Blocking callers never own the services they are waiting for.
4. All guest I/O crosses an explicit, versioned boundary.
5. Processes share files and channels — not Python globals.
6. Every resource has an owner and a cleanup path.

## Shape

```
browser app / shell                  kernel owner (volume, process table)
        │                                     │
        ├── preview service worker ───────────┤
        │                                     ├── VFS + descriptors + storage
        │                                     ├── process supervisor + signals
        │                                     └── virtual sockets + net policy
        │                                             ▲
   python worker A ──── sbx_host_v1 ────────────────── ┤
   python worker B ──── sbx_host_v1 ────────────────── ┤
   node workers ────────────────────────────────────── ┘
```

Kernel services stay on the host that owns them today; Python runs in dedicated
workers and calls back. Moving the kernel into its own worker is a later
optimisation with its own migration plan, driven by measured UI responsiveness,
and is not a prerequisite for any of this.

## Three compatibility targets

| Target | Behaviour | Mechanism |
|---|---|---|
| Python development | scripts, REPL, venv, pip, subprocesses, local servers | CPython to Wasm plus this kernel |
| Scientific / ML | supported packages, model inference | recompiled extensions, dedicated compute services |
| Linux binaries | existing Linux executables and wheels | optional CPU emulation running a real Linux guest |

The first is the product. The third is a separate execution backend and must
never silently substitute for the first when an install fails: it changes
architecture, path semantics, performance and persistence.

## What exists today

- `python-runtime/abi/` — the versioned ABI and its generated bindings.
- `src/kernel/open-file.ts`, `src/kernel/descriptors.ts` — pathnames, inodes,
  open-file descriptions and per-process descriptor tables.
- `src/runtime/python/` — framing, the blocking transport, the kernel-side
  dispatcher, the typed guest client, the Emscripten filesystem bridge, the
  release manifest, and the per-process supervisor and worker.
- `python-runtime/native/probe/` — a C program that exercises the whole path.
- `python-runtime/scripts/` — the pinned build: fetch and verify, host Python,
  cross-built CPython, and the packaged release with its manifest.
- `test/python-abi/`, `test/python-runtime/` — the baseline inventory and the
  M1 and M2 gates.

### The owned interpreter

`make -C python-runtime fetch python package` produces
`out/sbx-cpython-<version>-<profile>/`: `python.js`, `python.wasm`,
`python.data`, and a `runtime.json` naming the ABI it was built against and the
capabilities the profile actually has. A host selects it with
`configurePython({ backend: "sbx-cpython-wasm", manifest })`; Pyodide remains
the default until the parity gates pass.

Two things about the build are worth knowing before reading it:

- Upstream's browser target links a *page* — a classic script that assigns a
  global `Module` and runs `main` on load. A process worker needs a factory it
  instantiates once per process, so the final link is repeated with
  `-sMODULARIZE -sEXPORT_ES6 -sINVOKE_RUN=0`. Only the link changes.
- Some of what a program asks an operating system for is compiled into libc
  rather than routed through anything a host can serve. musl's `getpid` under
  Emscripten returns a constant, so every process would report the same
  identity however many interpreters were running. `-Wl,--wrap=` is what makes
  those reachable, and `python-runtime/native/js/library_sbx_posix.js` is where
  the first of them live.

See [release-gates.md](release-gates.md) for what each milestone has to prove.
