Skip to main content

Lumina Algorithm

The algorithm for Nightlight which powers the home page recommendations.

How does it work?

Lumina fetches a personalized list of recommended posts for the current user, scored and ranked using a combination of category affinity, recency, engagement, and view history.

Overview

     User Request

|
|

┌──────────────────────────────┐
│ Load Category Preferences │
│ (from cache or live query) │
└──────────────────────────────┘


┌──────────────────────────────┐
│ Score every eligible post │
│ using ranking signals │
└──────────────────────────────┘


┌──────────────────────────────┐
│ Keep only the best post │
│ per author │
└──────────────────────────────┘


Return top N results
ordered by score

Step 1 - Category Preference Loading

The algorithm learns what kind of content the user enjoys by looking at their most liked post categories. To avoid doing this on every request, the result is cached for 10 hours.

┌─────────────────────────────────────────┐
│ Check preference cache │
│ (is it less than 10 hours old?) │
└─────────────────────────────────────────┘
│ │
YES NO
│ │
▼ ▼
Use cached Look up the top 3
categories categories from the
│ user's liked posts
│ │
│ │
│ │
│ Save result to cache
│ for next 10 hours
│ │
└────────┬─────────┘

Score calculated

Example: gaming, music, art - in order of most liked to least.


Step 2 - Filtering

Before scoring, posts are filtered down to only those that are eligible to appear in recommendations.

FilterReason
Not the user's own postsRecommendations are for discovering others
Not private postsRespect visibility settings
Not repostsOriginal content only
Posted within the last 6 monthsAvoid surfacing very stale content
Not seen in the last 24 hoursPrioritize fresh or unseen posts
Not from a blocked userRespect block relationships
Not from a restricted accountRespect platform restrictions

Step 3 - Scoring

Every eligible post is given a score. The higher the score, the more relevant the post is to the user. The score is built from several independent signals added together.

score =
Category Match Bonus
+ Recency Bonus
+ View Status Modifier
+ Recent Re-view Penalty
+ Staleness Penalty
+ Engagement Score or Low-Engagement Penalty

Scoring Signals

SignalConditionPoints
Category matchPost matches the user's #1 liked category+30
Category matchPost matches the user's #2 liked category+20
Category matchPost matches the user's #3 liked category+10
Fresh postPosted within the last 24 hours+100
Not yet viewedUser has never seen this post+30
Already viewedUser has a view record for this post−30
Recently viewedViewed within the last week−50
Old postPosted more than 1 month ago−20
Low engagementFewer than 5 likes AND older than 3 days−50
EngagementOtherwise: like count contributes proportionally+varies

Scoring Examples

Great match - Post is in the user's #1 category, posted yesterday, never viewed, has 20 likes: 30 + 100 + 30 + (20 ÷ 10) = 162 points

Poor match - Post is 2 months old, viewed last week, only 2 likes: −30 + (−50) + (−20) + (−50) = −150 points


Step 4 - Per-Author Deduplication

To keep the feed diverse, only the single highest-scoring post from each author is kept. If an author has multiple eligible posts, only their best one makes it through.

Author A → 3 eligible posts → only the top-scored one is kept
Author B → 1 eligible post → kept as-is
Author C → 5 eligible posts → only the top-scored one is kept

Final feed: one post per author, ordered by score

See something you think could improve?

If you think some parts about the algorithm could be improved, let us know on our Discord or [email protected] !