Skip to content

Repository files navigation

pyproc

pyproc

A persistent Python computer in your browser.

Open one Machine. Keep its workspace, environment, processes, and history; rewind it when work
goes wrong; carry it as a signed image. Real CPython, no application server required.

npm ci license MPL-2.0 zero runtime npm dependencies CPython 3.14 on WebAssembly

Live demo · Product model · Quick start · AI-agent patterns · Entrances · 한국어


Contents

The product

pyproc is one product: a persistent, browser-native Python computer. It is not a bag of unrelated runtime helpers. The public noun is Machine; execution, files, processes, durable history, images, and permissions are parts of that machine.

The promise is simple: prepare Python once, keep the live state, branch or rewind it, survive a tab closing, and move the machine as a verified file. The default product path is the Python Machine. Linux, WASI, GPU, sockets, and MCP are optional guests or capabilities around the same contract, not separate identities.

Product model

Product concept Current contract What it owns
Machine open() by default; boot() for an explicit transient kernel The single durable root and lifecycle
Workspace open({ name }) + /home/web Files and live work that survive reopening
Environment deterministic manifest + exact engine version Packages, setup, and replay boundary
Processes machine.proc() Independent worker interpreters, forks, signals, and parallel work
History automatic Machine generations + explicit transient checkpoints Checkpoints, branches, restore, journal, and recovery
Image signed .pymachine / .webmachine Portable state with integrity and an explicit trust gate
Permissions capability contracts + permission jail Network, storage, devices, memory, and execution policy

These are product concepts, not seven competing top-level APIs. A Machine remains the root, and its verbs reveal only the capability being used. Internal engine objects stay behind that boundary.

One machine lifecycle

create / open  ->  work  ->  checkpoint / commit  ->  branch / restore  ->  export / reopen
      Machine      Workspace + Environment     History + Processes           Image + Trust

The default Machine commits each completed command before its Promise settles. commit() remains a force-boundary verb, while boot() is the explicit transient workbench for checkpoint and branching experiments. A commit failure is outcome-unknown and never invites an automatic retry.

For an explicit transient rewind session:

import { boot } from "pyproc";

const machine = await boot();
await machine.loadPackages(["numpy"]);   // prepare once (packages, data)
const cp = machine.history.checkpoint(); // save the prepared state

const attempts = [
  "import numpy as np; float(np.arange(10).men())", // deliberate failing attempt
  "import numpy as np; float(np.arange(10).mean())",
];

for (const code of attempts) {
  try {
    console.log(machine.run(code));      // 4.5 on the successful attempt
    break;
  } catch (error) {
    machine.history.restore(cp);         // prepared state is back
  }
}

checkpoint and restore move the interpreter state, not a serialized copy of selected variables. The prepared environment returns without re-importing or reinstalling it.

Where the Machine pays off

Work What happens Why the Machine helps
AI data analysis Run AI-written pandas / NumPy on the user's file Analyze without shipping the raw file to a server
AI coding tools Checkpoint before running AI code; restore on failure Cheap trial-and-error, no environment reset
Multi-agent analysis Branch many runs from one prepared state Compare independent approaches in isolation
Browser notebooks Keep packages and data loaded across runs No re-boot, no re-install
Coding education Save the student state; test AI fixes on a branch Feedback without touching their work
Internal analytics Process sensitive CSV / Excel in the local tab Minimize sending data off-device
Offline tools Cache the runtime and packages Runs where the network is limited

The common thread is one long-lived Python Machine that can be prepared once, saved, branched, and restored. A fail-closed network policy can also keep selected data local while code runs.

What the Machine delivers

  • Runs in the browser - no application server is required. Python executes in the tab inside the Chromium renderer sandbox and WebAssembly boundary. Resource and network policy remain explicit; see Security model.
  • Restore without rebuilding. Checkpoint a state with packages and data already loaded, then roll back to it - no re-run, no re-install.
  • Close the tab; keep the machine (open() / open({ name })). Tabs share one logical Python state. Every completed command auto-commits memory, /home/web, and forwarded outcomes before returning; if the leader closes, another tab recovers that generation from OPFS and continues locally.
  • Branch from one state (machine.history + machine.proc()). An agent runs several code candidates from the same prepared state, independently, and compares results.
  • Data can stay local under a fail-closed policy. Process data in the tab and export only selected results. Local execution alone is not a no-exfiltration boundary.
  • Isolated execution. Python runs off the main UI thread, across multiple workers you manage.

Quick start

npm install pyproc@0.0.11 --save-exact
npx pyproc-engine --out public/vendor/pyodide
import { open } from "pyproc";

const machine = await open();
console.log(await machine.run("sum(range(1_000_000))")); // 499999500000

Name a Machine to open the same workspace from any number of same-origin tabs:

import { open } from "pyproc";

const persistentMachine = await open({ name: "workspace" });
await persistentMachine.run("counter = globals().get('counter', 40) + 1");
console.log(await persistentMachine.run("counter")); // 41, including after leader takeover

Try the full lifecycle in the Immortal Python Machine demo: shared state, leader identity, durable epoch, forced takeover, and local recovery with no backend.

Checkpoint and restore. The handle's history speaks both zones: the closing checkpoint() marks the execution boundary that makes the restore sound:

machine.run("values = [10, 20, 30]");
const cp = machine.history.checkpoint();      // save this state
machine.run("values.append(999)");
machine.history.checkpoint();                 // close the execution boundary -> instant restore path
machine.history.restore(cp);                  // back to the checkpoint - writes only changed pages
console.log(machine.run("len(values)"));      // 3

If the boundary was not closed (an exception mid-run, a stray mutation), cp.restore() detects it and falls back to a full rehash automatically - slower, never silently corrupt. After calling Python through a live proxy handle, report it with machine.markDirty().

The basics above need only a Chromium browser. PyProc (process OS) and sockets also need crossOriginIsolated (COOP: same-origin, COEP: require-corp) and same-origin workers - see Setup. Run checkEnvironment() to check.

Product entrances

Import from pyproc. That root is the complete product entrance; subpaths are advanced plumbing, not competing products.

You need Root entry Returned handle and capability path
The durable Python Machine open() or open({ name }) KernelElection: async run, automatic durable commit, status, failover, and cold reopen
A transient Python Machine boot() PyprocMachine: run, fs, history, term, proc, and the advanced runtime escape hatch
A portable or saved transient Machine open(blob, trust) or open({ dir, name }) PyprocMachine after the source-specific integrity, trust, and replay checks
A multi-guest browser computer createWebComputer() WebComputer: guest lifecycle, shared devices, durable generations, and signed computer images
Platform readiness checkEnvironment() Structured capability report with actionable issues
Programmatic failure handling PyProcError, PYPROC_ERROR_CODES One error-code contract shared by every root path

Deterministic replay is the shared foundation: boot({ deterministic: true }) fixes the boot entropy so the same manifest reproduces byte-identical memory at the replay boundary (cp0), which is what makes delta save, journal revival, and worker-to-worker fork sound. That choice is recorded in every durable commit's environment fingerprint - a non-deterministic machine refuses history.export instead of silently losing the replay guarantee.

Using it from an AI agent

Pattern 1 - restore on failure. Prepare the environment, checkpoint, run AI-generated code; if it throws or dirties the interpreter, restore the boundary and run the fix. The AI can't corrupt state you can't get back to.

prepare env  ->  checkpoint  ->  run AI code  ->  (fails)  ->  restore  ->  run fixed code

Pattern 2 - branch candidates. Load shared data and packages once, then run several approaches from the same prepared state, each isolated - via PyProc workers, or by repeated restore from one checkpoint.

load data + packages
        |-- pandas approach
        |-- SQL approach
        \-- NumPy approach

Pattern 3 - local-first data. The user's file is analyzed in the tab; only the summary leaves. Apply a fail-closed CSP before agent code runs so it cannot open an external endpoint, and constrain what the trusted agent control channel returns.

user file  ->  browser Python  ->  summary only  ->  AI model

Plug it into an AI agent (MCP)

The repo ships an MCP server with no additional runtime npm packages. It exposes a persistent pyproc Machine as four agent tools: pythonRun, checkpointSave, checkpointRestore, sandboxReset. It boots a headless Chromium machine page behind a COOP/COEP server and speaks MCP over stdio, so the retry loop above becomes tool calls:

git clone https://github.com/eddmpython/pyproc && cd pyproc
# register with your MCP client (claude CLI shown):
claude mcp add pyproc-sandbox -- node scripts/mcpSandboxServer.mjs
# or run it directly and speak newline-delimited JSON-RPC on stdio:
npm run mcp:sandbox

The agent prepares state once (pythonRun), saves a handle (checkpointSave), lets a risky attempt run, and rolls back in milliseconds (checkpointRestore) instead of rebuilding the environment. Trusted engine boot finishes first; agent code then runs under a fail-closed external-network CSP while same-origin MCP control traffic stays open. Self-host the engine if boot itself must make no CDN request. Tool results intentionally cross the MCP channel, so the calling application still owns output review and authorization. npm run test:mcp verifies the full round trip and an import js / fetch exfiltration attempt against a controlled receiver in CI.

Capability contract

These states measure only pyproc's own invariants. They never depend on adoption, user count, another repository, release age, or market response. Complete means the declared path, failure, and recovery contract is browser-gated through the installed package; Bounded means the listed ability works inside an explicit intrinsic boundary; Probe is an opt-in mechanism outside the default entrance.

Area Contract state
Python execution (boot / run / loadPackages) Complete
Default durable Machine (open() / open({ name })) Complete
Process OS, restore reactivity, ASGI, declared environments, terminal, machine images, and journal Bounded
Device FS, permission jail, GPU, and sockets Probe
non-Pyodide CPython 3.14 (bootWasi / WasiSession) Engine proof

What it guarantees, and what it doesn't

Guaranteed (browser-measured):

  • Pyodide-based Python on supported browsers.
  • WASM heap state saved at declared execution boundaries.
  • State restore under the recorded engine and manifest contract.
  • Worker-based execution isolation.

Not (yet) guaranteed:

  • Full process capture at an arbitrary instant - in-flight network requests and Promises are not restored.
  • Silent replay whose effect cannot be checked. A normal follower cannot inspect the leader's heap, so a sent call cut off by failover returns PYPROC_RPC_OUTCOME_UNKNOWN and is not resent. The narrow exception is a durable caller controller that can prove its own session is proxy-free: it parks the same request ID and asks the successor once, which answers from the recovered outcome record or runs against the recovered generation. Live-leader timeout and caller loss are never resent. See the durable RPC state table.
  • Every Python package - native C-extension wheels need a static build; pure-Python and Pyodide-built packages work.
  • Cross-version snapshot loading. .pymachine portability requires the recorded engine/manifest and either an explicit trust decision or a verified signer; mismatches fail closed.
  • GPU / native Linux packages, full POSIX fork, arbitrary native binaries.

Scope and platform direction

pyproc is the persistent Python computer described in the North Star above. Python is the default Machine. The Web Machine host ships inside the package (src/machine, entered through createWebComputer) and extends the same lifecycle to Linux: both guests can save memory and disks together, recover after a browser-process restart, and open a signed image in a fresh browser profile. The reproducible Buildroot Linux guest ships separately as a hash-pinned project release with source, legal material, SBOM, configuration, and independent-build evidence. The x86 emulator and remaining firmware stay externally supplied assets and are not part of npm.

Within that larger goal, Python reach remains unbounded: whatever Python runs locally should eventually run in the browser without an application server. Everything local sorts into four states, and pyproc's job is to push things up the list and absorb a wall when the platform reopens it:

  • Delivered (browser-gated in CI today): pure-Python + Pyodide packages, multi-core processes, checkpoint / restore, in-kernel ASGI, terminal, persistent FS, portable .pymachine and .webmachine images.
  • Shipped without a headless CI gate: pyproc/socket (outbound Python sockets need a WS-to-TCP relay this package does not ship) and pyproc/gpu (needs a real WebGPU adapter, which headless CI does not have). Both are opt-in subpaths you must verify inside your own product; the standing gap is tracked in contract reality.
  • Virtualized (the browser way): a TCP listen() becomes an ASGI app, os.fork becomes worker kernels, outbound sockets ride a thin relay.
  • Upstream-pending (walled now, reopenable): native C-extension wheels (Emscripten static builds / the WebAssembly component model), real threading.
  • Permanent web-security wall: inbound connections and arbitrary native binaries need an external relay or agent.

The current gap map is the capability matrix. The host architecture is the shipped src/machine contract, and its Dual-Boot evidence is registered in the executable North Star ledger and the Web Machine browser gates.

Security model

pyproc runs Python inside the browser's WebAssembly and Web Worker isolation boundaries. That is not a claim of safety for arbitrary untrusted code: an application running untrusted code is still responsible for its own network, storage, package, memory, and execution-time policies appropriate to its threat model. A .pymachine file is live state and carries the same risk as an executable - open(blob, trustOpts) verifies a SHA-256 envelope and refuses to open without either explicit { trust: true } or a signature verified by trustedPublicKeys.

Supply chain: npm releases use Trusted Publishing (OIDC) with provenance (manual publishes disabled); the pyproc-assets CLI emits an SRI manifest over the worker/service-worker import graph and verifyPyProcAssetIntegrity enforces it before any worker spawns; engine boot supports fail-closed SRI (engineScriptIntegrity / coreIntegrity) with a re-verifying OPFS offline cache. Threat model details: SECURITY.md.

How it works (one page)

pyproc treats browser Python not as "one notebook cell" but with an OS-like process model: a Web Worker is a process, a heap snapshot is a process image, injecting that snapshot is a fork, and N interpreters mean N GILs = N-core parallelism. It runs Pyodide (CPython on WebAssembly) and adds what Pyodide doesn't give you alone: cheap process spawn, real parallelism, and interpreter-state restore without re-running your code.

Application / AI agent
        |
     pyproc API
   +----+----------+
Runtime  Process OS  Capabilities
   |        |        (reactive / syscall / socket / asgi / terminal / session / ...)
Pyodide  Workers
        |
 Snapshot / Journal / Restore

Four primitives make it sound: complete heap hashing at each execution boundary (sampling would miss changes and corrupt a restore); deterministic boot (a byte-identical base, so only your delta has to travel); snapshot-fork; and an engine seam (the same primitives also run on non-Pyodide CPython 3.14, proving they don't depend on Pyodide internals). The persistent design lives in the product direction and module boundaries; the executable axis-by-axis gap map is tests/northStar.mjs.

Where the shape pays off

pyproc is not "Python, but faster." It is Python with a process model, and the wins come from the contract rather than from arithmetic: prepare state once and branch it, restore instead of re-running, shard work across independent interpreters (N interpreters = N GILs = real parallelism), serve from inside the tab, and move a live machine as a signed image. Single-kernel NumPy is ordinary WebAssembly BLAS, and pyproc does not pretend otherwise.

Measure the envelope on your own hardware: run Speed Lab with npm run serve. The measurement contract is in benchmarking.md.

Run the Web Computer

The multi-guest Web Computer surface extends the same Machine lifecycle to Python and Linux in one browser workspace. Both guests have real memory and block-backed files, save into one durable IndexedDB generation, recover after the browser process closes, and move together in a signed .webmachine file. It proves that the Machine contract can host more than Python; it does not replace the persistent Python Machine as pyproc's default product path.

npm run assets:web-computer
npm run serve

Open http://localhost:8788/apps/webComputer/ in Edge or Chromium. The product includes Python execution, a Linux VGA display and terminal, pause/resume/shutdown controls, automatic durable saves after commands, manual save, signed export, and an explicit signer trust screen for import.

The current Linux execution catalog is a hash-pinned development channel. Its engine and image binaries are prepared locally and excluded from git and npm packages; public redistribution remains disabled until the complete source and license inventory is reproducible.

Capability paths

Start from the Product entrances table. Capabilities below those handles are opt-in; use their contracts rather than engine internals (HEAPU8 and friends). The full intrinsic capability table lives in the capability matrix.

Plumbing subpaths carry the contracts underneath the handle:

// The advanced engine seam when you boot Pyodide yourself and hand the instance to pyproc.
import { Runtime, bootRuntime, checkEnvironment } from "pyproc/runtime";
// The durable-state kernel: object model, commit/open protocol, stores, signed bundles.
import { commitState, openState, OpfsStateStore, decodeStateBundle } from "pyproc/history";
// The browser-computer internals (hosts, devices, guest adapters, machine stores).
import { createMachineCryptoProvider, MachineCommitCoordinator } from "pyproc/machine";
// Deployment assets: manifest, SRI verification, Service Worker registration.
import { getPyProcAssetManifest, verifyPyProcAssetIntegrity, registerPyProcServiceWorker } from "pyproc/assets";
// Demoted (no headless CI gate, or research preview) - deliberately off the root surface:
import { GpuCompute } from "pyproc/gpu";
import { SocketBridge } from "pyproc/socket";
import { bootWasi } from "pyproc/wasi";

The function-level reference is docs/reference/api.md (English); this README stays the map. docs/usage/, docs/reference/, and docs/product/ are English; docs/operations/ is the internal operating tree and stays Korean. For product decisions by capability, use the capability matrix: it maps each public export to value, status, setup, runnable surfaces, gates, and boundaries.

Deployment asset manifest:

npx pyproc-assets --baseURL /vendor/pyproc/ --out public/vendor/pyproc-assets.json --copy-to public/vendor/pyproc

The CLI follows the Worker / SharedWorker / Service Worker import graph, copies the required files when --copy-to is set, and emits sha256-... integrity for every file. Load that JSON as assetIntegrity before worker-backed capabilities spawn, and register pyprocSw.js through registerPyProcServiceWorker(...) so the Service Worker path is verified too.

Dependency boundary

Zero runtime npm dependencies is an exact package fact, not a claim that computers have no dependencies. pyproc owns the JavaScript runtime graph it publishes. A working Machine still rests on an engine, browser primitives, and any explicitly enabled external capability.

Layer Current boundary Can it be removed?
Runtime npm graph No packages under dependencies; native ESM ships as source Already zero
Python engine assets Pyodide v314.0.2 at the verified same-origin /vendor/pyodide/ default Third-party delivery is absent by default; the engine itself cannot be removed without replacing CPython
Browser platform Chromium/Edge, WebAssembly, Workers, OPFS; JSPI and COOP/COEP for blocking/process paths No; this is the hardware and security boundary
Optional capabilities Relay for raw outbound sockets; WebGPU hardware; injected x86 emulator, firmware, and Linux image Yes; omit the capability and the Python Machine remains complete

The strongest deployment is therefore not an imaginary dependency-free computer. It is an owned and verified dependency chain: pin the exact pyproc version, prepare the pinned engine with pyproc-engine, emit and verify the JavaScript asset SRI manifest, and cache verified assets in OPFS. An explicit CDN indexURL remains an evaluation route, never the default.

Setup

Chromium / Edge only, and the requirements are per capability rather than per package. Booting, running code, installing packages, and the whole of machine.history need nothing but the browser: no headers, no bundler configuration. JSPI (default since Chrome 137) is what the blocking paths need, and SharedArrayBuffer through COOP/COEP is what the process OS needs. checkEnvironment() reports exactly where a page stands, and each capability raises an actionable error rather than failing obscurely. Lack of Firefox / Safari support is a deliberate scope choice, not a defect. Full environment matrix (per-capability requirements, engine version, resource characteristics): docs/usage/platformRequirements.md.

There are two tiers of setup, so "just install and import" is true for the basics but not for everything:

You want You need Engine assets
open / run, or transient boot / packages / machine.history npm install, npx pyproc-engine --out <static-root>/vendor/pyodide, and Chromium. No headers. The verified same-origin /vendor/pyodide/ distribution
machine.proc() (fork, map, interrupt), IPC, blocking sockets The two headers below, plus same-origin worker files (so npm install / vendoring, not CDN import) Same, and the worker file must be same-origin

Engine assets are prepared at deployment, not embedded in the npm tarball. The published pyproc-engine CLI downloads the exact release, verifies the six boot anchors against pyproc's catalog, then verifies all package files against the pinned lock before placing them under the default /vendor/pyodide/ URL.

npx pyproc-engine --out public/vendor/pyodide
// The default verifies pyodide.js plus fetched core bytes from /vendor/pyodide/.
await boot();
// Optionally cache the verified core in OPFS so later boots do no network at the fetch layer.
await boot({ coreCacheDir: await navigator.storage.getDirectory() });
// Evaluation only: opt into a different distribution point explicitly.
await boot({ indexURL: "https://cdn.jsdelivr.net/pyodide/v314.0.2/full/" });

The default runtime re-verifies core bytes as they are fetched. Custom engine loaders own their own trust policy. The pinned version and distribution boundary are the package contract: docs/usage/contract.md.

Serve the page that hosts pyproc with:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

checkEnvironment() tells you exactly where you stand and how to fix any gap - call it once before you rely on the process OS:

import { checkEnvironment } from "pyproc";

const env = checkEnvironment();
if (!env.ok) console.warn(env.issues);   // each issue has { code, need, why, fix }
// env.ok true  -> everything works, process OS included
// env.ok false -> basics still work; issues list what unlocks PyProc / sockets

Skip the headers and reach for PyProc anyway and you get an actionable error (which headers to add), not a cryptic SharedArrayBuffer is not defined.

Common ways to send the headers:

// Vite (vite.config.js)
export default { server: { headers: {
  "Cross-Origin-Opener-Policy": "same-origin",
  "Cross-Origin-Embedder-Policy": "require-corp",
} } };
# Static hosts that read a _headers file (Netlify, Cloudflare Pages)
/*
  Cross-Origin-Opener-Policy: same-origin
  Cross-Origin-Embedder-Policy: require-corp

Can't set headers at all (e.g. GitHub Pages)? Register pyprocSw.js?coi=1 and reload once - the service worker injects the headers (virtual COI).

Install and pinning

From npm (npmjs.com/package/pyproc): npm install pyproc@0.0.11 --save-exact. There is no build step (native ESM). Floating ranges (^, ~, latest) are not supported because a state kernel's replay guarantee is version-bound:

// package.json
"dependencies": { "pyproc": "0.0.11" }

pyproc/runtime and the typed API subpath entries ship in 0.0.11. A SHA pin (github:eddmpython/pyproc#<commit-sha>) remains the documented way to consume a commit that has not been released. Full policy: docs/usage/contract.md.

North Star

Make the browser a persistent computer, make Python its default Machine, and make that computer pyproc itself.

Scores measure only capabilities and invariants pyproc owns. Adoption, user counts, release age, other repositories, and market response never score. A path no automated gate runs does not score, and an axis with manual-only evidence stays below 9. A 10 means the capability is complete: repeatedly verified in a real browser, with no workaround left in the public surface.

Today that is 105.2 / 120, average 8.8 / 10.

Axis Score Where it stands today Where it has to land Next move
Real Python in the tab 9.7 open is the durable Machine and boot is the transient workbench; both drive CPython on WebAssembly. The pinned engine is prepared by the shipped zero-dependency CLI, served from the same origin, checked against catalog and lock hashes, then core-verified again in the browser with zero third-party requests. Browser, installed-package, demo, and agent gates run it. The platform is Chromium and Edge only. The Python a local interpreter runs, running in a tab, with no server and no setup ritual. Broaden the browser platform without weakening the Machine contract or hiding unavailable capabilities
State you can rewind 9.0 Checkpoint, restore, branch, and prune run at execution boundaries over complete heap hashing: a full-heap byte-equality round trip, sibling-delta isolation across a branch tree, and a violated boundary that falls back to a full rehash instead of restoring something corrupt. Node property and fuzz gates cover delta soundness and tree integrity. An arbitrary instant is still not capturable, because in-flight promises and network requests live outside the boundary. Any past state comes back instantly, including the work that was in flight when it was left. Capture an arbitrary instant rather than an execution boundary, by pulling in-flight promises and requests inside the boundary
Processes and real parallelism 8.5 Workers are processes: snapshot-fork spawn, map, forkMany, a signal table, kill, job control, nested containers, pool exhaustion, and mid-flight worker death all converge under the browser gate. N interpreters are N GILs, so the parallelism is structural rather than scheduled. There is no shared-memory threading and no arbitrary POSIX process tree. A process model with the vocabulary of a real operating system, threads included once the platform allows them. Take shared-memory threading the moment nogil and WASM threads land upstream, without changing the process vocabulary
A disk that survives 9.0 The state kernel commits content-addressed generations into OPFS under a write-order law: a tampered blob is caught, a broken HEAD falls back to PREV instead of impersonating a first boot, journals pack, an unchanged re-commit writes zero bytes, and the durable generation is what the browser computer restores after its process restarts. There is exactly one format on disk now: the legacy envelope reader was retired, and a file written by an older version is refused with what to do about it rather than half-read. Durability with the guarantees of a real filesystem: no torn commit, no silent loss, exactly one format. Survive an OPFS quota eviction as explicitly as a torn commit: today persistence is requested best-effort and a denial is a browser heuristic
A machine that outlives its tab 9.7 Argument-free open() now enters the named OPFS Machine rather than a transient kernel. Commands and commits are serialized, and every completed run reaches a generation carrying heap, /home/web, and forwarded outcome before settling; the installed package cold-reopens that state without a manual commit. Leader election spans same-origin tabs, a repeated request ID is answered from its durable record, and commit failure is non-retryable outcome-unknown. A normal follower still cannot prove a cut-off leader heap portable, so failover of an in-flight call remains PYPROC_RPC_OUTCOME_UNKNOWN. The complete rule is the durable RPC state table. The machine keeps running while any tab is open, and every command it accepted resolves exactly once. Carry a fenced portability fact to ordinary followers so they can safely use the outcome-record path; a proxy-bearing heap remains outcome-unknown
A machine you can carry 9.0 .pymachine and .webmachine files are signed content-addressed envelopes: signature and trusted-key verification, byte-tamper rejection, layout-independent reparse, worker-to-worker revival, and a cross-context transport refused on an h0 mismatch instead of opened silently. The product gate exports a signed image and imports it into a fresh browser profile behind an explicit signer trust screen. Portability still assumes the same engine and manifest. A JS proxy handle cannot cross an image at all, so a surface that installs one poisons every proxy path in the revived kernel; the packet device and the permission jail were moved to value boundaries and survive a revival in CI, while a blocking surface (the syscall bridge behind input(), sockets, GPU) cannot move and is refused at export unless the caller acknowledges it. A machine file verifies and revives offline in a clean profile under one explicit execution contract; every mismatch is rejected with an actionable error. Rebind JS handles after materialisation, or find a blocking mechanism that needs none, so a machine that used input() can still ship a portable image; Prove offline signed-image revival in a clean browser profile while rejecting every engine or manifest mismatch
A computer that boots guests 9.0 The Web Machine host lives inside this package behind createWebComputer, and Python and x86 Linux guests use the same lifecycle, device, generation, and envelope contracts. Host contract, dual-engine, owner succession, durable generation, and guest-network probes run in CI, and the product gate boots both guests, survives a browser-process restart, and moves the pair as one signed image. The x86 lane puts the real Python and Linux guests on one switch: Linux pings Python, a Python-sent Ethernet frame increments Linux's NIC receive counter, and both directions survive one generation commit and a process cold restore. A guest can also run in its own worker (pyproc-worker), so a CPU-bound guest no longer stalls the others. The reproducible Linux build is checked against exact source, legal inventory, SBOM, config, and an independent byte-identical build receipt. Any guest with an adapter boots on the browser computer, and its image ships as freely as the host does. rung 5: Enable memory64 once the engine contract can prove it, lifting the per-module heap ceiling a large guest hits first; rung 7: Boot a Node guest beside Python and Linux, making JavaScript CLI tools residents of the computer
Primitives that outlive the engine 7.0 A non-Pyodide lane boots CPython 3.14.6 on WASI in the browser and takes checkpoint, time travel, repeated branching, and pure-Python wheel installation through the same contracts, which is what proves the primitives are not Pyodide internals. That lane has no dlopen, so it carries no dynamic C extensions, and its value bridge is JSON only. Every primitive runs on any CPython-on-WebAssembly engine, with the same package reach on each. Close the WASI gap: dynamic linking (cpython#142234) for C extensions, and a value bridge that is not JSON only
Network, the browser way 8.0 An in-kernel ASGI server answers fetch from Python with concurrent requests kept apart, a virtual origin serves it from the installed package, urllib performs real HTTP through the syscall bridge, and the permission jail decides connectSrc per host. Python-to-Python traffic is gated without assets, while the x86 lane proves the real cross-engine path: Linux pings Python and a Python-sent Ethernet frame arrives at the Linux NIC before and after process cold restore. Outbound raw sockets still need a WS-to-TCP relay this package does not ship, but a hermetic lane starts the in-repo relay and a local TCP origin and reads bytes back through Python urllib. Python network code runs unmodified, and the relay boundary is the only thing a reader has to know. rung 1: Terminate TLS inside the tab, so a relay carries ciphertext it cannot read and needs no trust; rung 2: Carry many sockets over one WebSocket, the Wisp class of relay hardening; rung 3: Open a direct tab-to-tab transport over WebRTC as an opt-in subpath, once the surface freeze clears; rung 4: Keep an Isolated Web App packaging lane ready for the day Direct Sockets opens a real inbound listen
Everything local Python does 7.5 Pyodide's dlopen already loads native C-extension wheels (numpy, pandas, scipy and more), packages install from a cache, %pip and freeze work inside the machine, and the WASI lane installs pure-Python wheels. The long tail is what is missing: an arbitrary package needs a published pyemscripten wheel, numpy has no SIMD build, threading is upstream-pending, and the GPU lane has no headless adapter, so what CI holds is the byte identity of the WGSL each integration path compiles, not its result on a GPU. Whatever runs in a local interpreter runs in the tab, at a speed that needs no apology. Widen package reach where it is thin: a pyemscripten wheel for the long tail, and a SIMD numpy build; rung 6: Bring the tools a working machine assumes (the git and ripgrep class) inside as wasm residents, so shelling out is real
One gathered product entrance 10.0 The pyproc root gathers the complete choice: open for the durable Python Machine, boot for an explicit transient Machine, createWebComputer for the multi-guest host, and checkEnvironment for preflight. Errors share one contract, advanced plumbing stays in named subpaths, and installed-package plus browser gates prove every root door without a deep import. One root import that shows every product door, the handle each door returns, and the capability path beneath it, with no competing top-level identity.
A supply chain you can verify 8.8 The zero-dependency engine CLI verifies catalog-pinned boot anchors and every lock-listed package before same-origin deployment; runtime pins the script SRI, re-verifies fetched core bytes, and the browser gate proves zero third-party requests. The asset CLI seals the worker and Service Worker graph, bad hashes refuse spawn, machine images verify signers before import, and the Linux guest build is checked by byte-identical independent rebuild plus source, legal inventory, SBOM, config, and manifest. Every byte pyproc executes is either built by a repository recipe or pinned by a digest, and every mismatch fails before execution. Build the remaining firmware and emulator assets twice from repository recipes and gate every digest in the final execution graph

The axis ledger is tests/northStar.mjs: each axis registers the executable artifacts standing behind it, and the structure gate turns red when a registered gate is missing, is opened by no runner, or does not run in CI. This table is rendered from that ledger, so no score moves by editing prose. What each axis means, and what would move it, is in the product direction.

Where the ceiling moves next

The distance that remains is two walls with different fates. The transport wall (a tab accepting an inbound connection) is opening, so it gets climbed in order. The native wall (web content spawning a native process) never opens, by the design of the web itself, so what only local machines run moves inward instead. Every rung names the axis it moves:

  1. Terminate TLS inside the tab, so a relay carries ciphertext it cannot read and needs no trust (moves: Network, the browser way)
  2. Carry many sockets over one WebSocket, the Wisp class of relay hardening (moves: Network, the browser way)
  3. Open a direct tab-to-tab transport over WebRTC as an opt-in subpath, once the surface freeze clears (moves: Network, the browser way)
  4. Keep an Isolated Web App packaging lane ready for the day Direct Sockets opens a real inbound listen (moves: Network, the browser way)
  5. Enable memory64 once the engine contract can prove it, lifting the per-module heap ceiling a large guest hits first (moves: A computer that boots guests)
  6. Bring the tools a working machine assumes (the git and ripgrep class) inside as wasm residents, so shelling out is real (moves: Everything local Python does)
  7. Boot a Node guest beside Python and Linux, making JavaScript CLI tools residents of the computer (moves: A computer that boots guests)

The repo-local acceptance condition and order of every rung are in the product direction. The rungs are registered in the axis ledger, so no outside adoption signal can move a score or reorder the work.

Development

npm test              # Node structure / lint gate (no runtime npm dependencies)
npm run test:installed # installed package browser gate
npm run test:browser  # headless Chromium runtime gate: boot / reactive / fork / map (no runtime npm dependencies)
npm run serve         # COOP/COEP static server for manual validation and benchmarks

Because this is a WASM runtime, real validation only happens in a browser: test:browser verifies the repo public surface, and test:installed verifies an installed npm package inside an isolated browser fixture, including the Service Worker + VirtualOrigin URL path. Both run in CI. Persistent product and operating decisions live in docs/, executable truth lives in src/ and tests/, historical decisions remain in git history, and contribution rules live in CONTRIBUTING.md.

License

Mozilla Public License 2.0, the same license as Pyodide, the engine underneath. Copyright 2026 eddmpython.

MPL-2.0 is file-level copyleft, so the practical terms are: embedding is free (import pyproc into a closed-source app, ship it, sell it; your own code stays yours); forks of pyproc itself stay open (modify a covered file and you publish that file's source under MPL-2.0); patents are granted by every contributor for their contributions (Section 2.1(b)). Contributions are accepted under the same license without a separate CLA (inbound = outbound). See CONTRIBUTING.md.

About

Real Python runtime in the browser tab: processes and multicore parallelism, checkpoint/time-travel, portable .pymachine images, uv-grade instant environments, a Python server on a real URL. No server, no build step.

Topics

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Sponsor this project

Packages

Contributors

Languages