UID Pressure Reduction: Let one miner UID handle multiple concurrent queries
Problem
Currently one miner UID = one process = one unit of work. Miners who want more capacity must register more UIDs, creating UID pressure on the subnet. With 250 UIDs we score 750 responses/hour (1 per type per miner). Organic requests flow through one validator but aren't scored at all. The private utility API generates low-quality synthetics.
Proposal
Let a single UID declare and serve multiple concurrent queries per search type. Validators verify capacity over time by ramping from 1, and reward proven capacity with more queries and higher scores. Organics become part of scoring. A superlinear incentive formula makes consolidating under one UID strictly more profitable than splitting across many.
1. Miner architecture: axon/worker split
Split the miner into a lightweight axon (registered on-chain, advertises config via IsAlive) and a worker API behind a load balancer that serves queries. Validators call the worker API directly.
Miner publishes config through IsAlive:
{
"worker_url": "http://ip:port",
"concurrency": { "web_search": 20, "x_search": 15, "ai_search": 10 }
}
Concurrency is a per-validator ceiling. A miner advertising 20 with 12 validators must handle up to 240 concurrent requests. Infrastructure sizing is the miner's responsibility.
2. Concurrency verification
Validators don't trust declared concurrency. Every new miner starts at 1 query per search type per hour. Each scoring window (1 UTC hour), the validator checks quality at the current level. Good quality → increase by up to 5% of declared (minimum +1). Poor quality → cut by 30% immediately. Declared concurrency is only a ceiling.
A miner declaring 100 ramps at +5/window and reaches full capacity in ~20 hours. A miner declaring 100 but only capable of 10 gets caught by the capacity liar defense (see section 9). Hard cap at 100 per search type.
3. Replace synthetic generation
Replace the private utility API with the existing LLM dataset generation logic in desearch/dataset. Each validator generates its own synthetics independently. The utility API is retired.
Each hour, the validator sends as many synthetic queries per miner per search type as that miner has earned through the ramp. Queries are spread across ~55 minutes with random inter-arrival times so miners can't distinguish them from organics by timing.
Future improvement: make synthetic content statistically indistinguishable from organics. Ships later, no protocol changes needed.
4. Organic routing
Organics are routed to miners weighted by quality × earned concurrency (verified, not declared). All organics are routed and answered — no queries are dropped. Organics and synthetics use the same code path, headers, and timing jitter — miners cannot tell them apart.
5. Scoring
Flow
Scoring runs once per hour after responses are collected:
- Score each search type independently (AI, X, Web).
- Combine into one score per miner using weights (AI 50%, X 25%, Web 25%).
- Apply one EMA update to the running score.
- Adjust earned concurrency per search type based on step 1 quality.
The EMA update happens once on the combined score — not per search type.
Synthetics: all deep-scored
Every synthetic response gets full scoring (LLM pipeline for AI search, re-scraping + verification for X/Web). Cost is bounded because synthetic volume per miner equals earned concurrency, which grows slowly.
Organics: code-check all, deep-score a sample
Currently organics aren't scored at all. This changes.
Every organic response gets fast code-based checks: timeout, format, required fields, deduplication, structural sanity. Failing any check → score 0.
A sampled portion of passing organics also get deep-scored using the same pipeline as synthetics. The budget is fixed per validator per hour per search type (e.g. 250 for web search). The validator picks randomly from all passing organics across all miners. Since organics are already routed proportionally to earned concurrency, miners handling more traffic naturally get more deep-score samples:
- 5,000 organic web responses, budget 250 → 5% sample rate
- Miner A (earned concurrency 100) received ~500 organics → ~25 deep-scored
- Miner B (earned concurrency 10) received ~50 → ~2-3 deep-scored
- Miner C (earned concurrency 1) received ~5 → 0 or 1 this hour
If volume is below budget, all organics get deep-scored. As volume grows, sample rate drops but cost stays flat. Deep-scoring every organic is not viable: LLM calls and live re-scraping would make validator costs scale linearly with user traffic. Sampling also prevents self-sent organic abuse (section 9).
Organic deep scores count 5x
Deep-scored organic responses carry 5x weight vs synthetics in the quality average. A miner serving organics well gets a significant quality boost. A miner returning garbage hoping it won't be sampled risks a 5x penalty on the ones that do get sampled. Since miners can't tell which organics will be deep-scored, they must treat all of them seriously.
Zero organics in a window
Score based entirely on synthetics. No penalty — just fewer data points, EMA smooths it.
6. Incentive formula
score = quality^alpha * volume^beta - failure_penalty
Both exponents greater than 1 (superlinear). This is the core sybil defense: splitting volume across N UIDs multiplies total reward by N^(1-beta), always less than 1. Consolidation is strictly more profitable than splitting.
This also replaces separate rate limiting. If a miner fails → earned concurrency drops → fewer queries → volume drops → superlinear formula punishes it. The scoring formula is the rate limit.
7. Unreachable miners
Two failure modes:
Axon not responding to IsAlive — detected on metagraph resync. No queries sent.
Worker URL failing but axon alive — after a few consecutive worker failures, validator immediately removes miner from organic routing so users stop getting failed results. Synthetics continue as health probes to detect recovery.
Recovery penalty: earned concurrency decays by 10% every 5 minutes while down. Down 10 minutes → recovers at ~80%. Down 50 minutes → back to 1, full re-ramp. On first successful response, miner re-enters organic pool at whatever earned concurrency remains after decay.
8. Persistence between restarts
Without persistence, a validator restart resets every miner to earned concurrency 1 and re-ramps over ~24 hours.
Use SQLite to store per-miner state with 3-day rolling retention: earned concurrency and quality average per search type, unreachable status, per-window scoring history (quality, volume, timing, pass/fail counts).
9. Attack mitigations
Capacity lying (declaring high, can't deliver)
A miner declares 100 but can only handle 10. It ramps up fast (+5/window), hits 10, starts failing, gets cut 30%, ramps again, fails again — oscillating.
Defense: validator tracks failed scoring windows over a rolling 12-hour period. After repeated failures (e.g. 4+ in 12 hours), earned concurrency is frozen at the highest sustained level and ramping is blocked for 12 hours. Miner can still serve and score at its proven level, just can't climb higher. After 12 clean hours, normal ramping resumes.
Honest miners reach 100 in ~20 hours unimpeded. Liars get capped at their real capacity.
Sybil (splitting into multiple UIDs)
- Superlinear scoring — splitting always loses mathematically
- Earned concurrency is proportional — two UIDs at 100 get the same total queries as one at 200, no extra allocation
- One hotkey per coldkey — validators zero-weight coldkeys with multiple active hotkeys
- IP clustering — multiple UIDs on the same IP range are flagged
- Registration cost at protocol maximum
Self-sent organics (Desearch API abuse)
- Sampled deep scoring — self-sent organics don't get guaranteed scoring opportunities
- Origin-aware dampening — disproportionate routing from one API caller to one miner reduces that miner's organic weight
- Deep scoring re-scrapes live data — pre-computed answers don't help
- Quality delta monitoring — miner scoring much better on organics than synthetics is flagged
- Attacker pays API fees — economically self-limiting at scale
Implementation steps
Each step is independently shippable and reversible.
- Synthetic generator. Replace private utility API with LLM dataset generation.
- Miner config + worker API. Axon/worker split. Config via IsAlive. All miners start at earned concurrency 1.
- Scoring pipeline. Deep scoring on synthetics, code checks + sampled deep scoring on organics, single EMA update, 5x organic deep-score weight.
- Concurrency ramp. Validators send earned-concurrency queries per miner. Ramp up on quality, cut on failure.
- DB persistence. Survive restarts. 3-day retention.
- Organic routing. Quality × earned concurrency weighting.
- Incentive formula. Shadow-calculate for one epoch, then go live.
- Public metrics API. Per-miner stats, quality scores, concurrency history. Includes: per-miner earned concurrency / declared concurrency / quality average / failure rate / organic volume / synthetic volume per search type, subnet-wide organic failure rate / wait time percentiles / total volumes. Public endpoint on each validator, no auth, retained for at least 30 epochs.
- Synthetic content improvement. Match organic distribution.
UID Pressure Reduction: Let one miner UID handle multiple concurrent queries
Problem
Currently one miner UID = one process = one unit of work. Miners who want more capacity must register more UIDs, creating UID pressure on the subnet. With 250 UIDs we score 750 responses/hour (1 per type per miner). Organic requests flow through one validator but aren't scored at all. The private utility API generates low-quality synthetics.
Proposal
Let a single UID declare and serve multiple concurrent queries per search type. Validators verify capacity over time by ramping from 1, and reward proven capacity with more queries and higher scores. Organics become part of scoring. A superlinear incentive formula makes consolidating under one UID strictly more profitable than splitting across many.
1. Miner architecture: axon/worker split
Split the miner into a lightweight axon (registered on-chain, advertises config via IsAlive) and a worker API behind a load balancer that serves queries. Validators call the worker API directly.
Miner publishes config through IsAlive:
{ "worker_url": "http://ip:port", "concurrency": { "web_search": 20, "x_search": 15, "ai_search": 10 } }Concurrency is a per-validator ceiling. A miner advertising 20 with 12 validators must handle up to 240 concurrent requests. Infrastructure sizing is the miner's responsibility.
2. Concurrency verification
Validators don't trust declared concurrency. Every new miner starts at 1 query per search type per hour. Each scoring window (1 UTC hour), the validator checks quality at the current level. Good quality → increase by up to 5% of declared (minimum +1). Poor quality → cut by 30% immediately. Declared concurrency is only a ceiling.
A miner declaring 100 ramps at +5/window and reaches full capacity in ~20 hours. A miner declaring 100 but only capable of 10 gets caught by the capacity liar defense (see section 9). Hard cap at 100 per search type.
3. Replace synthetic generation
Replace the private utility API with the existing LLM dataset generation logic in
desearch/dataset. Each validator generates its own synthetics independently. The utility API is retired.Each hour, the validator sends as many synthetic queries per miner per search type as that miner has earned through the ramp. Queries are spread across ~55 minutes with random inter-arrival times so miners can't distinguish them from organics by timing.
Future improvement: make synthetic content statistically indistinguishable from organics. Ships later, no protocol changes needed.
4. Organic routing
Organics are routed to miners weighted by quality × earned concurrency (verified, not declared). All organics are routed and answered — no queries are dropped. Organics and synthetics use the same code path, headers, and timing jitter — miners cannot tell them apart.
5. Scoring
Flow
Scoring runs once per hour after responses are collected:
The EMA update happens once on the combined score — not per search type.
Synthetics: all deep-scored
Every synthetic response gets full scoring (LLM pipeline for AI search, re-scraping + verification for X/Web). Cost is bounded because synthetic volume per miner equals earned concurrency, which grows slowly.
Organics: code-check all, deep-score a sample
Currently organics aren't scored at all. This changes.
Every organic response gets fast code-based checks: timeout, format, required fields, deduplication, structural sanity. Failing any check → score 0.
A sampled portion of passing organics also get deep-scored using the same pipeline as synthetics. The budget is fixed per validator per hour per search type (e.g. 250 for web search). The validator picks randomly from all passing organics across all miners. Since organics are already routed proportionally to earned concurrency, miners handling more traffic naturally get more deep-score samples:
If volume is below budget, all organics get deep-scored. As volume grows, sample rate drops but cost stays flat. Deep-scoring every organic is not viable: LLM calls and live re-scraping would make validator costs scale linearly with user traffic. Sampling also prevents self-sent organic abuse (section 9).
Organic deep scores count 5x
Deep-scored organic responses carry 5x weight vs synthetics in the quality average. A miner serving organics well gets a significant quality boost. A miner returning garbage hoping it won't be sampled risks a 5x penalty on the ones that do get sampled. Since miners can't tell which organics will be deep-scored, they must treat all of them seriously.
Zero organics in a window
Score based entirely on synthetics. No penalty — just fewer data points, EMA smooths it.
6. Incentive formula
Both exponents greater than 1 (superlinear). This is the core sybil defense: splitting volume across N UIDs multiplies total reward by N^(1-beta), always less than 1. Consolidation is strictly more profitable than splitting.
This also replaces separate rate limiting. If a miner fails → earned concurrency drops → fewer queries → volume drops → superlinear formula punishes it. The scoring formula is the rate limit.
7. Unreachable miners
Two failure modes:
Axon not responding to IsAlive — detected on metagraph resync. No queries sent.
Worker URL failing but axon alive — after a few consecutive worker failures, validator immediately removes miner from organic routing so users stop getting failed results. Synthetics continue as health probes to detect recovery.
Recovery penalty: earned concurrency decays by 10% every 5 minutes while down. Down 10 minutes → recovers at ~80%. Down 50 minutes → back to 1, full re-ramp. On first successful response, miner re-enters organic pool at whatever earned concurrency remains after decay.
8. Persistence between restarts
Without persistence, a validator restart resets every miner to earned concurrency 1 and re-ramps over ~24 hours.
Use SQLite to store per-miner state with 3-day rolling retention: earned concurrency and quality average per search type, unreachable status, per-window scoring history (quality, volume, timing, pass/fail counts).
9. Attack mitigations
Capacity lying (declaring high, can't deliver)
A miner declares 100 but can only handle 10. It ramps up fast (+5/window), hits 10, starts failing, gets cut 30%, ramps again, fails again — oscillating.
Defense: validator tracks failed scoring windows over a rolling 12-hour period. After repeated failures (e.g. 4+ in 12 hours), earned concurrency is frozen at the highest sustained level and ramping is blocked for 12 hours. Miner can still serve and score at its proven level, just can't climb higher. After 12 clean hours, normal ramping resumes.
Honest miners reach 100 in ~20 hours unimpeded. Liars get capped at their real capacity.
Sybil (splitting into multiple UIDs)
Self-sent organics (Desearch API abuse)
Implementation steps
Each step is independently shippable and reversible.