A build tool shouldn't make you choose between fast and flexible, or between powerful and free. vx is fast and simple from the first task, fully extensible in real TypeScript as you scale, and yours to keep — no daemon, no proprietary cloud, nothing to lock you in.
// only in vx
vx.config.ts is evaluated before hashing, so imports, shared presets and computed values all participate in the cache key. Change a preset and exactly the right tasks re-run. Turbo and Nx hash static JSON — they can’t see this.
Declared outputs are wiped before every run and every restore, so your tree ends bit-identical to the cache — stale files cannot survive. Turbo and Nx are additive: leftovers linger.
One binary, no Node, nothing long-lived. On a clean tree, every cache key comes from the git index with zero file reads — 3.2× faster warm at 15k files. Nx needs a daemon for this; vx doesn’t.
Run any task under an OS-level file + network allow-list with sandbox: {}. An undeclared read or a phone-home fails the build instead of hiding it. No other runner has it.
Trust-scoped remote cache: a fork PR reads main’s cache to stay fast but can only write its own quarantined scope — a poisoned artifact never reaches a trusted build. Artifacts are immutable, and HMAC signing verifies bytes on top. The GitHub-Actions model, server-enforced.
vx lock freezes the fully-resolved task graph into vx-lock.json; --frozen runs skip evaluation entirely and run exactly that graph. No other runner has an equivalent.
// beyond a task runner
One binary. Eight surfaces. Every contract is documented; every wire is JSON-RPC 2.0. Subscribe, query, drive, extend — your stack, your tools, your data.
vx mcp boots a Model Context Protocol server over stdio. Claude Code, Cursor, Continue.dev query cache stats, run history, and rebuild causes through the standard agent-tool protocol. No other runner ships an MCP surface.
Learn more →Register plugins in vx.workspace.ts to subscribe to lifecycle hooks. Forward outcomes to Sentry, post to Slack, ship metrics anywhere. Vite-style lifecycle hooks, crash-isolated per hook.
Learn more →predictive: true and the scheduler reads run history, computes expected remaining critical-path per task, and dispatches by it. The only task runner that learns from itself.
Learn more →Core is only a task runner — fully offline, no account, no service. A dashboard, remote cache, distributed execution and telemetry export are plugins you declare in vx.workspace.ts, first-party or your own. Core depends on none of them.
Learn more →An optional self-hosted CI platform: a dashboard, a trust-scoped shared remote cache, distributed CI across an agent pool, and MCP over HTTP — one docker compose stack backed by Postgres + S3, with accounts and RBAC. Your workspaces connect to it; core never depends on it.
Learn more →// 3,270 tasks
Cold build · from scratch
Fully cached · nothing to rebuild
Restoring outputs · cache → disk
CPU burned · cold build, user time
vx builds the whole graph in under 4 minutes where Turborepo and Nx take over 8 — burning ~50× less CPU doing it. Once cached, every repeat run replays in under a second, and Nx never catches up.
// configuration
Not JSON, not YAML. vx.config.ts is evaluated before hashing, so imports,
shared presets and computed values all participate in the cache key. Tasks are shell
commands; declare inputs and outputs with full type-safety.
// vx.config.ts — next to any package.jsonimport { defineProject } from '@vzn/vx'export default defineProject({tasks: {build: {exec: { command: 'tsc -b' },dependsOn: ['^build'],cache: {inputs: { files: ['src/**'] },outputs: { files: ['dist/**'] },},},test: {exec: { command: 'bun test' },dependsOn: ['build'],cache: { inputs: { files: ['src/**'] }, outputs: { files: [] } },},},})
// migrate
vx migrate converts your turbo.json or Nx project graph into a real vx.config.ts. The
mapping is mechanical — keep your task graph and your speed, drop the daemon and the
plugins.