Alpha
@vzn/vx-dteand@vzn/vx-dte-serverare alpha. They are open source,
MIT, in the vx
repository, and
vx's own CI runs on them every push.Names and options may still change.
vx's CI runs on its own GitHub runners, spread by @vzn/vx-dte. On Nx
Cloud's Team plan with Nx's agents, the same CI would cost about
$12,192 a year. On vx it costs $0.
| Scenario | Nx Cloud Team, Nx's agents | vx DTE |
|---|---|---|
| this repository | $1,016 a month, $12,192 a year | $0 |
| a 20-person team | $1,902 a month, $22,824 a year | $0 |
This repository: 108 CI runs a day, week to 2026-10-23 · Nx's public prices read 2026-10-23 · Mac work and concurrent connections not counted · the math
One run, two minutes, six workers
The first green main as one DTE run
(#4119,
49adc41) took 1
min 59 s, docs deploy included.
It was three jobs before: Linux, arm64 and macOS.
| What | Count |
|---|---|
| tasks | 129 |
| answered by the local cache | 96 |
| downloaded from the remote cache | 8 |
| ran on workers | 22 |
| workers that ran tasks (4 Linux, 2 Mac) | 6 |
| workers started, then cancelled as not needed | 13 |
A Mac or arm worker starts only when the graph has a task for it
(exec.platforms). Each worker's warm-up and time online:
| Worker | Warm-up | Online |
|---|---|---|
| small#1 | 19.9 s | 47.3 s |
| small#4 | 21.1 s | 58.9 s |
| small#6 | 20.3 s | 19.8 s |
| small#8 | 19.8 s | 52.8 s |
| mac#17 | 37.2 s | 52.5 s |
| mac#19 | 31.9 s | 15.4 s |
How the pieces talk
- The orchestrator is your CI job's
vx run. It plans how many workers the misses need, starts them and hands out tasks. It runs none itself. - A worker is
vx dte workerin a CI job. It runs what it is sent and saves outputs to your cache under the task's key. - The relay passes messages, since CI runners take no inbound connection. It keeps none: each side numbers its messages and sends one again until the other acks it.
- The cache carries every file. No file goes through the relay.
Adapters start workers: github(), gitlab(), local() (git
worktrees) and docker().
The relay stores nothing
@vzn/vx-dte-server depends only on
jose and speaks
PROTOCOL.md.
It has one route: a websocket that joins as it opens. It pushes each
message down the receiver's socket and keeps no copy.
It runs one Durable Object per run on Cloudflare's free plan, or on Bun
in Docker. One var, ALLOW, lists who may join as
issuer:owner/repo:workflow.
1 orchestrator and 3 idle workers · before: 88-96 frames, 44-48 each way · after: 24 up, 36 down (#4309) · each participant joins with one request; Workers bills 20 websocket messages as one request, and the relay writes no storage row (pricing, read 2026-10-23) · how it works
The relay reads nothing it carries
Each worker runs a Noise
handshake with the orchestrator, on WebCrypto, checked against the
cacophony vectors (packages/vx-dte/src/noise.ts).
- With a run key (
VX_DTE_SECRET) it isNoise_NNpsk0_25519_AESGCM_SHA256. On GitHub Actions with no key it isNoise_NN_25519_AESGCM_SHA256, bound by OIDC tokens of the same repo. - Keys are ephemeral, so a leaked run key opens no past run.
- Each task gets only the env its config passes, encrypted.
- Workers borrow the orchestrator's short-lived cache token. Nothing is stored.
How a run schedules
Only misses need workers. A run the cache answers whole starts none.
- How many workers: the planner (
packages/vx-dte/src/planner.ts) starts the fewest that end within 5% of the best. Idle workers leave. - Where a task goes: to a worker with room, holding most of its dependencies' outputs, then the fullest one.
- Which first: the longest tail of recorded time.
- What it books: the cores and memory its history used. No history gets one free core.
Each is a layer you swap with dte({ scheduler })
(packages/vx-dte/src/strategy.ts):
| Layer | Choices (default first) |
|---|---|
order | critical-path (HEFT's rank), graphene (long and wide first, Grandl OSDI'16), fifo |
place | best-fit (fewest cores left), tetris (needs aligned with what is free, SIGCOMM'14), worst-fit, first-fit |
book | measured (history), margin (a fifth more, as Borg does), whole (a worker each) |
scale | plan (the planner above), backlog (cores for what is ready or running, as Spark's dynamic allocation) |
The simulator matches real runs
packages/vx-dte/tests/sim.ts plays a run on a virtual clock with the
plugin's own code, so minutes play in milliseconds.
Main's run at
c5d8353, with
each task's measured time, CPU and memory:
| c5d8353 | Real | Simulated |
|---|---|---|
| run end | 113.8 s | 112.7 s |
| CPU used | 53% | 56% |
| workers | 4 | 4 |
| cancelled workers | 0 | 0 |
Every strategy on main's a09b987 run (65 misses, 4-core workers, 20 s
warm-up). The best end on unlimited workers is 120.5 s:
| Strategy | Simulated end | Workers | Utilization | Worker time |
|---|---|---|---|---|
| default | 120.5 s | 10 | 86% | 13.1 min |
| order: graphene | 120.5 s | 10 | 89% | 12.7 min |
| order: fifo | 120.5 s | 10 | 76% | 14.8 min |
| place: tetris | 120.5 s | 10 | 77% | 14.6 min |
| place: worst-fit | 120.5 s | 10 | 77% | 14.6 min |
| place: first-fit | 120.5 s | 10 | 86% | 13.1 min |
| book: margin | 120.5 s | 12 | 69% | 16.2 min |
| book: whole | 348.9 s | 11 | 19% | 58.9 min |
| scale: backlog | 120.5 s | 12 | 86% | 13.1 min |
bun test packages/vx-dte/tests/sim.test.ts Other ways to split a run
Five ways, on 4 vCPU machines that take 20 s to start:
- One machine: every task on one job.
- Static matrix: N YAML jobs, each a fixed share, tuned once.
- Build stage, then a matrix: one job builds, N jobs split the rest.
- Naive DTE: N workers up front, one task each, all stay to the end.
- vx DTE: the default above.
Machine time is billed 4 vCPU minutes, warm-up included; the 1 vCPU orchestrator counts a quarter · CPU used is work over billed cores · every way but vx DTE played by the ideal packer, vx DTE by the full simulator · bun packages/vx-dte/scripts/compare.ts
vx's main run, a09b987. 65 tasks, 45.0 core-minutes, a flat graph.
| Way | Ends | Machine time | CPU used |
|---|---|---|---|
| One machine | 751.0 s | 12.5 min | 90% |
| Static matrix, 10 jobs | 126.0 s | 18.3 min | 62% |
| Build stage, then 10 jobs | 190.0 s | 18.6 min | 60% |
| Naive DTE, 10 workers | 392.8 s | 67.1 min | 17% |
| vx DTE, 10 workers | 120.5 s | 16.9 min | 67% |
A vx PR. 16 tasks, 9.2 core-minutes. A bar is a task, as tall as
its cores. Orange is warm-up, grey is billed and idle.
| Way | Ends | Machine time | CPU used |
|---|---|---|---|
| One machine | 189.9 s | 3.2 min | 73% |
| Static matrix, 10 jobs | 90.1 s | 13.2 min | 17% |
| Build stage, then 10 jobs | 110.1 s | 13.6 min | 17% |
| Naive DTE, 10 workers | 120.2 s | 20.5 min | 11% |
| vx DTE, 3 workers | 90.1 s | 4.4 min | 53% |
An app monorepo after a base library change. 24 chained libraries,
4 apps, 84 tasks, 71.0 core-minutes.
| Way | Ends | Machine time | CPU used |
|---|---|---|---|
| One machine | 1205.0 s | 20.1 min | 88% |
| Static matrix, 6 jobs | 810.0 s | 73.1 min | 24% |
| Build stage, then 6 jobs | 1060.0 s | 27.3 min | 65% |
| Naive DTE, 6 workers | 765.0 s | 79.7 min | 22% |
| vx DTE, 6 workers | 720.0 s | 34.7 min | 51% |
The same monorepo, one app changed. 3 tasks, 9.3 core-minutes.
| Way | Ends | Machine time | CPU used |
|---|---|---|---|
| One machine | 260.0 s | 4.3 min | 53% |
| Static matrix, 6 jobs | 260.0 s | 10.0 min | 23% |
| Build stage, then 6 jobs | 280.0 s | 7.3 min | 32% |
| Naive DTE, 6 workers | 260.0 s | 27.1 min | 9% |
| vx DTE, 1 worker | 260.0 s | 5.4 min | 43% |
- One machine bills least and ends last when there is work to spread.
- A static matrix fits the graph it was tuned for. On a PR all 10 jobs still start: 13.2 machine-minutes against 4.4.
- A build stage makes every test wait for the slowest build.
- Naive DTE keeps every worker to the end: 17% CPU on main.
- vx DTE ends first or level in all four. One machine bills less when one task sets the end.
What it costs, next to Nx Cloud
Nx Cloud is the closest product. Since 2026-10-22 its dynamic task
packing packs
tasks by measured CPU and memory, as vx DTE does.
The difference is where it runs and what it costs. Running Nx Cloud
yourself is its Enterprise plan, at a custom price.
| Item | Nx Cloud | vx DTE |
|---|---|---|
| Hobby plan | free, 50,000 credits a month, 5 contributors, 10 concurrent CI connections; features turn off when credits run out | — |
| Team plan | $29 a month, $29 of usage included | — |
| Extra credits | $5.50 per 10,000 | — |
| Each CI pipeline execution | 500 credits | nothing |
| Contributors beyond 5 | $19 each | nothing |
| Concurrent CI connections beyond 10 | $2.25 each | nothing |
| Its agents, Linux x64 | 5 credits/min per vCPU (4 vCPU: 20) | your CI's own runners |
| Its agents, Linux arm64 | 26 credits/min for 4 vCPU | your CI's own runners |
| Its agents, macOS | no resource class listed | your CI's own runners |
| Self-hosted | Enterprise plan, custom price | every plan there is: MIT, all of it |
Read 2026-10-23 from nx.dev/pricing, Credit Pricing, CI with Nx Cloud · 30-day months, Team plan, a 4 vCPU agent per worker, reports off, Mac work, concurrent connections and taxes not counted · the $29 fee buys $29 of usage
This repository: 760 runs in a week (GitHub's API, cancelled not
counted), 3,257 a month. From the 54 runs after
#4150, a PR run kept Linux
workers online 4.2 minutes on average, a main run 2.1.
That is 567 credits a run with the 500 per execution: 1,846,529 a month,
$1,016. The Hobby plan's 50,000 credits last 88 runs (per-run
numbers).
A 20-person team, a made-up case: 200 executions a working day, 21
days, 10 agent minutes each. 2,940,000 credits is $1,617, plus 15 seats
at $19. $1,902 a month.
vx DTE has no plan, credits or seats. Its compute is the CI minutes your
runners already bill.
Try it
bun add -d @vzn/vx-dte, and host a relay
(README):
// in .vx/workspace.ts's plugins dte({ relay: 'https://relay.example.com', workers: { linux: { machines: { small: { max: 8, cpu: 4, memory: '16GB' } } } }, adapter: github(), }) The run's job needs id-token: write and actions: write, and a
vx-worker.yml whose jobs run bunx @vzn/vx dte worker.
The package README has both workflows, GitLab's pipeline
and every option. vx's own are ci.yml and
vx-worker.yml.
More at vznjs.github.io/vx.
Originally published on the vx blog. vx is open-source (MIT) task orchestration with a build cache: vznjs.github.io/vx · GitHub.
Written with AI assistance.
