starl3xx.fun / algo / 007

Issue № 007 (commit cb45b55)

A pool of the biggest accounts’ fastest-moving posts switched on for For You, a narrower exemption from the reply-spam label, a vertical-video carousel and an “under the hood” trace built and left off, and no cold-start lift in topic feeds

X Algo Watch

Every Sunday I diff the open-source X ranking code, xai-org/x-algorithm, against last week’s mirror and write down what actually moved. This issue covers b412112 to cb45b55, which is 4 commits, dated 6 October, 7 October, 8 October, 9 October.

Everything below is a default in the code, not a claim about production. The repo’s own README says the two can differ. Every number is copied from the diff; where I’m inferring what a change means for posting, I say so.

If you change one thing this week

  • Reply under big accounts like a person, not a campaign. When the reply-spam classifier flags a reply, its author is now spared the down-ranking label only with a cred score of 66 or more, up from 62, or a gray badge.
  • If you are one of the most-followed accounts, make your originals move in their first hours. A pool switched on this week ranks the last day’s original posts from roughly the top one in 20,000 active accounts by how fast they gather views, so a two-hour-old post with 10,000 views outranks a twenty-hour-old one with 30,000.
  • Don’t reshoot everything vertical for a carousel yet. One is written into the code, picking three to five vertical videos by the predicted chance of a video open, and its switch, EnableVideoCarousel, is false.

Everything below is the evidence for those three, plus what else moved.

What moved

RISKY_HIGH_VIZ_REPLY_EXEMPT_MIN_PAGE_RANK_SCORE62 → 66
FOLLOWER_COUNT_THRESHOLD_FOR_SPAM_DETECTION125000 → 180000
FEED_MODULE_SLOTS4 → 5
PostUnexploredWeight0.02 → 0.015

1. A pool of popular posts is switched on for For You. EnablePopularPostsSource went from false to true in param.rs, and the source it switches on was rebuilt in the same range. A week ago, popular_posts_source.rs would have asked for up to three recent original posts from each of the 1,000 most-followed accounts on a stored list; those three params are gone from param.rs. Now it reads a shared list that a separate job rewrites every five minutes. The authors come from popular_authors_job.rs: the accounts that posted in the last seven days, ranked by follower count, and the top 0.00005 of them (TOP_POSTING_AUTHORS_FRACTION in popular_authors.rs), one account in 20,000. popular_posts_job.rs takes their original posts from the last 24 hours, no replies and no reposts, and popular_posts.rs ranks each one by the views it would reach in a day if its views kept slowing like a post with an eight-hour half-life, keeps at most five per author, and keeps the top 500 (the job’s defaults in main.rs). On that formula, a two-hour-old post with 10,000 views projects to about 55,000 and outranks a twenty-hour-old post with 30,000, which projects to about 32,000. Each For You request then takes up to PopularPostsMaxResults 500 from the list, minus posts the viewer has already seen. Expect to read “X now boosts big accounts.” Nothing in the source adds weight: these posts join the candidates and are scored on the same ladder as every other post. If the list cannot be loaded, the source returns nothing. Takeaway for the largest accounts, an original post’s first hours decide whether it joins a pool every For You request can draw on; for everyone else, it is more competition for the same slots, not a change in what earns them

2. The reply-spam label spares fewer accounts. In constants.py, RISKY_HIGH_VIZ_REPLY_EXEMPT_MIN_PAGE_RANK_SCORE went from 62 to 66. When the reply-spam flow decides to label a reply, task_write.py skips the label, RiskyHighVizReply, for an author whose cred score is at least that number, and for a gray-badge account. The repo’s enforcement rules log that label as a down-rank, under DownRankGroxFlaggedReply in GroxTweetProcessor.bot. A second check moved the other way. The coordinated-spam filter in task_filter.py looks only at replies at least two levels deep in a thread, and only when the thread’s first post comes from an account above a follower floor; FOLLOWER_COUNT_THRESHOLD_FOR_SPAM_DETECTION there went from 125_000 to 180_000, so threads under accounts between those two sizes left that check. Takeaway a reply that reads as spam is now less likely to be excused by your account’s standing, so under big accounts the safe reply is still one written to the post in front of you

3. A vertical-video carousel is built and switched off. video_carousel.rs would put three to five videos in one For You module at position VideoCarouselPosition 6, and only for a viewer who has not been served one in the last VideoCarouselFatigueMinutes 60 minutes. It takes only videos that are not replies and are taller than wide (an aspect ratio below 1.0), and it orders them by the predicted chance of a video open, not by their score. To find them, top_k_score_selector.rs would pull up to VIDEO_CAROUSEL_EXTRA_CANDIDATES 30 extra videos from beyond the top 50 by score. Its switch, EnableVideoCarousel, is false, so none of this runs. One related change does take effect: FEED_MODULE_SLOTS in config.rs went from 4 to 5, so a For You response has room for one more module, not one more post. Takeaway nothing in the default feed favors vertical video this week; if the switch is ever turned on, a vertical video’s open rate, not its overall score, would decide whether it lands in the carousel

4. The cold-start lift skips topic feeds. author_cold_start.rs now returns the scores unchanged when a request carries topic IDs, before any lift is considered, and a new test pins it: the same candidates get the lift in a plain request and keep their scores in a topic request. There is no switch. The lift that issue 006 described still runs in the main For You feed. Takeaway a new post’s one shot at the cold-start lift comes from the main feed, not from a topic feed

Also in this range, and not worth an item each. PostUnexploredWeight, a weight on Phoenix’s post_unexplored prediction that applies only to posts from accounts the viewer follows, went from 0.02 to 0.015 in param.rs but not in the ranking service’s params.rs, which still says 0.02: the first time the two copies have disagreed on a shared default. Home-mixer’s copy is the one in the score it sends with each request and serves itself when the call to the service fails, in vm_ranker.rs. The service also gained PostUnexploredIncludeOutOfNetwork false, which would extend that weight to accounts the viewer doesn’t follow. Three new weights in scoring.rs would credit the predicted seconds spent watching more video after opening one, and on a profile after visiting it; VideoContinuationWeight, UserVideoContinuationWeight and ProfileVisitSecsWeight are all 0.0, and EnableHomeExcursionScores, which fetches those predictions, is false. EnableUnderTheHood false switches off a trace of each feed build, stage by stage, with a link from each stage to its file in this repo (under_the_hood.rs). VMRankerClusterId went from "Experiment6" to "Experiment1", a different deployment of the ranking service, and the repo does not say what differs between them. RetrievalCandidatesKafkaMaxCandidates went from 200 to 100000; it caps how many candidates a sampled request logs in retrieval_candidates_kafka_side_effect.rs and does not touch ranking. The visibility filter gained a fourth answer besides keeping, dropping and interstitials: a notice attached to a post that stays in the feed, with reasons typed for X’s freedom-of-speech-not-reach rules (verdict.rs), and the repo’s README.md says nothing in the repository draws the notice.

The standing numbers

For anyone new here, the weights that matter most didn’t move this week. From param.rs, and declared again with the same defaults in the service’s params.rs, each one against a like:

  • share via copy link: 20.0, forty times a like
  • reply between two accounts that follow each other: 5.0 plus a 15.0 boost, also forty times
  • reply or quote: 5.0, ten times
  • like: 0.5, the unit

Nothing older than 48 hours enters the For You feed at all: MAX_POST_AGE in config.rs is 48 * 60 * 60 seconds. A reply or a repost from an account a viewer doesn’t follow is dropped outright by oon_retweet_reply_filter.rs at its default, so replies earn their weight from your existing followers rather than from new reach. A quote carries the same weight and does travel out of network. And where the ranking service applies them, two multipliers follow the sum: the repeat-author factor, which keeps 0.625 of your second-best post and 0.4375 of your third-best, and OonWeightFactor 0.75, on a post from an account the viewer does not follow and on a reply or repost from one they do.

Caveats

These are defaults in open-source code, overridable per experiment at runtime. The ranking model itself (Phoenix) is trained, not rule-based; the weights blend its per-viewer predictions and don’t describe what it learned. Reading weights as tactics is inherently lossy. I’d rather you know that than not.

Sources: param.rs, config.rs, params.rs, popular_posts_source.rs, popular_authors_job.rs, popular_authors.rs, popular_posts_job.rs, popular_posts.rs, main.rs, constants.py, task_write.py, task_filter.py, GroxTweetProcessor.bot, video_carousel.rs, top_k_score_selector.rs, author_cold_start.rs, vm_ranker.rs, scoring.rs, under_the_hood.rs, retrieval_candidates_kafka_side_effect.rs, verdict.rs, oon_retweet_reply_filter.rs, README.md, all at cb45b55.