starl3xx.fun / algo / 009
Issue № 009 (commit 6258a15)
An equal share of the 2,800 scoring slots for every source, a soft-NSFW screen for accounts in their first day, a viewer’s recent searches and topic history wired in and switched off, and a 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 862a123 to 6258a15, which is 5 commits, dated 20 October, 21 October, 22 October, 23 October, 24 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
- Post close to when your followers read. When a feed build holds more than 2,800 candidates, posts from accounts the viewer follows are now trimmed along with everyone else’s, and the ones trimmed are the oldest.
- Keep writing for the reply and the copy link. No weight on the ladder moved, so last week’s 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. Every source now keeps the same share of the 2,800 posts Phoenix scores. Phoenix, the model that predicts each action the ladder weighs, already scored at most 2,800 candidates per feed build: phoenix_request.rs sends it the first PHOENIX_CLIENT_MAX_CANDIDATES, 2800, and no others, and a candidate past that line comes back with no prediction for the weights to multiply. Until this range, order alone decided who made the cut. Each source’s results are joined in the order the sources are listed (fetch_candidates in candidate_pipeline.rs), and the list in phoenix_candidate_pipeline.rs starts with Thunder, the source of posts from accounts the viewer follows, which asks for at most 1200 (ThunderMaxResults). So a post from an account the viewer follows was always scored, and when the pool ran long, what fell past the line came from the end of the list: Phoenix retrieval and the sources after it. The last filter before scoring is now FairShareRankingFilter, in a new file, fair_share_ranking_filter.rs, and its switch, EnableFairShareRanking, is true. When the pool is longer than PhoenixScoresResultSize, also 2800, it groups the candidates by the kind of source that found them and keeps the same fraction of each group. Inside a group it keeps the first ones in the order they arrived, and it reads no score. Thunder returns posts newest first (score_recent in thunder_service.rs), so the in-network posts it trims are the oldest ones. The chart is the file’s own test, 1,100 candidates into 1,000 slots: the old first-come order would have kept every in-network and SimClusters post and 340 of Phoenix retrieval’s 440, and the filter keeps 400, 200 and 400, the same ten in eleven of each. At the open defaults the sources ask for up to 3,500 posts between them (Thunder 1,200, Phoenix retrieval 1,000, SimClusters 800, popular posts 500), before the filters ahead of this one remove any, so whether a typical feed build overflows is not something the repo shows. The filter stands aside when a build reuses cached posts, when the viewer has no engagement history for Phoenix to score against, or when Phoenix’s kill switch is set, and the same filter now runs in the pipeline that scores a supplied list of posts, phoenix_scores_pipeline.rs. Expect to read “X now favors strangers over the accounts you follow.” Nothing here scores anyone higher: no weight moved, and the out-of-network sources gain only the slots the old order took from them. What changed is that following an account no longer reserves its posts a place in the scoring. Takeaway in a crowded feed your older posts are the first to lose their place, so post when your followers are reading rather than hours ahead
2. A soft-NSFW screen for accounts in their first day, built and switched off. In new_user_soft_nsfw_filter.rs, a new filter would drop every post that carries a SOFT_NSFW safety label and comes from an account the viewer does not follow, when the viewer’s own account is younger than NewUserSoftNsfwFilterMaxAccountAgeSecs, 86400 seconds, which is one day. It applies to For You and not to topic feeds. Its switch, EnableNewUserSoftNsfwFilter, is false, so it is off. A new hydrator, new_user_soft_nsfw_label_hydrator.rs, looks the label up only while the filter is on, and only for posts from accounts the viewer doesn’t follow. Expect to read “X is hiding NSFW accounts.” This is off by default, and switched on it would reach only accounts in their first day, only in For You, and never a post from an account they follow. Takeaway nothing changes at this default; switched on, a brand-new account would see a soft-NSFW post of yours in For You only after following you
3. A viewer’s recent searches, and 28 days of the topics they engaged with, wired toward the ranking and switched off. In recent_search_queries_query_hydrator.rs, a new step would fetch up to 16 of the viewer’s most recent searches and send them to Phoenix, for both retrieval and prediction (recent_search_queries in phoenix_request.rs) as embeddings: the step blanks each search’s text before it goes. Its switch, EnableRecentSearchQueriesHydration, is false. Separately, HistoryTopicsEnabled false would count the topics of the posts a viewer engaged with over the last HistoryTopicsWindowDays, 28, from the engagement history home-mixer already fetches (scoring_sequence_query_hydrator.rs), and VMRankerSendHistoryTopics false would pass those counts to the ranking service (vm_ranker_request.rs). The open ranking service does not read those counts yet, and the one other reader is a measurement, serendipity_stats_side_effect.rs, which would compare the top ten posts served against that history on a sample of requests. Expect to read “X ranks your feed by what you search.” At these defaults nothing is fetched, and switched on, a search would become one more input to the model that predicts engagement, as an embedding, not a rule that inserts posts on the subject. Takeaway nothing to change at these defaults; if searches switch on, a post that plainly names its subject is the easier one to connect to someone who searched for it, which is inference rather than code
Also in this range, and not worth an item each. EnablePhoenixRetrievalFallback, which was false, is gone, and Phoenix retrieval and prediction now call one cluster with no fallback path (retrieve_excluding in phoenix_source.rs, predict in phoenix_scorer.rs). RetrievalValueModelWeights "" would send a set of value-model weights, written as JSON, with each retrieval request; empty, it sends none. The same source now counts, as a metric, how many retrieved posts come from accounts the viewer muted or blocked; the filters further down still remove them. ShadowTrafficPhoenixClusterRates went from vec![] to vec!["Experiment18Fou:0.3".to_string()], and the new ShadowTrafficPhoenixClusterAggregationTypes gives a second experimental cluster a different summary of the viewer’s engagement history (shadow.rs): shadow scoring sends a sampled request to an experimental model as well and serves none of what comes back. EnableAdsFunnelTrace and EnableVideoCarouselCandidatesLogging are new at true, and both only log, in production (funnel_trace_side_effect.rs, video_carousel_candidates_kafka_side_effect.rs); the carousel from issue 007 is still off. Visibility filtering gained an endpoint for notifications, which drops one when the account behind it is suspended or protected, or when the recipient blocks it, or mutes it without following it (filter_notifications.rs). Its test tables also gained rules for what a quote shows of the post inside it, including dropping that post when its author blocks the account quoting it, unless the quote is their own (quoted_tweet_rules.rs), and rules that blur a reaction to a post labeled adult or violent the way the original is blurred, and drop a reaction to a protected account’s post (reaction_video.rs). This issue did not establish which of those are new behavior and which moved over from older services. 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; 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_request.rs, candidate_pipeline.rs, phoenix_candidate_pipeline.rs, fair_share_ranking_filter.rs, thunder_service.rs, phoenix_scores_pipeline.rs, new_user_soft_nsfw_filter.rs, new_user_soft_nsfw_label_hydrator.rs, recent_search_queries_query_hydrator.rs, scoring_sequence_query_hydrator.rs, vm_ranker_request.rs, serendipity_stats_side_effect.rs, phoenix_source.rs, phoenix_scorer.rs, shadow.rs, funnel_trace_side_effect.rs, video_carousel_candidates_kafka_side_effect.rs, filter_notifications.rs, quoted_tweet_rules.rs, reaction_video.rs, param.rs, params.rs, config.rs, oon_retweet_reply_filter.rs, all at 6258a15.