starl3xx.fun / algo / 010
Issue № 010 (commit 9106e9f)
The end of Brazil’s election filter, a per-viewer correction to predicted engagement and a whole-page reranker, both built and off, a new-account soft-NSFW screen stretched from one day to seven and still off, and a second quiet week for the ladder
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 6258a15 to 9106e9f, which is 5 commits, dated 27 October, 28 October, 29 October, 30 October, 31 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
- Quote and reply under Brazil’s runoff candidates as you would under anyone. The election filter is gone, so a quote or a reply involving one of its 25 accounts no longer leaves For You for viewers who don’t follow that account.
- Keep writing for the reply and the copy link. No weight on the ladder moved, so the ratios stand: a reply about 7.6 times a like, a share via copy link about 30 times.
Everything below is the evidence for those two, plus what else moved.
What moved
1. Brazil’s election filter is gone. Brazil2026ElectionFilter no longer appears in the filter list in phoenix_candidate_pipeline.rs, which builds For You and the ranked Following feed, and its file, brazil_2026_election_filter.rs, is deleted. This series has followed it since issue 002: it removed a post when its author, the account it reposted, the account it quoted, or an account earlier in its reply chain was on the list, unless the viewer followed that listed account. Issue 008 reported the list cut to the 25 accounts in the 25 October runoff. The repo’s README.md dates the removal 26 October and says that, with the elections over, posts from the accounts it covered are no longer filtered out because of it. Expect to read “X has unblocked Brazil’s candidates” or “X is boosting them now.” Nothing scores them higher: no weight moved, and their posts, and posts that quote or reply under them, go back to the same filters and the same weights as everyone else’s. Takeaway quoting or replying under one of the 25 runoff accounts no longer costs your post its place in For You with viewers who don’t follow that account
2. A per-viewer correction to predicted engagement, built in the ranking service and switched off. The ranking service gained a step in debias.rs that would adjust Phoenix’s predictions for each viewer before the weights are applied. A separately trained model would read the viewer’s recent engagement history, with the hour, the day, the time zone, the app and the surface, and predict how likely this viewer is to like, reply, repost, dwell and so on in general, against the same figure for viewers overall. Each post’s predicted probability for that action would then be recentered on the gap between the two, with the shift capped by UserDebiasClipLogit, 2.0, and blended in by UserDebiasAlpha. The function that does the arithmetic, debiased_probability, comes from a library that is not in the open repo, so the exact formula is not something the code shows. By default it adjusts thirteen actions, from the like and the reply to the video open, and leaves report, block, mute and “not interested” alone. The file’s own test shows the direction: a viewer whose predicted like rate is above the global one has every post’s like probability pulled down. It is off three ways at the defaults: UserDebiasEnabled is false and UserDebiasAlpha is 0.0 in the service’s params.rs, and the service loads a trained model only when it is started with --user-debias-artifact-path, which has no default (args.rs). It also skips any model trained more than UserDebiasMaxArtifactAgeSecs, 3600.0 seconds, ago. The half that did switch on is the input: in param.rs, VMRankerSendDebiasInputs went from false to true, so on every feed build that is not served from cache home-mixer now sends the service that history and each post’s content features (vm_ranker_debias_payload.rs). A test in mod.rs switches it on for viewers in Germany; that is test data, not a default. Expect to read “X now discounts likes from people who like everything.” It would not touch the likes your post has already earned. It would change how one viewer’s predicted actions are weighed against each other, before the same weights apply. Takeaway nothing changes at these defaults; switched on, a viewer who likes everything would rank you more on whether they reply, dwell or share than on whether they like, which is inference from the mechanism
3. A feed page chosen as a whole, and a shuffle of the top 64, both built and off. Two new scorers now run after the ranking service in phoenix_candidate_pipeline.rs. With EnablePageDecodeRerank false, the first would ask Phoenix to fill up to PageDecodeSteps, 64, slots from a pool of PageDecodePoolN, 200, sending along the weights home-mixer already uses for those actions, and page_decode_rerank_scorer.rs would put Phoenix’s picks first, in Phoenix’s order. The model it calls is a decoder in Phoenix’s training code, page_decoder.py, which picks posts one slot at a time, so what goes in slot three can depend on slots one and two. With EnablePageRlExploration false, the second, page_rl_exploration_scorer.rs, would reshuffle the top PageRlExplorationTopK, 64, by score at PageRlExplorationTemperature 0.2, so a higher score makes the top slot more likely rather than certain, and it would record each post’s odds of the slot it got. The policy-gradient settings in page_decoder.py, PagePgConfig, divide scores by the same 0.2, named pi_b_tau there. One piece is on: EnablePageLogging is true, so in production home-mixer now keeps each served page, up to 64 posts, for PageLogTtlSeconds, 3600 seconds (phoenix_request_cache_side_effect.rs). That only records; it changes no order. Takeaway nothing changes at these defaults; this is groundwork for ranking a page rather than one post at a time, and if it switches on, where your post lands would depend on what sits around it as well as on its own score
4. The new-account soft-NSFW screen now covers a week, and is still off. In param.rs, NewUserSoftNsfwFilterMaxAccountAgeSecs went from 86400 to 604800 seconds, one day to seven. Issue 009 described the screen: it would drop a post labeled SOFT_NSFW from an account the viewer does not follow, in For You only, for viewers whose account is younger than that age. A second condition is new in new_user_soft_nsfw_filter.rs: the viewer’s engagement history must also be shorter than NewUserSoftNsfwFilterMaxScoringSeqLength, 100 entries, or missing, so a week-old account that already engages a lot is not screened. Its switch, EnableNewUserSoftNsfwFilter, is still false. Takeaway nothing changes at this default; switched on, a soft-NSFW post of yours would reach an account in its first week, outside For You or after a follow, until that account has built up some history
Also in this range, and not worth an item each. Ads placement changed: AdsBlenderType went from "multi_risk" to "slot_spread", a new blender that sets the number of ad slots from the requested spacing and then checks each slot’s neighboring posts (slot_spread_blender.rs), and EnableAdsBrandSafetyVerdictV2 is now false; both decide where ads go, not how organic posts rank. The video carousel from issue 007 is still off (EnableVideoCarousel false); it gained a drop for posts with a high-risk brand-safety verdict (EnableVideoCarouselHighRiskFilter true, video_carousel_filter.rs) and two scoring weights at 0.0. Shadow scoring now samples a second experimental Phoenix cluster at the same rate as the first, and serves nothing from either. ReactWithVideoWeight is renamed PostVideoWeight, at 0.0 in both copies, so still off. ThunderClusterId is gone, which only changes how home-mixer reaches Thunder. Visibility filtering was reorganized at length, with the safety-label lookup moved into its own crate (source.rs); this issue did not establish which of those changes are new behavior. The README also now dates two model notes: on 8 October the ranking model got better at estimating time spent on a post, and on 13 October retrieval was also trained to surface posts people read all the way through; this issue did not trace either to code in this range. The two copies of the weights still disagree on the same five defaults issue 008 reported: the like is 0.66 in param.rs and 0.5 in the service’s params.rs.
The standing numbers
For anyone new here, nothing on the ladder moved this week either; the like’s rise in issue 008 is still the latest. From param.rs, each one against a like; the service’s params.rs declares the same values for the rest and still 0.5 for the like:
- share via copy link:
20.0, about thirty times a like - reply between two accounts that follow each other:
5.0plus a15.0boost, also about thirty times - reply or quote:
5.0, about seven and a half times - like:
0.66, 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: phoenix_candidate_pipeline.rs, README.md, debias.rs, params.rs, args.rs, param.rs, vm_ranker_debias_payload.rs, mod.rs, page_decode_rerank_scorer.rs, page_decoder.py, page_rl_exploration_scorer.rs, phoenix_request_cache_side_effect.rs, new_user_soft_nsfw_filter.rs, slot_spread_blender.rs, video_carousel_filter.rs, source.rs, config.rs, oon_retweet_reply_filter.rs, all at 9106e9f.