
Distributed task execution, a cache you host, exact restores
Spread one CI run over workers your own CI starts, through a relay that stores nothing. Plus task timings and a remote cache on servers you host, and exact restores.
Both task lists share one grid with each task's time, wait, cpu, memory, outputs and worker. A run's steps, warnings and failures are tables vx last --sql reads.

A run now shows what each task cost and where it ran, in one grid. What a run leaves behind is in one database you can query.
This release is breaking. Read the breaking changes first.
In this release
To find a slow or memory-hungry task you had to read logs or run vx last.
Now the live list and the results list share one grid under one header:
$ vx run build lint test --all time ±usual waited status cache cpu mem outputs blocks worker task ⏺︎ 205ms success miss 1% 0 local app#lint ⏺︎ 310ms success miss 2% 1 local app#build ⏺︎ 202ms success no-cache 1% 0 local app#testusual is a task’s median time from local history while it runs, and
±usual is how far it landed from it once it is done. waited is
time spent ready but waiting for a slot. blocks counts the tasks
downstream of it.
A task with no cache block reads no-cache. worker is local
unless a plugin ran the task elsewhere.
See vx run.
A run’s steps, its warnings and each failed task’s output were JSON
files under the cache dir, out of reach of vx last --sql.
They are now the steps, warnings and failures tables of
cache.db:
$ vx last --sql "SELECT task_id, output FROM failures"task_id outputapp#check 2 type errorsA lockfile plugin’s memo is the lockfile_memos table. Artifacts and
outputs stay files.
See vx last.
A plugin got cache.db itself through Cache.dbHandle(). It could read
or write any table, and a schema change could break it silently.
localHistory(cache, …) is now the only way in, and each call returns
plain rows:
import { Cache, localHistory } from '@vzn/vx'
const cache = Cache.inspect(ctx.cacheDir)try { const table = await localHistory(cache).loadFor(argv)} finally { cache.close()}See Writing plugins.
@vzn/vx-cache sends it again,
three tries in all.no-terminal, before its question prints.vx upgradeThe first run after upgrading resets run history (schema v50;
artifacts stay).
<cacheDir>/steps/, warnings/, failures/ and lockfile-claims/ → the steps, warnings, failures and lockfile_memos tables.Cache.dbHandle(), LocalHistoryProvider → localHistory(cache, …).Logger.taskStart(node) → taskStart(node, info?) with info.waitedMs.See also Upgrading.