Skip to main content

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 code inside ws.execute('python3 ...') can open(), os.listdir(), pathlib.Path() etc. against any registered Mirage mount. The shim routes those calls through Mirage’s mount layer (RAM, S3, Linear, GDocs, Slack, anything you’ve registered).
# inside python3
import json
team = json.load(open('/linear/teams/eng/team.json'))   # reads through Linear's API
open('/ram/notes.txt', 'w').write('hello')              # writes flush back to RAMResource

What works

open('/s3/data.csv').read()
open('/linear/teams/eng/team.json').read()
open('/gdocs/owned/MyDoc.gdoc.json').read()
The first read fetches via the resource and caches in MEMFS. Subsequent reads are sync.

What doesn’t work

CaseWhyWorkaround
C extensions calling fopen directly, sqlite3.connect('/ram/db.sqlite'), h5py.File('/ram/x.h5')They bypass Python’s open() and the shim never sees themUse a local copy, or stream bytes via stdin
External edits show up live, someone else edits the GDoc while you’re readingOnce a path is in MEMFS the shim doesn’t re-fetch itCurrently no API; must unmount + addMount
Huge mounts, preloading a 100GB S3 bucketEvery byte lives in MEMFS until closeMount a narrower prefix
Concurrent writers, two Python processes write the same pathLast close() wins, no conflict detectionOut of scope for v1
Browser-only resources from Node, OPFSOPFS isn’t a thing in NodeRun in a browser-side Mirage

How it works (one paragraph)

Pyodide’s in-memory FS (MEMFS) is the sync facade. At addMount, Mirage walks the prefix and copies files into MEMFS. Python reads hit MEMFS directly, fast, no network. On a miss (path not yet in MEMFS but the prefix is registered), the shim catches the error, calls back through the bridge via JSPI to fetch it, populates MEMFS, and retries. Writes are intercepted on close() and flushed back through Mirage’s write op.
Python open() / listdir()


   Pyodide MEMFS  ──hit──►  bytes

       miss


   run_sync(_mirage_bridge.list/fetch)


   populate MEMFS, retry

Quick start (TypeScript)

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

const ws = new Workspace({}, { mode: MountMode.WRITE })
ws.addMount('/ram', new RAMResource(), MountMode.WRITE)
ws.addMount('/s3', new S3Resource({ bucket: 'my-bucket', region: 'us-east-1' }), MountMode.READ)

await ws.fs.writeFile('/ram/in.json', '{"hello":"world"}')

const r = await ws.execute(`python3 -c '
import json
print(json.load(open("/ram/in.json"))["hello"])
'`)
console.log(r.stdoutText)  // "world"
See the full demo at examples/typescript/python/vfs.ts.

Python packages (PIL, numpy, pandas, …)

Pyodide ships CPython, but third-party packages aren’t loaded until you import them. Mirage scans the code you run for import statements and auto-fetches matching packages on demand, so from PIL import Image, import numpy as np, import pandas as pd all just work the first time. Subsequent calls hit Pyodide’s package cache. If you need to opt out (e.g., to keep workspace startup lean):
new Workspace({}, { python: { autoLoadFromImports: false } })

Resource compatibility

ResourceStatus
RAM, OPFS (browser), Disk
S3, R2, GCS, OCI, Supabase✅ (narrow the prefix to keep the working set small)
Linear, GDocs, GSheets, GSlides, GDrive
GitHub, Redis
Gmail (including synthesized date dirs)✅ (lazy-fetched on first access)
Slack⚠️ preloads channel histories; large workspaces may be slow

Runtime requirements

The shim uses JSPI to bridge sync Python calls to async JS.
  • Chrome / Edge 137+ (May 2025): works out of the box
  • Firefox: behind javascript.options.wasm_js_promise_integration
  • Node 24+: pass --experimental-wasm-jspi (vitest config already does this)
  • Cloudflare Workers: Python Workers with JSPI runtime
Without JSPI, reads of preloaded files still work but writes throw RuntimeError: Cannot stack switch on close().
To run examples directly:
node --experimental-wasm-jspi --import tsx/esm examples/typescript/python/vfs.ts

Errors you might see

SymptomWhat it means
FileNotFoundError [Errno 44]Path isn’t in MEMFS and lazy fetch failed too. Check the preceding console.warn: mirage lazy: ... for the real reason (auth, network, bad path).
console.warn: mirage lazy: list <path> failedThe lazy backfill called _mirage_bridge.list and got rejected. Followed by FileNotFoundError.
RuntimeError: Cannot stack switchJSPI not enabled. Pass the Node flag or upgrade browser.
console.warn: mirage preload: skipping <path>A single entry failed during initial preload, others continued. Usually harmless (synthetic listing entries).

See also