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:
- macOS:
sysctlfor CPU model and RAM. On Apple Silicon the SoC is treated as the GPU. - Linux:
/proc/cpuinfoand/proc/meminfo, plusnvidia-smi --query-gpu=namefor GPUs.
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:
- an unknown NVIDIA GPU (name matching nvidia/geforce/rtx/gtx/quadro/tesla) falls back to
generic-gpu, with deliberately conservative floor numbers (GTX 1660 class) and no Vast rental destination, since Vast requires the exact model name; - otherwise an unknown x86 CPU (intel/amd/ryzen/xeon/etc.) falls back to
generic-cpu(6-core desktop class RandomX floor).
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:
- the oracle (
rankDestinationsinsrc/engine.ts): net USD/day on mature markets (direct mining via WhatToMine, NiceHash hashpower sale, Vast.ai rental, Salad-class inference), using live third-party data; - the scorer (
scoreAllover the internal registry): risk-adjusted EV for networks no third party covers.
Plan semantics:
- PRIMARY: the single best use of the machine's contended compute resource (GPU or CPU). The scorer's best compute-contending network wins over the oracle's best destination only if its risk-adjusted USD/day is strictly higher. The item name is tagged with its origin:
[oracle, cash]or[tier X, scorer]. Tier D primaries are markedspeculative: true(EV, not cash). - IDLE: if the best primary nets under $0.01/day, the plan replaces it with an explicit IDLE item at $0/day: no destination beats the electricity cost, do not power the compute. IDLE is a recommendation the executor honors by shutting the compute workload down.
- STACK: additive workloads that do not contend for the compute resource: up to
ipSlotspositive-EV networks contending forip_slot(each carrying arisque ban: respecter 1 device/1 IPwarning when the registry flags high ToS ban risk), plus all positive-EV networks contending for nothing (none). - nextBest: the runner-up compute destination, kept visible for context.
- totalUsdDay: primary plus the sum of the stack.
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:
- MinerDriver (
src/agent/drivers/miner.ts): matchesMine <COIN> (<algo>)andNiceHash <ALGO>items. It translates them into concrete miner command lines:xmrigfor RandomX,t-rexfor 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 viaBun.spawn, enforcing one miner at a time. - 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 avastApiKeyit logs the intended list/reprice or unlist action. With a key it lists or reprices your registered machines and delists them onstop. - DryRunDriver "inference": matches inference/Salad items, log-only in this phase.
- 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.