What vx is, and what it refuses to be
vx is a task runner and a content-addressed cache for JavaScript monorepos, and nothing else. This post is the shape of the thing: the five stages, the seams, and the list of features that will never be inside.
vx is a task runner and a content-addressed cache for JavaScript monorepos, and nothing else. This post is the shape of the thing: the five stages, the seams, and the list of features that will never be inside.
Turborepo is fast and stops at the edge of a laptop. Nx scales and is a product with a runner attached. Between a tool that will not grow and a platform that will not get out of the way, the thing a large JavaScript monorepo actually needs did not exist. That is why vx does.
Every design decision in vx is settled by three drivers in a fixed order, and nine principles that follow from them. This is the list, with what each one has already cost and what it has bought.
A daemon answers 'what changed' quickly by keeping a second copy of the truth. vx keeps no copy. Every run pays its own discovery and still wins the warm benchmarks, because the discovery was made cheap instead of being hidden.
Auto-inferred inputs are the most requested feature vx will not build. A traced read set describes what a task read once, on one machine, after it ran. A cache key has to be right before the task runs, everywhere.
A vx task is one shell command. Not a JavaScript function, not an executor with an options object, not a list of steps. The constraint is what makes remote execution, sandboxing, replay and migration all fall out for free.
vx ships as one self-contained executable per platform. No Node to pick, no Bun to install, no daemon to start. npm install -D @vzn/vx gets you the binary; a release tarball gets you the same binary without npm.
vx is built like Vite: a core pipeline with a named hook at every stage, and plugins that fill exactly the stage they need. A remote cache is one hook. A lockfile parser is one hook. Zero-migration Turbo support is one hook.
Every executor list ends with this machine. Every cache chain ends with .vx/cache. That is the one thing core does without being told, and it is what lets a plugin decline a task and hand it back instead of failing the run.