v0.3.0 on npmChangelog

better/compute v0.3.0·GPUs, CPUs and sandboxes

Every GPU and coreaccounted for.Nothing booked twice.

A TypeScript control plane you embed. It knows what exists, what is free and who holds it, and Postgres referees every booking, from a whole NVLink set to a few vCPUs.

  • Adopts running servers
  • Deploys bare metal
  • Runs sandbox VMs
  • Better IAM decides
FleetSimulated
  1. gpu-018/8
  2. gpu-028/8
  3. gpu-035/8
  4. gpu-040/8
  5. gpu-05cordoned
  6. gpu-060/8
  7. gpu-077/8
  8. cpu-0128/32 vCPU
  9. cpu-020/32 vCPU
  10. cpu-030/32 vCPU

Events

  1. +rsv_TZP4 gpu-03 3×H100
  2. +rsv_9JRN gpu-01 2×H100
  3. −rsv_ME0B gpu-01
GPUs
28/48
vCPUs
28/96
Pending
0
Overlaps
0

Simulated, not live data: whole NVLink sets for 4-GPU jobs, sandbox VMs claiming vCPUs, and a request that fits nowhere stays pending. Point at the board to hold it.

U01Capacity

One ledger for GPUs, CPUs and sandboxes

Every kind of capacity comes out of the same pools, the same quotas and the same Postgres tables, so a GPU reservation and a sandbox can never take what the other holds.

Reserve GPUs for a window of time. A booking holds atoms (whole GPUs, MIG instances or time slots) on one node, prefers complete NVLink sets, and binds them on the node as CDI devices for your own workload manager.

Booked as
device atoms over a time range
Guard
EXCLUDE on device bookings
Topology
'any' or 'nvlink-all'
Metered
gpu.<model>.hours to Better IAM
GPU reservations
const reservation = await client.reservations.create({
  tenantId,
  poolId: 'pool_h100',
  devices: [{ class: 'gpu', count: 4, topology: 'nvlink-all' }],
  duration: 8 * 3_600_000, // 8 h
  binding: 'hold',
  idempotencyKey: crypto.randomUUID(),
});
// One IAM decision on the draft, then four GPUs booked on one node.
// Bound on the node as CDI devices: state 'active'.

U02Signal path

From one API call to a machine that converged

A request is decided once and written once. Everything after that is workers moving rows and daemons converging toward them, so a lost message is repaired by the next pass instead of lost with it.

  1. S01Callerclient · CLI · MCP · console
    • ›a Better IAM credential
    • ›POST /api/compute/{group}/{method}
  2. S02API envelopevalidate, then decide once
    • ›one IAM decision on the draft
    • ›X-Better-Compute CSRF header
  3. S03Postgresthe only source of truth
    • ›booking and outbox in one transaction
    • ›EXCLUDE: no double booking
  4. S04Reconcilerslevel-triggered workers
    • ›work queue, 256 shards, leases
    • ›desired state by generation
  5. S05Gatewayholds the node sessions
    • ›mTLS, outbound from nodes
    • ›control · provision · bulk lanes
  6. S06bcd on the nodeconverges the machine
    • ›journal: nothing runs twice
    • ›CDI devices, VMs, reports back
Reports flow backNodes stream inventory, status and command results to the gateway, which writes them to Postgres with dedupe; reconcilers pick them up from there.
No IAM on the fleet pathJoins, heartbeats, inventory and commands never call Better IAM. A ten-minute simulated run asserts zero calls.
Disconnected is not deadA node that goes quiet turns unreachable and unschedulable. Nothing it holds is released, and bare metal is never declared lost on its own.

U03Deep dives

Eight channels into the machine room

Each channel is a working model of one part of the system: race two placements, edit an audit row and watch its proof fail, cut a node’s session mid-command. Pick a channel; only that one runs.

Machines join with a token, never a password

A node only dials out. It trades a single-use join token, sealed to the JoinService key and bound to its CSR, for a 24-hour SPIFFE certificate, reports its inventory, and passes acceptance before anything can book it.

How a node joins

Attested join · join-token

Attestor

A token from joinTokens.create: single use by default, stored as a hash, sealed in transit. Built in.

Try an outcome

Join

  1. bcdKey and CSR(running)

    A fresh P-256 key, in the TPM when there is one, and a PKCS#10 CSR. The signed answer will name its sha256.

  2. bcdSealed join token

    Reads bcjt_<id>.<secret> from BCD_JOIN_TOKEN or stdin and seals it to the pinned JoinService key: a JWE whose header names sha256(csr).

  3. gatewayJoin, relayed

    AgentGateway.Join over TLS pinned to the serving CA. The gateway forwards it to JoinService in api; it holds no CA and no signing key.

  4. apiAttestor verifies

    One transaction locks the join_tokens row: the secret in constant time, revocation, expiry at compute_now(), uses left, role, then the expect binding. One use is spent.

  5. apiNode and certificate

    A new node in adopting and a 24 h certificate with URI SAN spiffe://<td>/site/<site>/node/<id>, role node. The response is signed with the pinned join-signing key.

  6. gatewaySession opens

    bcd checks the signature against its pin, csr_sha256 and the SPIFFE SAN, then sends Hello over mTLS. The gateway bumps the epoch and answers Welcome: heartbeat 10 s, lease 40 s.

  7. workerAdopted

    The first inventory derives the devices. NodeLifecycle runs the pool’s acceptance checks, such as diag.nvml; on a pass the node is adopted and NodeSpec writes generation 1.

Node

no node yetwaiting
provision_state—
connectivity—
session_epoch—
certificate—
uses_left1

Answer

Waiting for JoinService

Every refusal looks the same to the agent; only the audit event carries the reason. Simulated from JoinService and the pki attestors: join-token is built in, the others are opt-in through betterCompute({ attestors }). The Go daemon’s bcd join runs the join-token path end to end against the real gateway (not released yet); the other attestors’ agent halves run in the TypeScript agent in tests.

U04Guarantees

The datasheet

What holds under load, crashes and partitions, the mechanism behind each guarantee, and the test or tool in the repository that checks it.

GuaranteeMechanismChecked by
Nothing is booked twiceEXCLUDE constraint on device bookings over time ranges; guarded counters for vCPU and memory; a fixed lock orderI1 in the fleet simulator · tests/capacity-torture.test.ts
A command runs at most onceCommands written to Postgres first, delivered on lanes with credits, deduplicated by the daemon’s journalI2 in the fleet simulator, with crashes and journal loss
Disconnected is not deadLiveness only moves a node to unreachable; releases need an allowed cause; bare metal is never lost automaticallyI4 and I5 in the fleet simulator, with partitions and gateway kills
One decision per request, none on the fleet pathThe envelope asks Better IAM once, on the draft; joins, heartbeats and commands never call ittests/sim/iam-negative.test.ts: zero IAM calls in a 10-minute run
Only the node can confirm what it didSigned commands and specs; a reservation goes active only on the node’s own signed AllocationBoundtests/e2e/attested-bindings.test.ts: a gateway-forged binding is rejected
The audit trail is provableHourly signed Merkle checkpoints per tenant (RFC 6962) over audit_events, in commit orderbetter-compute audit verify · audit prove

U06Quickstart

From npm install to a booked GPU

PGlite in memory, an embedded Better IAM, and simulated machines that join through a real gateway: the whole control plane on your laptop. Add --sim-variant cpu for machines without GPUs.

tty · better-compute
# 1. Install from npm
$ npm install better-compute better-iam @electric-sql/pglite
$ alias better-compute="npx better-compute"
# 2. A control plane with eight simulated H100 machines
$ better-compute dev --memory --sim 8
better-compute listening on http://127.0.0.1:3000/api/compute
Workers running: controllers, scheduler, sinks, iam
8 simulated node(s) adopted
# 3. Book four GPUs for an hour
$ better-compute reserve --pool $POOL --gpus 4 --for 1h
$ better-compute reservations get rsv_… --query state
active
$

Run your own GPU, CPU and sandbox capacity, with the guarantees of a database

Version 0.2.0 is on npm: install better-compute and better-iam, and the control plane runs on PGlite or PostgreSQL. bcd’s node agent is built and tested but not released yet, and nothing has run on real GPU hardware, so machines join as simulated daemons for now.

Built and maintained by Sean Filimon