- Sources read: 1
- Canon files updated: 1
- Radar entries: 1
Other changes reviewed today:
- In the same commit, X's impression verdict service added 4 reply-restriction rules and a reverse-follow lookup, completing the reply-restriction slot reserved yesterday.
- The same commit also added a pipeline that picks key frames for video safety verdicts based on scene cuts.
- Spotify's paper on training an LLM reranker passed the radar criteria, together with online A/B results.
1The VM-Ranker service took over value model computation — X
Source xai-org/x-algorithm@1b3fec20 — public source code (Apache 2.0), 2026-09-23 02:19 UTC, +4,993/−3,132 (92 files). These notes number the X commits after the Gen4 release in sequence. This commit counts as a separate logical generation bump, so the x.md table labels it Gen4+27.
Background VM-Ranker is a separate service that re-adjusts the order of the candidates sorted by home-mixer (the service that assembles the For You feed) using DPP. DPP is a diversity technique that selects candidates as a set so that similar candidates are not picked together. The value model is a score computation that starts from the sum of the action probabilities predicted by Phoenix (the ranking model of X For You), each multiplied by a per-action weight. Yesterday's (2026-09-22) commit 3aa0fa33 reserved three "slots", but there was no code to fill them.
- First, the home-mixer
VMRankerscorer moved beyond a score-only DPP wrapper and gained two parameters that send candidates, user context, and 26 PhoenixScores fields. However, there was still no code for what the receiving side would compute or how.- The two parameters are
VMRankerSendValueModelInputsandVMRankerComputeValueModel.
- The two parameters are
- Second, two new slots were reserved in the verdict tree of VF (visibility-filtering, a service that decides for each post whether it may be shown to this viewer). However, no rule filled these slots (
#[expect(dead_code)]).- The two slots are
TombstoneReason::LocalRegulationsandLimitedEngagementReason::ConversationControl.
- The two slots are
- Third, grox (the Grok-based nearline classifier sub-repo) did not yet have the frame sampling utility needed for adult content verdicts.
This commit (2026-09-23, 1b3fec20, Gen4+27) fills these three slots with real code.
What we learned
A. The VM-Ranker service started computing the value model directly (4 new files, 56 parameters)
- VM-Ranker's value model computation is pure Rust code that does not call a separate model server verified.
- The computation is handled by
vm-ranker/scoring/value_model.rs(191 new lines) in thevm-rankerservice. This file calls the shared crate'sxai_value_model::compute_value_scores()inline. - There are three inputs.
ValueModelWeights: 38 action-weight parametersQueryScoringContext: OON (out-of-network, posts from accounts the user does not follow) multipliers per user cohortCandidateScoringInputs: per-candidate Phoenix engagement probabilities
- The output is two vectors.
scoresis for the final ordering, andweightedis the weighted sum before DPP adjustment.
- The computation is handled by
- The weights were released together with production values verified —
vm-ranker/params.rs.- It is a new file with 56 parameters in 211 lines.
- The key gating flag
ComputeValueModelisfalseby default. This means it is still at the experimental stage. - The weights of the positive signals are
FavoriteWeight=0.5,ReplyWeight=5.0,RetweetWeight=1.0,QuoteWeight=5.0,ShareWeight=2.0,FollowAuthorWeight=4.0,ClickWeight=0.4,VideoOpenWeight=0.07, andPhotoExpandWeight=0.05. - The weights of the negative signals are
NotInterestedWeight=-43.2,BlockAuthorWeight=-31.2,MuteAuthorWeight=-58.8, andReportWeight=-234.0. - The DPP settings are
DppEnabled=true,DppTheta=0.65, andDppMaxSelectedRank=150. - The OON multipliers are
OonWeightFactor=0.75,TopicOonWeightFactor=0.5, andNewUserOonWeightFactor=0.00001.
- The author-diversity parameters are also exposed in VM-Ranker verified. The VM-Ranker process reads these values on its own, separately from the home-mixer param mirror.
- The parameters are
EnableAuthorDiversity=true,AuthorDiversityDecay=0.5, andAuthorDiversityFloor=0.25. - An example of a value on the home-mixer param mirror side is
AuthorSoftmaxOutputDecayFactor=0.5.
- The parameters are
- Parameters are updated without a process restart by polling the git remote (hot-reload) verified —
vm-ranker/config_sync.rs.- The
ConfigSynctask in the new file (235 lines) polls a sparse-cloned git remote every 30 seconds withgit fetch → reset. The interval setting isconfig_sync_interval_secs, and its default is 30. - The files updated by polling are
features/home-mixer/main/rust_home_mixer.ymlandabdecider/abdecider.yml. FeatureSwitchesreads the new values from the nextmatch_recipient()call.- Values do not change within a request in progress (immutable).
- The
- An explicit fallback logic was added verified. If any one of the three conditions below applies, it returns the upstream score (the weighted sum passed by home-mixer) unchanged and emits a throttled warning log.
- The three conditions are unresolved params, missing viewer context, and
ComputeValueModel=false. - This logic is
compute_or_fallback(around vm-ranker/scoring/value_model.rs:108). - The checkpoints to watch are three metrics.
vm_ranker_value_model_requests_total{mode=passthrough|value_model|fallback}vm_ranker_value_model_fallback_total{reason=...}vm_ranker_value_model_stage_total{stage=heads|cached_weighted}
- The three conditions are unresolved params, missing viewer context, and
- A new path skips recomputation for cached candidates verified.
- If the new home-mixer param
CachedPostsReuseWeightedScore(default false) is true, the cached candidate'sweighted_scoreis reused as is and head recomputation is skipped. - The gating function
RankingScorer::reuses_cached_weighted_score()and the helperunoffset_score(), which inverts the offset transform, are new inhome-mixer/scorers/ranking_scorer.rs.
- If the new home-mixer param
- A new channel sends the debias payload verified —
home-mixer/scorers/vm_ranker_debias_payload.rs.- It is a new file (52 lines). If the new home-mixer param
VMRankerSendDebiasInputs(default false) is true, it sends per-candidate content features and the request context in theexperiment_payloadproto field. - The content features are
has_video,max_video_duration_ms,has_photo,media_count,weighted_text_len,newline_count,has_url, and the same features of the quoted tweet. - The request context is
engine_time_hint_ms, the columnar user-sequence ArrowIPC bytes, product surface, and client_app_id. - The name is
debias, but the code alone does not show what is being debiased; it is likely a reserved slot for an experimental payload inferred.
- It is a new file (52 lines). If the new home-mixer param
- The vm_ranker scorer on the home-mixer side became thinner verified —
home-mixer/scorers/vm_ranker.rs.- 15 lines were added and 31 lines were deleted in the existing file.
- The inline branching that selected among 4 modes was encapsulated in the new struct
OptionalInputs { value_model, compute_value_model, cached_weighted_score, debias }.OptionalInputs::from_query()reads the home-mixer params and sets the 4 flags. write_request()/write_candidate()branch into helpers.- The score response also widened from
(tweet_id → score)to a(tweet_id → (score, weighted_score))tuple, and this value is carried in candidate.weighted_score.
B. VF filled the reply-restriction slot reserved yesterday with 4 rules
- Four new rules that decide conversation control (a setting where the author restricts who can reply) were added verified —
visibility-filtering/rules/tweet_rules.rs:328-383.- The four rules are grouped in
LIMIT_REPLIES_CONVERSATION_RULES. - The result of
LimitRepliesByInvitationConversationRule(:338-346) isLimitedEngagementReason::ConversationControl. Its condition isHasConversationControl(ByInvitation) AND NOT_LOGGED_OUT AND NOT_RETWEET AND NOT_CONVERSATION_ROOT_AUTHOR AND NOT_INVITED_TO_CONVERSATION. LimitRepliesCommunityConversationRule(:347-359) addsNOT ViewerIsFollowedByConversationRootAuthorto the same base.LimitRepliesSubscribersConversationRule(:360-372) addsNOT ViewerSuperFollowsConversationRootAuthorto the same base.- The condition of
LimitRepliesVerifiedConversationRule(:373-382) isHasConversationControl(Verified) AND ... AND NOT ViewerHasVerifiedBadge. - The
LimitedEngagementReason::ConversationControlslot is filled by these 4 rules, as reserved yesterday.
- The four rules are grouped in
- A new hydrator (a stage that fills in the data needed before a verdict) fetches data from both TES and socialgraph verified —
visibility-filtering/hydration/conversation_control_hydrator.rs.- It is a new file (310 lines), and the flow has three steps.
- TES (Tweet Entity Service, X's internal tweet metadata service)
get_conversation_controls()returns the arm, the root author, and the invited user IDs. - If the arm is
Community, socialgraphbatch_check_followed_by(viewer_id, root_authors)checks whether the root author follows the viewer (reverse edge). - If the arm is
Subscribers, socialgraphbatch_check_super_follows(viewer_id, root_authors)checks whether the viewer super-follows the root author.
- TES (Tweet Entity Service, X's internal tweet metadata service)
- The result is held in the new type
ConversationControlFeatures { control, root_author_follows_viewer: Option<bool>, viewer_super_follows_root_author: Option<bool> }. This type is invisibility-filtering/models/conversation_control.rs.
- It is a new file (310 lines), and the flow has three steps.
- The socialgraph client gained a reverse-edge lookup verified —
visibility-filtering/clients/socialgraph_client.rs.- The change is +130/−67, and the new method
batch_check_followed_by(viewer_id, user_ids)was added. - This method looks up reverse edges of FOLLOWS_GRAPH with FlockDB 500-ID chunk pagination and returns a membership dict.
- The
edge_membership_query()signature was generalized to takedirection: EdgeDirection.
- The change is +130/−67, and the new method
- The value representing a logged-in viewer now carries the profile as well verified —
visibility-filtering/models/viewer.rs.Viewer::LoggedIncarries the profile inline.Viewer::LoggedIn(u64)was expanded toViewer::LoggedIn { id, profile: ViewerProfile }.ViewerProfilehas 4 fields:allows_sensitive_media,viewer_age,has_verified_badge, andaccount_country_code.has_verified_badgeis true when Gizmoduck (X's user information service) reportsVerifiedType::Business/Governmentoris_blue_verified. TheLimitRepliesVerifiedConversationRuleseen above consumes this value.
- Hydration was reorganized to be lazy, and this is the core of this refactor verified —
visibility-filtering/hydration/mod.rs. As a result, hydration latency depends only on the set of rules each surface actually references.- The change is +360/−77. The new enum
Hydratorand the bitsetHydrators(u8)were introduced. - The enum has 7 variants:
ViewerProfile,TweetSafetyLabels,Tweet,ExclusiveContent,Author,Relationship, andConversationControl. - Each predicate declares the hydrators it needs with
const fn hydrators().RuleClause::hydrators()takes the union of all predicates in a clause to determine the required hydrator set. RuleEngine::hydrators_for(safety_level)runs only the hydrators that the active policy requires.- For example,
TweetPredicate::HasConversationControl(_)requiresHydrator::ConversationControl. This hydrator runs only when a SafetyLevel (a per-surface policy bundle) enables a rule that references this predicate.
- The change is +360/−77. The new enum
- The large deletion of TES hydration code is a reaction to the change of direction verified.
- The deletions are tes_hydrator.rs -333 and models/mod.rs -223.
- The 11 fields were migrated to the Strato (the X internal system that code goes through to read or store data) column
TweetForVisibilityin the 2026-09-18 commit (Gen4+25, 8b25829). - Since then, TES handles only the pure
PureCoreData(author_id, text, source_tweet_id). - In today's commit, the
PureCoreHydrationwrapper struct and the cache-recovery logic were both removed. - Renaming
fetch_pure_core() → fetch_author_ids()simplified the return type toTweetHydrationBatch<AuthorId>. resolve_candidates()was also compressed into an inline fallback that no longer takes a wrapper struct.
C. grox video safety verdicts gained a pipeline that extracts key frames
- It finds scene cuts with PyAV and OpenCV and extracts representative frames verified —
grox/libs/video_tools/key_frames.py.KeyFramesExtractor.extract_key_frames()in the new file (206 lines) does this work.- It resizes frames to 32×32 grayscale to build a normalized signature. It judges a cut when the L2 distance between adjacent frames exceeds
max(6.0, 4.0 × median_delta). - For each shot, it ranks by how far the mid-frame signature is from the video median. Blank shots with stddev<8.0 come last.
- The top
max_key_frames=3frames are decoded exactly by PTS. - The scan window is
window_sec=3andmax_fps=15(default).
- Key frame extraction is attached as a new task to the PTOS (grox's policy-violation verdict stack) safety pipeline verified —
grox/flows/ptos/plan_safety_ptos_key_frames.py.- The DAG in the new file (52 lines) is
rate limit → key frames filter → media hydration → adult-only init → policy detection → safemodel sex/nudity → adult content cross-validation → result sink. - To pass the filter (
task_safety_ptos_key_frames_filter.py:26-83), the media must be a video and have at leastHIGH_FAV_THRESHOLD=128. In addition, it must have an existing adult label or have fav of at leastKEY_FRAMES_UNCONDITIONAL_FAV=512. - A rate limit (60-second TTL, 10,000-entry dedupe) suppresses duplicate processing.
- The DAG in the new file (52 lines) is
- Media storage moves from blobstore to O2 verified —
grox/core/data_loaders/media_reference_bundle.py.- The change is +51/−18. The new class
S3Storewraps O2 storage (https://o2-https.o2-prod.atla-prod-storage.cluster.kube.int-x.ai) with boto3. s3://<bucket>/<prefix>URI parsing replaces the existingblobstore://namespace/subpath.- In Gen4+6 (2026-08-21), only the trait interface in
phoenix/crates/storage/xai-o2/was released, and the implementation was missing from the build. That backend is actually used in the grox media path through the boto3 S3 protocol.
- The change is +51/−18. The new class
- The media loader gained an optional key frame path verified —
grox/core/data_loaders/media_loader.py.- The change is +39, and
_construct_convo_video_from_key_frames()is new. - If the config
MediaHydrationConfig.use_key_frames(defaultFalse) is turned on, it returns aConvoVideocomposed of 3 key frames instead of the original video. - The 3-second window, 15 fps, and maximum of 3 frames are exposed as config.
- The change is +39, and
D. The cold-start options of Phoenix MoE changed
- The cold-start parameter names of Phoenix MoE (mixture of experts, a model structure that selects and uses some of several expert subnetworks) are being migrated for experiments verified —
home-mixer/params/param.rs.- The default value layout of
PhoenixMOEMaxResultsandPhoenixMoeColdStartMaxResultschanged. PhoenixRetrievalMOEInferenceClusterIdwas rotated to a different cluster constant.- This appears to be swapping parameters while an experiment on the cold-start MoE slice is running inferred.
- The default value layout of
pub compute_value_model: bool = false;pub weights: ValueModelWeights = ValueModelWeights { favorite: 0.5, reply: 5.0, retweet: 1.0, quote: 5.0, share: 2.0, follow_author: 4.0, click: 0.4, video_open: 0.07, photo_expand: 0.05, not_interested: -43.2, block_author: -31.2, mute_author: -58.8, report: -234.0 };— Summary,vm-ranker/params.rs, 2026-09-23
Canon update Folded into the public source Gen4 table, ranking section, re-ranking, diversity, and filter section, safety pipeline section, and source section of services/x.md, and adds a new Gen4+27 row. The ranking section now reflects that the generation-by-generation framing of VMRanker changed to "prepared in 3aa0fa33, executed in 1b3fec20". The weight caveat section adds that a second mirror channel of production weights (vm-ranker/params.rs, separate from param.rs) was released.
Why it matters ⚠ Claim changed: three items. The existing statements about the weight mirror, the role of VM-Ranker, and the VF reserved slots each change.
- The statement that home-mixer param.rs is the only weight mirror is partially updated. Until now, x.md treated home-mixer
home-mixer/params/param.rsas the production cron mirror (a mirror file that syncs production values daily and publishes them). Today a second channel,vm-ranker/params.rs, opened.- The home-mixer mirror holds the weights that the home-mixer process consumes, and the VM-Ranker mirror holds separate values that the VM-Ranker process consumes. The relationship between the two is still unclear.
- With
ComputeValueModel=falseby default, VM-Ranker's values appear to run only as shadow (for observation and comparison) inferred.
- The statement that home-mixer ranking is "weighted sum → filter → DPP → ads blend" is kept, but the description "vm-ranker is a score-only wrapper" is retired. Which of the three modes VM-Ranker runs in is now decided at run time.
- The three modes are a mode that only does DPP reordering, a mode that only sends inputs, and a mode that computes the value model inline.
- The commit alone does not show whether the
send_inputsmode is always on in home-mixer or whencompute_value_modelis turned on in the experiment group.
- The statement about the reserved slots in the VF verdict tree is corrected. Yesterday's daily recorded "the
LimitedEngagementReason::ConversationControlslot has only wiring and no rule yet", but 4 rules filled this slot within a day.TombstoneReason::LocalRegulationsis still reserved.
Needs a deep dive The 4 items below need separate investigation, so they are handled in a separate human-initiated session.
- What exact function form the inline computation in vm-ranker/scoring/value_model.rs takes (the aggregation of the weighted sum, and the score combination rule before DPP)
- Whether the
xai_value_modelcrate is released in this repository - Through what editing workflow the split weights of home-mixer and VM-Ranker are synchronized
- What the intent of
SendDebiasInputsis
2Of 5 new arXiv papers, 1 Spotify paper passed the radar criteria — arXiv
Of the 5 new arXiv cs.IR papers since yesterday, scanned against the radar requirements, 1 passed. Today's batch has 30 new cs.IR entries (30 parsed <entry> items, submitted 2026-09-19–09-22), and those new since yesterday are 5 submitted on 2026-09-22. The requirements are three: platform-company authors, a production A/B statement, and novelty in mechanism or scale.
- Spotify published a training method that reduces over-reliance in an LLM reranker for personalized search, together with online A/B results verified — arXiv 2609.25825.
- The paper title is Robust Fusion of Semantic and Behavioural Signals for LLM Reranking in Personalised Search (2026-09-22, accepted at USRW Workshop at RecSys 2026).
- In the HTML version, we confirmed that the email domain of all 7 authors is
@spotify.com. - The problem is shortcut learning that arises when Query Slice Stats (QSS, a summary of past success rates per query-candidate pair) are put into the prompt of an LLM cross-encoder reranker in personalized search. The model over-relies on QSS and becomes weak on rare/unseen queries.
- To mitigate this, it trains with deterministic dual-sample feature-dropout. Each example is fed to training twice: once with QSS and once without QSS.
- Offline, ranking quality rose +13.3% with QSS, and on the QSS-removed slice it improved +4.0% over naive QSS.
- In the online A/B test, both QSS-aware variants had search success of ≈+2%.
- Added to radar.md.
The other 4 were rejected, and none is on the watch list either. All four are academic research.
- SpeakerMem-R1: multi-party dialogue memory
- Discovery-Driven Table Integration: data integration
- Knowledge-as-Skill LLM Agents: benchmark
- ARAFA: Arabic fact-checking dataset
The industry blog scan is done only on Mondays, and today (Thursday) is not a target.
The latest Meta engineering blog posts are two from 2026-09-21 (Rebalancer open source, Petal subsea cable). Both are infrastructure posts, so we skip them. The latest Netflix Tech Blog post is "Leave the Class Path in the Rearview Mirror" (JVM infrastructure) from 2026-09-18, and we skip this one too.
Radar
- Spotify Robust Fusion: An LLM reranker using QSS and dual-sample dropout, with a production A/B test. Offline +13.3%, online A/B search success ≈+2%. Accepted at USRW Workshop @ RecSys 2026.
What to check next
- Watch for when vm-ranker actually runs live as a value model process — in follow-up commits, observe which experiment group turns the compute switch on and when the request counter for the value model mode leaves 0.
- The switch is the home-mixer
ComputeValueModelFeatureSwitch (FS), and the counter isvm_ranker_value_model_requests_total{mode=value_model}in the vm-ranker process.
- The switch is the home-mixer
- Check the consistency convention between the two mirrors, home-mixer param.rs and vm-ranker/params.rs — see whether edits update both places at once or one is canonical. Also see whether the
param.rs mirror syncstamp convention in recent commits extends to vm-ranker/params.rs. - Watch for when VF's local-regulation slot is filled with a real rule — Local Laws per-country takedown labels have already been deployed in UTH (under-the-hood, X's builder for account-level label transparency reports). However, how VF's own rules emit this slot is not yet public.
- The target slot is
TombstoneReason::LocalRegulations.
- The target slot is
- Watch for when the default of the switch that turns on the key frame media path in the grox key-frame pipeline changes — it is currently off by default, so observe when it is turned on in production and any adjustment to the fav-count threshold.
- The switch is the
use_key_framesconfig field, and its current value isFalse.
- The switch is the