The local agent

The agent detects the machine's hardware, builds an allocation plan for it, and applies that plan through workload drivers on a re-planning loop, dry-run by default.

Commands


bun run src/agent-cli.ts detect          # hardware detection + profile match
bun run src/agent-cli.ts plan            # allocation plan for THIS machine, then apply
bun run src/agent-cli.ts run [minutes]   # re-plan + apply loop, default every 15 minutes

The first run materializes data/agent.json with defaults (kwhUsd: 0.1, ipSlots: 1, dryRun: true).

Hardware detection

src/agent/detect.ts reads the machine without any external dependency:

Detected device names are matched against the profile registry (src/profiles.ts) by normalized substring, longest labels first, so "RTX 3060 Ti" matches the 3060 Ti profile and never falls through to "RTX 3060". A special matcher maps any "Apple M1/M2/M3/M4" string to the apple-silicon profile. GPU candidates are tried before the CPU because the GPU is the valuable contended resource.

Profiles and generic fallbacks

Each profile carries seed benchmarks: per-algo mining hashrate and watts (kawpow, autolykos, etchash, randomx), supported NiceHash algos, the exact Vast.ai gpu_name, rented power draw, and a Salad-class $/month estimate. Every number is an explicitly commented seed from public benchmark data, to be replaced by the local auto-benchmark (src/agent/benchmark.ts) and real measured earnings.

When a device is detected but not in the registry:

Generic fallbacks are never matched by label: they are only selected explicitly when nothing else matched. apple-silicon has no supported compute destination in v0 (no CUDA, RandomX not profitable): such a machine only earns through its stack.

The machine plan

src/plan.ts (buildPlan) merges the two engines into one MachinePlan:

Plan semantics:

Executor and drivers

src/agent/executor.ts applies the plan. A WorkloadDriver knows how to start and stop one workload family and declares which plan items it matches. The registered drivers, in order:

  1. MinerDriver (src/agent/drivers/miner.ts): matches Mine <COIN> (<algo>) and NiceHash <ALGO> items. It translates them into concrete miner command lines: xmrig for RandomX, t-rex for the GPU algos (kawpow, autolykos2, etchash; zelhash is intentionally unsupported, t-rex cannot mine it). Pool endpoints per coin (QUAI, RVN, ERG, ETC via HeroMiners/2Miners, XMR via SupportXMR) and the unified NiceHash stratum scheme are built in, each marked as an estimate to verify before production use. In dry-run, or when the binary is not configured or absent from disk, it logs the exact command line. Otherwise it spawns the process via Bun.spawn, enforcing one miner at a time.
  2. RentalDriver (src/agent/drivers/rental.ts): matches Vast.ai items. Pricing strategy: the market p25 ask (re-queried live from the public bundles API when possible, otherwise parsed from the plan item name) minus a 2% undercut. Without a vastApiKey it logs the intended list/reprice or unlist action. With a key it lists or reprices your registered machines and delists them on stop.
  3. DryRunDriver "inference": matches inference/Salad items, log-only in this phase.
  4. DryRunDriver "depin": catch-all fallback for points and bandwidth clients, log-only.

applyPlan is idempotent on the primary: if the primary is unchanged it does nothing; on a switch it stops the previous driver, then starts the new one, unless the primary is IDLE, in which case the compute stays off. Stack items are then started through their matching drivers.

The run loop

bun run src/agent-cli.ts run [minutes] prints the detection, builds and applies a plan immediately, then re-plans on a fixed interval (default 15 minutes, ctrl-c to stop). Each cycle re-reads live market data, so a profitability window or a rental price move changes the primary on the next tick, and the executor performs exactly one switch.