virrun
An ephemeral, in-memory virtual runner: boot any repo into a RAM-backed filesystem, run its real toolchain (pnpm, native addons, scripts) fast and isolated, then snapshot and fork the warm state so repeated runs are near-instant. It lives at packages/virrun and is published unscoped as virrun.
virrun is not a virtual filesystem — the VFS is a reused layer (@platformatic/vfs, later node:vfs). It is a runtime: the layer that runs real processes against that filesystem, isolates them, and snapshots/forks them.
Design goals
Developer experience through speed — remove the two things that make the everyday toolchain slow:
- Network-bound waits — dependencies are fetched once into a shared store and reused; a warm snapshot skips install entirely.
- Disk I/O — files live in a RAM filesystem;
node_modulesand build output never touch real disk.
Plus: ephemeral (spin up / throw away, no polluted machine state), reproducible (same source + lockfile → same warm snapshot), isolated (a run cannot corrupt the host), and drop-in (existing commands run unchanged behind a single virrun -- <cmd> prefix).
The two gates
Every backend and speed feature must pass two non-negotiable gates, both CI-enforced — a violation is not shippable, however clever:
- Faster than the native baseline — tracked by committed
*.bench.mdartifacts diffed offline. - Observably correct — exit code, stdout/stderr, produced files, and dependency tree identical to running the command natively, enforced by a differential Vitest harness that hard-fails CI on any divergence.
Correctness beats speed; a fast wrong answer is worthless.
Key concepts
- Backend — the one axis that changes what actually runs:
native(host passthrough),vfs(in-process pure-JS over a virtual FS),os(real process exec inside a bubblewrap RAM overlay). See execution backends. - The subprocess wall — an in-process VFS is blind to child processes; only the
osbackend puts a realpnpm installin RAM. See architecture. - Warm snapshot / fork — "clone + install" happens once into an environment-keyed overlay layer (lockfile + sandbox node major); each run forks it. See snapshot and fork.
- Write-back — a mutation command's produced files are flushed back to the host so disk matches native, while
node_modulesstructurally never flushes. See write-back. - Task cache — a persist run keyed by environment key + working-tree + command hash; a hit skips the sandbox and replays the recorded diff and streams.
- The prefix is the switch —
virrun -- <cmd>opts one command in; removing it opts out. No allowlist, no env flag. - Derived, not named — no path is special because of the tool that made it; every special case falls out of a structural property, with git the one sanctioned exception. See derived, not named.
Pages
| Page | Covers |
|---|---|
| Architecture | system overview, the five layers, the subprocess wall, where the speed comes from |
| Execution backends | the ExecBackend seam, virtual-FS layer, vfs and os backends |
| Snapshot and fork | warm deps snapshot, source-keyed prepare layer, atomic publish |
| Write-back | native-equivalent persistence of a mutation command's output |
| Task cache | content-keyed replay of unchanged persist runs, the two honesty guards |
| Task-cache eviction | age-prune the unbounded tasks dir, touch-on-hit recency, payload size in cache ls |
| WSL source mirror | win32 ext4 source mirror + host-side manifest delta sync |
| Subprocess timeouts | which bound a spawned child gets, and the one case that gets none |
| Adoption | the prefix-is-the-switch model, opt-in levels, auto-fallback, CLI subcommands |
| Configuration | the committed virrun.config.* — backend selection + environment preset |
| Cache | the gitignored .virrun layout, probe caches, cleanup & self-healing |
| Correctness | the correctness gate — differential, equivalence, and property/fuzz layers |
| Derived, not named | no tool-specific knowledge — special cases come from structure, git excepted |
| Benchmarking | the speed gate — committed bench artifacts, methodology, honest numbers |
| Orchestrator API | the public createVirrun TypeScript surface |
| Prior art | surveyed landscape — what was adopted, studied, or ruled out |
Open work: roadmap. Decided ideas: deferred (not yet, trigger-gated) · rejected (won't do) — grep both before proposing anything.
Shipped log
- Foundations —
ExecBackendseam, native passthrough backend, asynccreateVirrun,dir/files/gitsource loaders, thevirrun -- <cmd>CLI, colocatedpnpm benchartifacts. - VFS layer —
FsProviderover@platformatic/vfs(the lone import, doubling as thenode:vfsswap shim); mounting patchesrequire/fsto serve virtual files. vfsbackend — runsnode -eandnode <file>in-process over the overlay FS, falling back to native for anything it cannot run faithfully.osbackend — real process exec inside a rootless bubblewrap RAM overlay, a lazy content-addressable pnpm dep store, and the WSL2 bridge from Windows.- Snapshot + warm-fork — environment-keyed overlay snapshot with atomic publish, exposed as
fork()on the orchestrator. - Prepare layer — a second, source-keyed overlay layer capturing framework codegen (
nuxt prepare→.nuxt) so type-aware tooling reads Linux-generated artifacts. - Write-back — a persist run reconciles its overlay upper onto the host so disk matches native; the default for a bare
virrun -- <cmd>. - Task cache — content-keyed replay of unchanged persist runs; default-on locally, off in CI.
- Config backend selection — committed
virrun.config.{ts,mts,js,mjs,json}loaded via unconfig; this repo branches win32 →os, elsenative. - citty CLI —
run/exec/warm/init/cache/doctorsubcommands; the barevirrun -- <cmd>prefix is the defaultrun. - WSL ext4 source mirror + manifest delta sync — win32 source reads move off v9fs onto an ext4 mirror kept fresh by a host-side manifest diff (no per-run 9p stat-walk).
- Cross-process probe caches — os-backend capability probe and win32 WSL environment probes persisted across processes.
- Concurrency-safe cache — pid-tagged temps, pid-liveness reaping, and per-run leases so concurrent runs never delete each other's files.
virrun doctor— probes eachos-backend prerequisite and prints an aligned per-check report.- Native-on-Linux CI — the platform-branched config resolves
nativeon Linux CI runners; the former warm-snapshot CI pipeline was removed. - Bench-truth — corrected the speed story: no install bench group (the os install feeds the fork snapshot, not host disk), honest win32 numbers.
- Task-cache eviction — age-prune of the unbounded
tasks/dir beside the temp reap, touch-on-hit recency so age reflects use, and payload size incache ls.