Skip to content

feat: replace uniqueness multiplier with graduated pioneer reward mechanism#242

Closed
MkDev11 wants to merge 1 commit intoentrius:testfrom
MkDev11:feat/240-pioneer-reward-mechanism
Closed

feat: replace uniqueness multiplier with graduated pioneer reward mechanism#242
MkDev11 wants to merge 1 commit intoentrius:testfrom
MkDev11:feat/240-pioneer-reward-mechanism

Conversation

@MkDev11
Copy link
Contributor

@MkDev11 MkDev11 commented Feb 27, 2026

Summary

Replace the repository uniqueness multiplier with a pioneer reward mechanism that gives the first miner to land a quality merged PR on an untouched repo a meaningful score bonus, while followers score normally at 1.0x.

Problem

Contribution activity clusters on a small number of popular repositories. The existing repository_uniqueness_multiplier gives a shared bonus that shrinks as more miners pile on, but it is too weak to change miner behavior — miners still cluster on repos once they become visibly profitable.

Solution

A binary pioneer reward: exactly one winner per repo, decided by earliest eligible merge timestamp. The multiplier is a pure 4-line function with two constants:

def calculate_pioneer_reward_multiplier(pioneered_repo_count: int) -> float:
    count = max(1, pioneered_repo_count)
    decay = count ** PIONEER_MULTI_REPO_DECAY_EXPONENT
    pioneer_bonus = PIONEER_BASE_BONUS / decay
    return round(1.0 + pioneer_bonus, 2)

How it works

  1. For each repo in the scoring window, collect all merged PRs that pass the quality gate (token_score >= MIN_TOKEN_SCORE_FOR_BASE_SCORE, valid tier config, non-null merged_at).
  2. Per miner, keep only their earliest eligible PR on that repo.
  3. Sort by (merged_at, pr_number) — position 1 is the pioneer. The pioneer's specific PR gets the bonus multiplier; all other PRs (followers, later PRs from the same miner) get 1.0x.
  4. If a miner pioneers multiple repos in the same cycle, the bonus shrinks per repo via square-root decay (diminishing returns).
Pioneered repos Pioneer multiplier Follower multiplier
1 2.20x 1.0x
4 1.60x 1.0x
9 1.40x 1.0x

Constants: PIONEER_BASE_BONUS = 1.2, PIONEER_MULTI_REPO_DECAY_EXPONENT = 0.5

Design decisions

  • Quality gate — PRs below MIN_TOKEN_SCORE_FOR_BASE_SCORE are excluded from pioneer ranking entirely. This prevents low-effort snipe merges from capturing pioneer status. If a low-quality PR merges first, the next quality contributor becomes the pioneer instead.
  • Single-PR scoping — the bonus applies only to the one PR that earned pioneer status, not to follow-up PRs from the same miner on that repo. This keeps the incentive targeted.
  • Multi-repo damping — a miner who rushes to pioneer 4 repos gets 1.60x each, while a miner who pioneers 1 repo gets 2.20x — 37.5% higher per repo. This discourages land-grab strategies.
  • Deterministic tie-breaking — same-second merges (e.g. from merge bots processing a queue) are resolved by lower PR number (the earlier-created PR wins). This is intentionally single-winner: a co-pioneer design would multiply the bonus on repos with aggressive merge bots, turning high-traffic repos into bonus farms — the opposite of the intended incentive.
  • Logging — each PR's multiplier log shows pioneer=2.20(P) for pioneers and pioneer=1.00(F) for followers, making scoring transparent in validator output.

Changes

File Change
gittensor/constants.py Add PIONEER_BASE_BONUS = 1.2 and PIONEER_MULTI_REPO_DECAY_EXPONENT = 0.5; remove UNIQUE_PR_BOOST
gittensor/classes.py Replace repository_uniqueness_multiplier with pioneer_multiplier: float and pioneer_rank: int; update calculate_final_earned_score to use pioneer key with (P)/(F) tag
gittensor/validator/evaluation/scoring.py Add is_pioneer_eligible, collect_repo_pioneer_candidates, build_repo_contributor_ordering, count_pioneered_repositories, calculate_pioneer_reward_multiplier; update finalize_miner_scores to apply pioneer rewards; remove calculate_uniqueness_multiplier
gittensor/validator/storage/queries.py Replace repository_uniqueness_multiplier column with pioneer_multiplier and pioneer_rank in INSERT/UPDATE queries
gittensor/validator/storage/repository.py Update storage tuple to write pioneer_multiplier and pioneer_rank
tests/validator/conftest.py Extend PRBuilder.create() to accept merged_at, uid, and token_score parameters
tests/validator/test_pioneer_reward.py 33 new tests across 8 test classes (see Testing section)

DB migration

ALTER TABLE pull_requests ADD COLUMN pioneer_multiplier FLOAT DEFAULT 1.0;
ALTER TABLE pull_requests ADD COLUMN pioneer_rank INTEGER DEFAULT 0;
UPDATE pull_requests SET pioneer_multiplier = repository_uniqueness_multiplier;
ALTER TABLE pull_requests DROP COLUMN repository_uniqueness_multiplier;

Related Issues

Closes #240

Type of Change

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • Other (describe below)

Testing

  • Tests added/updated
  • Manually tested

Test coverage (33 tests, 8 classes)

Class Tests What it covers
TestBuildRepoContributorOrdering 5 Ordering by merge timestamp, tie-break by PR number, multiple repos, skipping ineligible PRs
TestPioneerEligibility 2 Requires tier config + merged_at + token_score >= MIN_TOKEN_SCORE_FOR_BASE_SCORE
TestCollectRepoPioneerCandidates 1 Low-token PR excluded from candidates; eligible PR kept
TestCalculatePioneerRewardMultiplier 6 Pure function: single repo bonus, multi-repo damping, monotonic decrease, zero/negative guard, follower always 1.0x
TestCountPioneeredRepositories 2 Correct per-miner pioneer count from ordering dict
TestFinalization 8 End-to-end: pioneer vs follower, single-PR scoping, multi-repo damping, quality gate snipe blocked, ineligible PR neutral, co-contributor ordering
TestPioneerIncentiveEvidence 3 Exploration beats pile-on (5x), distributed beats concentrated (10x), diminishing returns still material at 9 repos
TestEdgeCases 6 No contributions, single miner, all same repo, unknown repo, miner with no merged PRs, large miner count

Scoring simulation evidence (from TestPioneerIncentiveEvidence)

Strategy Setup Total pioneer bonus
Pile-on 5 miners → 1 repo 1.2 (1 event)
Explore 5 miners → 5 repos 6.0 (5 events)
Concentrated 20 miners → 2 repos 2.4
Distributed 20 miners → 20 repos 24.0

Exploration yields 5x more network-wide pioneer bonus than pile-on. Distributed mining yields 10x more than concentrated.

Test results

pytest tests/validator/test_pioneer_reward.py -v → 33 passed
pytest tests/validator/ -q                      → 220 passed
ruff check                                      → clean

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Changes are documented (if applicable)

@MkDev11 MkDev11 force-pushed the feat/240-pioneer-reward-mechanism branch from 55c5689 to 5294a0e Compare February 27, 2026 06:01
@MkDev11
Copy link
Contributor Author

MkDev11 commented Feb 27, 2026

@anderdc @LandynDev Please review the PR and let me know your feedback.

@MkDev11 MkDev11 force-pushed the feat/240-pioneer-reward-mechanism branch 2 times, most recently from 4b4f56f to d8be47a Compare March 1, 2026 11:19
@MkDev11 MkDev11 force-pushed the feat/240-pioneer-reward-mechanism branch 2 times, most recently from a16e944 to 912e90a Compare March 2, 2026 01:21
@LandynDev
Copy link
Collaborator

Can you rebase this onto entrius:test for a clean commit history?

…trius#240)

Replace the cross-miner repository uniqueness multiplier with a pioneer
reward mechanism that makes untouched repos genuinely attractive while
keeping follower scoring neutral.

- Binary pioneer bonus: only earliest merged PR on each repo gets bonus
- Quality gate: PRs below MIN_TOKEN_SCORE_FOR_BASE_SCORE are ineligible
- Single-PR scoping: bonus only on pioneering PR, not follow-ups
- Multi-repo damping: bonus decays as miner pioneers more repos
- Pure scalar function: calculate_pioneer_reward_multiplier(int) with
  round(..., 2) consistent with all other multipliers
- Pioneer multiplier: 2.20x (1 repo), 1.60x (4 repos), 1.40x (9 repos)
- Comprehensive test suite: 33 tests across 8 test classes
- DB schema: pioneer_multiplier + pioneer_rank replace
  repository_uniqueness_multiplier
@MkDev11 MkDev11 force-pushed the feat/240-pioneer-reward-mechanism branch from 912e90a to c7e2a77 Compare March 2, 2026 23:29
@MkDev11
Copy link
Contributor Author

MkDev11 commented Mar 2, 2026

@LandynDev please review again. thanks

@LandynDev
Copy link
Collaborator

  • is_pioneer_eligible probably shouldn't be in scoring.py. Because pioneer eligibility is a rule about the state and data of a PullRequest itself, it belongs as an instance method on the PullRequest class so the logic lives with the object it describes instead of being scattered in unrelated scoring code.
  • collect_repo_pioneer_candidates has nasty return type that isn't readable or maintainable, this doesn't fit the desired style for the heart of our code, the scoring code
  • Same situation with the function build_repo_contributor_ordering and its return type of Tuple[Dict[str, Dict[int, int]], Dict[str, int]] - should be using classes like the style of the codebase rather than deeply nested dicts + tuples. And here: repo_candidates: Dict[str, List[Tuple[datetime, int, int]]] = {}

Other notes:

  • Would also appreciate if variables of nested Dicts had clear comment for what they are so it's digestible and readable. Example is these 2 variables:
    repo_ordering: Dict[str, Dict[int, int]] = {}
    pioneer_pr_numbers: Dict[str, int] = {}
  • I think that instead of managing complex return types and variables in general, this could elegantly be solved by just having a pioneer_rank field that we set in each PullRequest. Then we don't have complex dicts and types to manage, it's all already set in the pioneer_rank field.
  • The minimum token requirement to be considered pioneer is a great and protective guard, love that

Overall the PR was a bit complex and doesn't feel like an elegant solution that is easily maintainable and readable. The work feels like it lacked stepping back and giving a thorough review to ensure high quality.
The work also feels like it lacked deep thought for a desirable solution and it does not solve the issue. Here's why:

Lets spell out a quick example between what existed and your proposed solution:
What existed was the unique repo multiplier giving 1.74x contribution if lone contributor (similar to pioneer),

Say miner 1 had solo contributions to repos A,B,C.
Before they'd get: 1.74x multiplier for each of their 3 PRs
With your proposal: 1.693x multiplier for each of their 3 PRs

We already had a repository uniqueness multiplier in place that clearly wasn't working strong enough, hence issue #240. Sure we can raise the PIONEER_BASE_BONUS value from 1.2 to be higher to nullify this example, but there's still fundamental flaws in the solution to me, such as the decay. The decay in your solution can actually end up disincentivizing a miner to pioneer a new repo as it will only harm their multiplier for their other contributions since it decays it. This is backwards and doesn't solve the problem in a well thought out manner. It makes more sense to me to reward the pioneers PR by PR, agnostic of who the miner is or what they've pioneered.

Key snippets from issue description:

Code cleanliness and readability matter. Follow existing patterns, write code that reads naturally.

The goal is to make pioneering genuinely attractive so that miners actively seek out new repos rather than piling onto proven ones.

Be creative. A naive if first_contributor: score *= 5 is not what we're looking for, think about the dynamics of the problem and take advantage of the opportunity to design something that handles them well.

The existing uniqueness multiplier attempts to address this, but the incentive isn't strong enough to change behavior (perhaps it needs to be removed and replaced with more ideal logic?).

@LandynDev LandynDev closed this Mar 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rewarding the pioneers

2 participants