starl3xx.fun / algo / 005

Issue № 005 (commit 4c5cfe8)

The final score now computed in a separate ranking service, the repeat-author and out-of-network multipliers moved there rather than removed, a correction to this series’ own repeat-author arithmetic, the text NSFW label no longer a drop for adults in out-of-network recommendations, and a Brazil election list ten accounts longer

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 8b25829 to 4c5cfe8, which is 5 commits, dated 22 September, 23 September, 24 September, 25 September, 26 September.

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

  • Do not start stacking posts because a “removed” list says you can. The repeat-author penalty and the out-of-network discount left home-mixer’s parameter file, but the separate ranking service that now sets the final score declares both at the same defaults, 0.5 and 0.25 for the penalty and 0.75 for the discount, and applies them once it has loaded its config.
  • Send a strong second post rather than holding it back, because the penalty is gentler than this series has said. Issues 001 to 004 said each extra post is multiplied by 0.5; the code has always halved only the part above the 0.25 floor, so your second-best post in one feed build keeps 0.625 of its score and your third-best 0.4375.
  • Keep writing for the same ladder. The service sums the same weights home-mixer did, so a reply still weighs ten times a like and a share via copy link forty times.

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

What moved

PhoenixMOEMaxResults200 → 0

1. The final score is now computed outside home-mixer, from the same ladder. ranking_scorer.rs, the file this series has cited for how a score is put together, is deleted, all 1160 lines. The scorer list in phoenix_candidate_pipeline.rs is down to two, Phoenix and VMRanker, and vm_ranker.rs now calls the ranking service whenever EnableRanking is on, which it is by default (true). What home-mixer sends, built in vm_ranker_request.rs, is Phoenix’s prediction for each action on each candidate, not the weights. The service, whose source is in the repo under vm-ranker, reads its own copy of the weights from params.rs and does the arithmetic in a new shared crate, scoring.rs. That params file shares 29 params by name with param.rs, the whole ladder among them, and every one has the same default. So the opposite overclaim, “ranking moved into a black box”, is wrong too: the code that computes the score is open and reads the same numbers. What the repo does not show is how X starts the service and the config it syncs, which was already true of every default this series has quoted. Takeaway write for the ladder exactly as before; what moved is the step after the sum, and that is where this week’s uncertainty sits

2. The repeat-author and out-of-network multipliers moved; they were not removed. In param.rs, 18 params are gone, and 12 of them reappear in params.rs under the same feature-switch keys with the same defaults. Six of them shape a score, EnableAuthorDiversity true, AuthorDiversityDecay 0.5, AuthorDiversityFloor 0.25, OonWeightFactor 0.75, TopicOonWeightFactor 0.5 and EnableOonRescoreForInNetworkRepliesRetweets true, and their formulas went into scoring.rs unchanged; the other six are off at their defaults or tune a step that is. Expect to read “X removed the repeat-author penalty” and “X removed the out-of-network discount” this week, because a diff of param.rs alone says exactly that. Neither is true. What did change is when they apply: only inside the service, and only once it has loaded its config. The startup flag for that, --config-sync-enabled, defaults to false in args.rs; with it off, main.rs logs that ranking parameters are not resolved, and the service works from home-mixer’s own score (mod.rs). Whether X runs the service with that flag on is not in the repo; the config sync it switches on, config_sync.rs, is new in this range. That score, the weighted sum from value_model.rs plus the cold-start lift, carries neither multiplier (enable_author_diversity is hard-coded there to false), and home-mixer also serves it when the call to the service fails, or when the answer leaves a post out. A week ago a failed call kept the old scorer’s result, which already had both. Takeaway treat both multipliers as still in force; the code drops them only when the service runs without its config, when the call to it fails, or for a post it leaves out, and none of that is visible from outside X

3. A correction: an extra post was never worth half. Issues 001 to 004, and the /algo page, said each extra post of yours in one viewer’s feed build is multiplied by 0.5. That was my arithmetic, not a change in the code. The formula is (1.0 - floor) * decay_factor.powf(exponent) + floor, the same in ranking_scorer.rs through 8b25829 and in scoring.rs now: only the part of the multiplier above the floor is halved, once for each better post of yours. With the defaults, a decay of 0.5 and a floor of 0.25, that is 0.25 + 0.75 × 0.5^k: your best post keeps all of its score, the second 0.625, the third 0.4375, and later ones tend toward 0.25 without reaching it. The repo’s own test in the same file writes the second one out as (1.0 - 0.25) * 0.5 + 0.25. Two more things the old sentence left loose. k is not posting order and not the time between posts: author_pool_counts sorts one feed build’s whole candidate pool by score and counts, for each of your posts, how many of yours scored higher, so your best post is never discounted. And it is a multiplier, not a cap, so a strong second post can still outrank someone else’s first. Takeaway where the penalty runs, your second-best post gives up three-eighths of its score, not half, and your best gives up nothing, so hold a post back because it is weaker, not because it is second

4. The text NSFW label no longer drops a post from adults’ out-of-network recommendations. NsfwTextTweetLabelDropRule is gone from tweet_rules.rs and from the TimelineHomeRecommendations list in registry.rs. That is the safety level vf_candidate_hydrator.rs checks every out-of-network post against, and also every quoted post and every post above a reply, even when an account the viewer follows wrote the quote or the reply. At 8b25829 the rule dropped any post carrying the NSFW_TEXT label at that level, for every viewer but its author. The repo’s test corpus pins the new verdict in age_gating.rs: for that label, the post is still dropped for an under-age viewer, by SensitiveViewerUnderageDropRule, and allowed for an adult one. It is still dropped for logged-out viewers too, and for viewers with no stated age in countries on the NSFW-gating list, a default in code that config can replace. The media rules did not move: NsfwHighRecallDropRule, NsfwHighPrecisionOonDropRule, NsfwCardImageOonDropRule and GoreAndViolenceOonDropRule are all still on the recommendations list, and so are the drops for an author flagged NSFW. As far as that corpus shows, it is the one For You verdict to change in a large rewrite of the visibility filter that otherwise restates the same rules as declarative clauses. Nothing in the commit says why. Takeaway read this as adult text becoming recommendable, by default, to adults who do not follow you, and not as For You opening to adult media, which it does not

5. The Brazil election filter matches accounts, not topics, and its list grew by ten. brazil_2026_election_filter.rs changed only its list this week: the test that counts the accounts went from 2776 to 2786. It removes a post when its author, the account it reposts, the account it quotes, or any account above it in a reply chain is on the list, unless the viewer follows that listed account. It matches account ids, not topics, keywords or text. It runs in phoenix_candidate_pipeline.rs, which builds both For You and the ranked Following feed, and not in the chronological Following feed. So a listed candidate’s followers still see the candidate’s posts, and an ordinary account’s quote, reply or repost involving a listed account disappears from those two feeds for everyone who does not follow the listed account. The file checks neither a date nor where the viewer is, so it applies to viewers everywhere, and nothing in it expires. Takeaway a quote of or reply to a listed candidate is shown in For You only to people who already follow that candidate, wherever they live, while a post that only discusses the election is untouched

Also in this range, and not worth an item each. The one number on the chart, PhoenixMOEMaxResults from 200 to 0, does not mean X shut a retrieval source down: EnablePhoenixMOESource is false at both ends, so phoenix_moe_source.rs retrieves nothing either way, and the same goes for its new PhoenixMoeColdStartMaxResults of 200 and for its inference cluster moving back from "Experiment3Memy04" to "Experiment2Memy04". The author cold-start lift kept every threshold; author_cold_start.rs now hands its decision to the service, which replays it before the two multipliers, and it now zeroes a post from Phoenix’s cold retrieval pool the way it zeroes an MOE post, for any viewer outside an experiment arm, though with PhoenixColdStartMaxResults at 0 none is expected. VMRankerClusterId went from "Experiment3" to "Experiment6", which picks the deployment home-mixer calls (vm_ranker_client.rs); what that deployment runs is not in the repo. VMRankerSendDebiasInputs is new at false, so it is off, and nothing in the repo reads what it would send. The other moved params do nothing at their defaults: MultiplierPreOffset false, WeightPerturbationSigma 0.0 with its salt and NewUserAgeThresholdSecs 0 are off, and DppTheta 0.65 and DppMaxSelectedRank 150, which lost a VMRanker prefix, tune a reordering step that also needs a startup flag, --dpp-enabled, defaulting to false, whatever the service’s new DppEnabled true says. Of the six params gone for good, four were already off, PostUnexploredWeightInNetworkOnly is now fixed in code at its old behavior, and EnableVMRanker went because the call now happens whenever ranking is on. NEW_USER_OON_WEIGHT_FACTOR left config.rs to become the service’s NewUserOonWeightFactor, still 0.00001 and still unreachable while NewUserAgeThresholdSecs is 0. The two dedup filters, drop_duplicates_filter.rs and retweet_deduplication_filter.rs, keep the same posts; what they add is read only by logging. Grox gained a video nudity check built on sampled key frames; the key-frame extraction it relies on is off by default, use_key_frames False in config.py, and the check itself runs only where X configures its task generator, which the repo does not show. And the repo’s own README.md still draws a RankingScorer that applies the repeat-author decay and the out-of-network discount, over a link that now opens value_model.rs, which does neither: anyone quoting that diagram is quoting a class that no longer exists.

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, 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, args.rs, main.rs, config_sync.rs, mod.rs, scoring.rs, vm_ranker.rs, vm_ranker_request.rs, value_model.rs, phoenix_candidate_pipeline.rs, vm_ranker_client.rs, author_cold_start.rs, phoenix_moe_source.rs, tweet_rules.rs, registry.rs, age_gating.rs, vf_candidate_hydrator.rs, brazil_2026_election_filter.rs, drop_duplicates_filter.rs, retweet_deduplication_filter.rs, oon_retweet_reply_filter.rs, config.py, README.md, all at 4c5cfe8.