{
  "_meta": {
    "source": "home-mixer/filters/ (17 filters) + pipeline order in candidate-pipeline/phoenix_candidate_pipeline.rs",
    "verified": "2026-07-31",
    "note": "These run BEFORE and AFTER scoring. A post that trips a filter never gets ranked at all — most 'shadowban' experiences are filters, not weights."
  },
  "filters": [
    {"slug": "drop-duplicates", "name": "Duplicate drop", "file": "home-mixer/filters/drop_duplicates_filter.rs", "stage": "pre-scoring", "what": "Removes duplicate candidates of the same post before scoring.", "lever": "Reposting identical content multiple ways collapses to one candidate."},
    {"slug": "core-data-hydration", "name": "Core data hydration", "file": "home-mixer/filters/core_data_hydration_filter.rs", "stage": "pre-scoring", "what": "Drops candidates whose core post data fails to hydrate — malformed/deleted/unavailable posts exit here.", "lever": "None — infrastructure gate."},
    {"slug": "age", "name": "Age ceiling", "file": "home-mixer/filters/age_filter.rs", "stage": "pre-scoring", "what": "Removes posts older than a max age (value withheld as a param). The Phoenix ranker itself buckets post age in 60-minute buckets capped at 4800 minutes = 80 hours (phoenix/recsys_model.py POST_AGE_MAX_MINUTES) — beyond that, age is indistinguishable.", "lever": "Your post's earning window is short. 'Evergreen recycling' exists because the pipeline hard-expires candidates."},
    {"slug": "self-tweet", "name": "Self-post removal", "file": "home-mixer/filters/self_tweet_filter.rs", "stage": "pre-scoring", "what": "You are not shown your own posts in recommendations — and your own engagement with them earns nothing.", "lever": "Self-liking and view-refreshing are scored at zero by construction."},
    {"slug": "retweet-dedup", "name": "Repost deduplication", "file": "home-mixer/filters/retweet_deduplication_filter.rs", "stage": "pre-scoring", "what": "Multiple reposts of the same underlying post collapse to one candidate.", "lever": "Ten mutuals reposting you ≠ ten timeline slots; it's one slot with better provenance."},
    {"slug": "ineligible-subscription", "name": "Subscription eligibility", "file": "home-mixer/filters/ineligible_subscription_filter.rs", "stage": "pre-scoring", "what": "Subscriber-only content is dropped for non-subscribers.", "lever": "Paywalled posts don't leak into non-subscriber timelines."},
    {"slug": "previously-seen", "name": "Previously seen", "file": "home-mixer/filters/previously_seen_posts_filter.rs (+ backup bloom filter)", "stage": "pre-scoring", "what": "Posts the viewer already saw are removed (impression history + a bloom-filter backup).", "lever": "You don't get repeat impressions on the same viewer — a flop can't be re-served to the same audience; new content can."},
    {"slug": "previously-served", "name": "Previously served", "file": "home-mixer/filters/previously_served_posts_filter.rs", "stage": "pre-scoring", "what": "Posts already served in recent responses are removed even if not confirmed seen.", "lever": "Same as above, tighter."},
    {"slug": "muted-keyword", "name": "Muted keywords", "file": "home-mixer/filters/muted_keyword_filter.rs", "stage": "pre-scoring", "what": "Tokenizes post text (TweetTokenizer) and drops posts matching any viewer-muted keyword. Real text matching, per viewer.", "lever": "Heavily-muted terms (politics rage-words, spoiler terms, crypto-shill vocabulary) silently shrink your eligible audience before scoring even happens."},
    {"slug": "author-socialgraph", "name": "Blocks/mutes graph", "file": "home-mixer/filters/author_socialgraph_filter.rs", "stage": "pre-scoring", "what": "Drops posts from authors the viewer blocked/muted AND from authors who block the viewer — including quoted authors. Blocks propagate through quotes.", "lever": "Every block you collect permanently deletes a viewer from your distribution. Quote-posting a widely-blocked account inherits their blocks."},
    {"slug": "video-exclude", "name": "Video exclude", "file": "home-mixer/filters/video_filter.rs", "stage": "pre-scoring", "what": "For surfaces that exclude video, any post with a video is dropped entirely.", "lever": "Video posts are all-or-nothing on some surfaces."},
    {"slug": "topic-ids", "name": "Topic pinning", "file": "home-mixer/filters/topic_ids_filter.rs (+ new_user_topic_ids_filter.rs)", "stage": "pre-scoring", "what": "Topic-pinned requests only keep posts in those topics; new users get a special variant.", "lever": "Clear topical identity matters for topic-surface distribution."},
    {"slug": "visibility", "name": "Trust & safety visibility (VF)", "file": "home-mixer/filters/vf_filter.rs (+ ancillary_vf_filter.rs)", "stage": "post-selection", "what": "Applies trust-and-safety visibility verdicts AFTER ranking. The enforcement logic lives in a withheld crate (xai_visibility_filtering) — this is where actual 'shadowbans' would live, and it is NOT public.", "lever": "Honesty note: nobody outside X can tell you your VF status from the open code. Tools claiming to 'detect shadowbans' from this repo are guessing."},
    {"slug": "dedup-conversation", "name": "Conversation dedup", "file": "home-mixer/filters/dedup_conversation_filter.rs", "stage": "post-selection", "what": "Removes multiple posts from the same conversation in one response.", "lever": "A viral thread occupies one slot, not five."}
  ],
  "adjustments": [
    {
      "slug": "author-diversity-decay",
      "name": "Author diversity decay",
      "file": "home-mixer/scorers/ranking_scorer.rs — diversity_multiplier()",
      "formula": "multiplier = (1 − floor) × decay^N + floor, where N = how many higher-scoring posts of YOURS are already in this viewer's candidate set (N=0 for your best post). Decay and floor values withheld.",
      "what": "Your posts compete with each other. Candidates are sorted by score; your 2nd, 3rd, Nth post each get multiplied down toward a floor.",
      "lever": "Volume-posting into the same audience has mechanically diminishing returns — the decay curve is in the code, only its constants are hidden."
    },
    {
      "slug": "oon-multiplier",
      "name": "Out-of-network multiplier",
      "file": "home-mixer/scorers/ranking_scorer.rs — effective_oon_weight()",
      "formula": "final = score × OonWeightFactor when the viewer doesn't follow you; special-cased for new users (account age < threshold AND following ≥ NEW_USER_MIN_FOLLOWING) and topic-pinned requests. All factor values withheld.",
      "what": "Discovery reach (For You beyond your followers) is a single multiplicative dial applied after everything else.",
      "lever": "Everything that raises your base score raises OON reach proportionally; there is no separate 'viral switch' in the scorer."
    },
    {
      "slug": "video-duration-gate",
      "name": "Minimum video duration gate",
      "file": "home-mixer/scorers/ranking_scorer.rs — vqv_weight()/quoted_vqv_weight()",
      "formula": "if video_duration_ms ≤ MinVideoDurationMs → vqv weight = 0 (the weight, not the probability). Threshold value withheld.",
      "what": "Short videos earn zero video-view credit.",
      "lever": "Don't attach a 3-second clip expecting video distribution."
    },
    {
      "slug": "negative-offset",
      "name": "Negative score offset",
      "file": "home-mixer/scorers/ranking_scorer.rs — offset_score()",
      "formula": "combined < 0 → (combined + negative_sum) / total_sum × NEGATIVE_SCORES_OFFSET; else combined + NEGATIVE_SCORES_OFFSET",
      "what": "Net-negative posts aren't clamped to zero — they're remapped into a low band below every net-positive post. Negative predictions have real ordering consequences.",
      "lever": "A high-engagement post with higher predicted mute/report can rank below a modest clean post."
    }
  ]
}
