Task orchestration you own.
Correct, sandboxed, extensible. No cloud, no account, no paywall.
vznjs.github.io/vx
Task orchestration with a cache you can trust, on a core you can extend. Each task is one shell command with declared inputs and outputs; vx runs them in dependency order and replays results from a content-addressed cache.
In a git repo:
bunx @vzn/vx init # installs vx, writes an empty root vx.config.ts and the lockfile pluginA prebuilt binary for Linux (glibc, not Alpine's musl) and macOS, x64 and arm64. Windows: run vx inside WSL2.
A task runs its tool directly, never through a package script. Cache one by saying what it reads and writes:
// packages/app/vx.config.ts
import { defineProject } from '@vzn/vx/config'
export default defineProject({
tasks: {
build: {
exec: { command: 'tsc -b' },
dependsOn: ['^build'], // dependencies build first
inputs: { files: ['src/**', 'tsconfig.json'] },
cache: {
outputs: { files: ['dist/**'] },
},
},
},
})vx run build --all # builds, in dependency order; stores the results
vx run build --all # nothing changed: up-to-date, in milliseconds
rm -rf packages/*/dist
vx run build --all # restored from the cache, not rebuiltinit also writes .vx/projects.lock.json, every project's config as it evaluated.
Commit it. Every run reads its configs from the lock, so run
vx lock and commit the lock again after you:
- edit any
vx.config.tsor.vx/workspace.ts, - update dependencies or plugins,
- change a file a config imports.
Otherwise runs keep the old configs. On CI, vx lock --check fails a
stale lock before any task runs.
Starters: examples/basic, and
examples/ci with a GitHub Actions workflow; CI runs both
on every commit. The CI guide
explains the workflow.
More: Quickstart ·
Configure ·
CLI · every config field.
- A cache you can trust: TypeScript configs are evaluated before hashing, so imports and computed values are in the key. Outputs are wiped before every run and restore: no stale file survives.
- Sandboxed tasks: opt in per task; a workspace file the task did not declare is out of its reach, so it cannot poison the cache.
- Clean exits: Ctrl-C reaps each task's process group.
- Plugins at every stage (15 hooks,
commandsincluded): remote cache, remote execution, telemetry, CLI verbs. Core ships none and needs none. - Free: MIT. No paywall, no cloud, no account.
What vx can do, not what it is limited to. Declared in .vx/workspace.ts; without any, vx runs and caches locally.
| Package | What it adds |
|---|---|
@vzn/vx-migrate |
Migrate from Turbo or Nx: write native configs, keep its remote cache |
@vzn/vx-reapi |
Remote cache and remote execution over Bazel's REAPI (NativeLink, BuildBuddy) |
@vzn/vx-lockfile |
Key each project on its own lockfile slice: pnpm, bun, npm, yarn |
@vzn/vx-otel |
OpenTelemetry traces, metrics and logs, no SDK |
@vzn/vx-ci |
GitHub Actions job summary and Checks API |
@vzn/vx-history |
Schedule by the critical path learned from past runs |
@vzn/vx-cache |
Remote cache for CI jobs, on a vx cache server |
@vzn/vx-cache-server |
The cache server you host: Bun or Docker with S3, or Workers with R2 |
@vzn/vx-history-server |
The task timings server vxHistory() reads: Bun, Docker or Workers |
@vzn/vx-devtools |
Code the servers and plugins share: the CI OIDC token check |
@vzn/vx-dte |
Spread one run over workers your own CI starts (not on npm yet) |
@vzn/vx-dte-server |
The relay dte() meets its workers at: Workers, Bun or Docker (not on npm yet) |
Hosting the three servers: servers docs.
Write your own: plugin guide · architecture.
Beside turbo.json or nx.json, bunx @vzn/vx init runs @vzn/vx-migrate:
it writes a vx.config.ts per package from turbo.json or the Nx graph,
and you keep those and delete the old config. vx init --keep writes only
a .vx/workspace.ts that maps the old config instead, and its lock: a temporary start,
not a way to run the repo. The benchmark below uses native vx config.
Migration guide.
| vx | Turborepo | Nx | |
|---|---|---|---|
| Config | TypeScript, evaluated into the cache key | JSON (static) | JSON (static) |
| Output ownership | Strict: wiped before exec and restore | Additive (stale files survive) | Additive |
| Clean-tree hashing | No source reads (git index OIDs) | git OIDs | re-hash / daemon |
| Daemon | None | None for turbo run |
On by default |
| Per-task sandbox | Yes: kernel-level, opt-in, local | No | On Nx Cloud only |
| Plugin API | Yes: every pipeline stage | No | Yes (TS-tied) |
| Install | Single binary; no Bun, Node optional | npm + Node | npm + Node |
Every gap, and when another tool is the better pick: comparison · vx, Turborepo, Nx, Bazel.
- Nothing boots per task: no daemon, no Node process; vx spawns your command.
- No source-file reads to hash: on a clean tree, keys come from git's index; each project's
package.jsonand the lockfile are still read. - One pass: one git walk per run; each stat and lookup happens once.
| Time added over the ideal run, 1,601 projects, 9,603 tasks | vx | Turborepo | Nx | Vite Task |
|---|---|---|---|---|
| Nothing changed | 799 ms | 927 ms | 24.15 s | 12.07 s |
| Nothing changed, outputs restored | 1.28 s | 2.01 s | 24.45 s | 11.86 s |
| Cold build | 7.57 s | 9.63 s | 3 min 6 s | 9.01 s |
| Cold build: CPU the runner burns | 36.64 s | 1 min 12 s | 5 min 34 s | 25.32 s |
Time each tool adds over the ideal run; lower is better.
Benchmark workload: a synthetic monorepo of 1,601 projects and 9,603 tasks in 30 dependency levels, five core libraries a quarter of the projects use; build 300 ms, test and typecheck 150 ms, lint 75 ms, publish 30 ms; real repos with uneven task times will differ. Run 2026-10-25 on linux x64, 4 cores: vx at commit 9abdfb52c, Turborepo 2.11.10, Nx 24.0.1, Vite Task (vite-plus) 1.4.0.
Same graph, commands and concurrency: how it is measured.
Pre-alpha, dogfooded: this repo lints, tests and builds itself with vx. The schema may still change; 1.0's promise: versioning-1.0.md.
vx is maintained by VZN and takes issues, not pull requests: report a bug or ask for a feature in issues.
vx is MIT licensed. The prebuilt binaries embed Bun and a few npm packages under permissive licenses; their notices ship as THIRD_PARTY_NOTICES.txt in each platform package and GitHub release.