A DTE relay that stores no message
A DTE relay that stores no message
vx DTE's relay keeps no message. It passes frames between sockets, and the two ends make sure each one lands.
vx's CI is one run spread over its own Linux and Mac runners. 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.
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
The first green main as one distributed 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 |
flowchart TB orch[your CI job: vx run, the orchestrator] api[CI API] workers[your CI jobs: vx dte worker, one per worker] relay[(relay: one socket each, nothing stored)] cache[(your remote cache)] orch -->|dispatch, cancel| api api --> workers orch <-->|orders and status, encrypted| relay workers <-->|encrypted| relay workers -->|save outputs by key| cache cache -->|restore by key| orch
vx run. It plans how many
workers the misses need, starts them and hands out tasks. It runs none
itself.vx dte worker in a CI job. It runs what it is sent
and saves outputs to your cache under the task’s key.Adapters start workers: github(), gitlab(), local() (git
worktrees) and docker().
@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.
xychart-beta title "Relay frames, quiet run, 4 sockets, 10 heartbeats" x-axis ["relay pings every socket", "peers probe only the quiet"] y-axis "frames" 0 --> 100 bar [92, 60]
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
sequenceDiagram participant W as worker participant R as relay participant O as orchestrator W->>R: open socket, join (CI OIDC token) R->>O: a worker joined W->>R: Noise message 1: e (psk), join info encrypted R->>O: message 1 O->>R: Noise message 2: e, ee R->>W: message 2 Note over W,O: transport keys exist only on W and O O->>R: order (task, keys, env), encrypted R->>W: order W->>R: begun, ran, saved, encrypted R->>O: status
Each worker runs a Noise
handshake with the orchestrator, on WebCrypto, checked against the
cacophony vectors (packages/vx-dte/src/noise.ts).
VX_DTE_SECRET) it is
Noise_NNpsk0_25519_AESGCM_SHA256. On GitHub Actions with no key it is
Noise_NN_25519_AESGCM_SHA256, bound by OIDC tokens of the same repo.Only misses need workers. A run the cache answers whole starts none.
packages/vx-dte/src/planner.ts)
starts the fewest that end within 5% of the best. Idle workers leave.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) |
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.tsFive ways, on 4 vCPU machines that take 20 s to start:
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% |
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
pie showData title "This repository's CI runs, 7 days to 2026-10-23" "PR" : 451 "main" : 304 "manual" : 5
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.
Not on npm yet: import dte and github from a checkout’s
packages/vx-dte/src/index.ts
(README),
and host a relay
(README):
// in .vx/workspace.ts's pluginsdte({ relay: 'https://relay.example.com', workers: { 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.