Emission window detector
The watcher diffs each run against a saved market baseline and emits four alert types (NEW_COIN, PROFIT_SPIKE, HASH_BID_SPIKE, RENTAL_PRICE_MOVE), the signals where mining briefly beats everything else.
Why windows matter
On a consumer GPU, steady-state mining loses to inference and rental (see the oracle numbers in the README). Mining only becomes interesting during emission windows: a new PoW chain's first days, an actor overpaying for hashpower, a price pump before difficulty catches up. The watcher exists to catch those windows within one cron interval.
How it works
bun run src/watch-cli.ts (logic in src/watch/detect.ts):
- Fetches three sources concurrently, each failure tolerated independently: WhatToMine
coins.json(BTC/day per hash per coin/algo), NiceHashsimplemultialgo(BTC/day per hash per algorithm), and Vast.ai p25 asks for RTX 4090, RTX 3080 and RTX 3060. - Builds the current snapshot and saves it to
data/snapshot.json. - If no previous snapshot existed, it stops there: "baseline initialized", run again to start detecting.
- Otherwise it diffs current vs previous and appends alerts to
data/alerts.jsonl(JSONL, one alert per line:ts,type,subject,detail, optionalratio).
Thresholds (constants in src/watch/detect.ts): SPIKE = 1.5 (a 1.5x jump vs baseline) for profitability and hash bids, RENTAL_MOVE = 0.3 (plus or minus 30% on the p25 ask).
The four alert types
NEW_COIN: new mineable coin
A coin/algo key present on WhatToMine that was absent from the baseline. This is the "Tari/Nockchain/Quai day one" signal: fresh PoW listings are where early hashrate earns outsized emissions before difficulty adjusts. Each detection also appends a pre-filled registry draft to data/review-queue.jsonl for a human pass before the coin can enter the scorer registry (see docs/scorer.md).
{ "ts": "...", "type": "NEW_COIN", "subject": "TARI/randomx", "detail": "nouveau coin minable detecte sur WhatToMine, evaluer la fenetre d'emission" }
PROFIT_SPIKE: profitability jump on a known coin
BTC/day per hash for a known coin at 1.5x or more vs baseline: a price pump or a nethash drop. Either way, revenue per hash jumped and the window closes as hashrate migrates in.
{ "ts": "...", "type": "PROFIT_SPIKE", "subject": "QUAI/kawpow", "detail": "profitabilite x1.82 vs derniere baseline", "ratio": 1.82 }
HASH_BID_SPIKE: someone overpaying for hashpower
A NiceHash algorithm's paying at 1.5x or more vs baseline: a buyer is bidding above market for that hash. This is the Qubic pattern: in 2025 Qubic bought RandomX hashpower on NiceHash at above-market rates to point it at its own chain, briefly making hash sellers the best-paid actors on the algo. When this alert fires, selling hashpower can transiently beat every other destination.
{ "ts": "...", "type": "HASH_BID_SPIKE", "subject": "RANDOMXMONERO", "detail": "paying NiceHash x2.40: un acheteur surencherit sur ce hash", "ratio": 2.4 }
RENTAL_PRICE_MOVE: GPU rental market move
The Vast.ai p25 ask for a tracked GPU moved 30% or more in either direction. Up: demand surge, reprice your listings (the rental driver prices at p25 minus 2%). Down: rental may have lost the primary slot to another destination.
{ "ts": "...", "type": "RENTAL_PRICE_MOVE", "subject": "RTX 4090", "detail": "p25 ask 0.312 -> 0.418 $/h", "ratio": 1.34 }
Cron usage
The watcher is designed for a ~15 minute cron:
*/15 * * * * cd /path/to/effective-intelligence && /path/to/bun run src/watch-cli.ts >> data/watch.log 2>&1
- First run initializes the baseline and exits; detection starts from the second run.
bun run src/watch-cli.ts --resetdeletesdata/snapshot.jsonso the next run re-initializes the baseline (useful after a long gap, where everything would look like a spike).- Each run prints the alerts found (or "no window detected"), and the current size of the review queue.
- History:
data/alerts.jsonlis append-only; the MCPget_alertstool reads its tail.
Telegram push
If data/agent.json contains a telegram block (botToken, chatId), each run with alerts pushes a summary through the Telegram Bot API (src/watch/notify.ts): one line per alert, message truncated at Telegram's 4096-char cap (the full log stays in data/alerts.jsonl). Without config the push is silently skipped; failures never break the watch run.