- Sources read: 1
- Canon files updated: 1
- Radar entries: 0
1SID retrieval server released and popular posts candidate source added — X
Source xai-org/x-algorithm@b412112 — xai-org, 2026-10-03T03:26:56Z, code. These notes number the X commits after the Gen4 release in order, and this commit is Gen4+35. The whole commit is +9,128/−2,356 across 148 files.
Background SID (semantic ID) is a code that compresses a post into a multi-stage token sequence using RQ-VAE-family quantization. Generative SID retrieval is an approach in which a Transformer that takes the user history as input autoregressively generates the SID tokens of the next post and returns the posts those codes point to as candidates. The Pinterest TIGER (2024), Alibaba PailitaoGR (2025), Kuaishou OneRec (2025), Tencent BARGE (2026), and ByteDance GEAR (2026-09-30 arXiv) lines use this approach. At X, this line has been built up in three stages over the last three days (2026-10-01–2026-10-03).
- The first stage is the 2026-10-01 commit (Gen4+33, b79b947). A sketch of the slot that extracts SIDs appeared in the retrieval wire of Phoenix (the Grok-based Transformer used for For You candidate retrieval and ranking).
- In the second stage, the 2026-10-02 commit (Gen4+34, 76843a5), four files for a dedicated Transformer in Phoenix (training config, model, trainer, runner) were released. home-mixer (the service that assembles the For You feed) got a
SidSourcecandidate source skeleton and aSidRetrievalClientgRPC client. - However, that commit had no server crate to receive actual requests and return responses. home-mixer sends calls to
xai-recsys-sid-retrieval.prod.fou.twitter.biz:443, but what the Rust server behind that endpoint looks like could not be known from outside.
This commit released that server crate and changed three other things.
- Server crate: 7 new src files under
phoenix/crates/serving/xai-recsys-sid-retrieval/, 1,018 lines (8 files, 1,050 lines with Cargo.toml). - Popular posts candidate source: home-mixer got a new OON (posts from accounts the user does not follow) candidate source,
PopularPostsSource. It fills the gap in OON recommendation by feeding top-K popular authors into Thunder (the in-network index holding the latest posts of followed accounts) as "virtual following". - Ads brand safety: the default of
EnableAdsBrandSafetyVerdictV2changed from false → true. A new parameter,EnableAdsAuthorBrandSafetyFallback, which uses the author-level Grok label instead when a post label is missing, was also added (off by default). - Replay and capture: a new subsystem of 6 new files, 1,334 lines, appeared under
visibility-filtering/staging/on the VF (visibility-filtering, the service that decides whether a post may be shown to a viewer) side. Including 3 moved files, the additions total 1,636 lines.
What we learnedA. The SID retrieval server builds an index from an SID snapshot and finds candidates by prefix range
The SID retrieval server crate was newly released with 7 src files (8 including Cargo.toml) verified —
phoenix/crates/serving/xai-recsys-sid-retrieval/.- Component files:
src/service.rs:208,src/index.rs:223,src/loader.rs:170,src/http_api.rs:109,src/main.rs:115,src/seed_sids.rs:114,src/metrics.rs:79.
- Component files:
The service interface is defined by a separate proto crate and has only one RPC verified —
sid_retrieval.proto:46.- The proto crate is
xai-recsys-sid-retrieval-proto, and the RPC isrpc Retrieve(RetrieveRequest) returns (RetrieveResponse). - The request fields are
seed_post_ids/max_results/max_per_seed/min_prefix_depth/max_prefix_depth/caller. - The response carries
candidates,seeds(marking where each seed came from with Index/SidService/Missing labels),snapshot_timestamp_secs, andindex_posts.
- The proto crate is
The index packs the SID into a single integer key and keeps two post lists sorted in different orders verified —
src/index.rs:5-110.SID_LEVELS = 6,BITS_PER_LEVEL = 8,MAX_CODE = 255. Each level has 2^8=256 codes.- The full SID is packed into a 48-bit u64 key, shifting left by 8 bits per level starting from the top level. In theory the key space is 256^6 ≈ 2.8 × 10^14.
- It keeps two vectors of
IndexedPost { key, post_id, author_id }.by_keyis sorted by(key ASC, post_id DESC)and used for prefix-range search.by_postis sorted by(post_id ASC)and used for binary search from seed post_id to SID key. prefix_range(key, depth)returns[lo, hi), keeping only the topdepthlevels and setting the lower(6-depth)*8bits to 0. It callsby_key.partition_pointwith this range to pick the posts inside it. Each depth level costs O(log N) + O(result size).
Search collects candidates by descending from the deepest prefix depth to the shallowest verified —
src/index.rs:138-193.- Each post enters the result "only once, at the deepest prefix level it shares". A post pulled at depth 6 is not pulled again at depths 5–3. This is implemented by walking the depths in reverse and subtracting the region already scanned (
consumed). - Within the same depth, posts with a larger post_id (= more recent) come first. It cuts the top K with
select_nth_unstable_by(post_id DESC)and then sorts. - The seed post itself is filtered out by a global excluded set. When requesting with multiple seeds, seed₂ does not get mixed into the results of seed₁.
for depth in (params.min_prefix_depth..=max_depth).rev() { let (lo, hi) = prefix_range(seed.key, depth); let (start, end) = self.key_range(lo, hi); let mut level: Vec<&IndexedPost> = match consumed { None => self.by_key[start..end].iter().collect(), Some((inner_start, inner_end)) => self.by_key[start..inner_start] .iter() .chain(self.by_key[inner_end..end].iter()) .collect(), }; level.retain(|p| !excluded.contains(&p.post_id)); ... picked.extend(level.into_iter().map(|p| Candidate { post_id: p.post_id, author_id: p.author_id, seed_post_id, shared_prefix_depth: depth as u32, })); consumed = Some((start, end)); }- Each post enters the result "only once, at the deepest prefix level it shares". A post pulled at depth 6 is not pulled again at depths 5–3. This is implemented by walking the depths in reverse and subtracting the region already scanned (
Candidates from multiple seeds are taken alternately by round-robin verified —
src/index.rs:196-222.- It cycles seed 1 → seed 2 → ... → seed N → seed 1 → ..., and removes duplicates with a global
HashSet<post_id> emitted. - It stops when it reaches
max_resultsor when nothing more can be taken. - As a result, position bias is equalized so that it does not skew toward a particular seed.
- It cycles seed 1 → seed 2 → ... → seed N → seed 1 → ..., and removes duplicates with a global
The snapshot loader checks the path every 30 seconds and, if it changed, rebuilds the whole index and swaps it in atomically verified —
src/loader.rs:1-170.- The input format is Arrow Parquet (
post_sid_snapshot.parquet). It has three columns,post_id: int64,author_id: int64, andpost_sid: list<int64>, and the batch size is 65,536. - The timestamp is taken from the file name suffix (
*_<unix_ts>.parquet), and if parsing fails, the file mtime is used instead. watch_snapshots(path, poll_interval=30s, index, ready_tx)callscanonicalize(path)every 30 seconds and reloads everything if the path changed. The build runs intokio::spawn_blocking, and on success it does an atomic swap withArcSwapOption<SidIndex>. It then raises the initial ready flag of/readyzwithready_tx.send(true).- In other words, there are no real-time upserts; the pattern is to push in, in batches, a code table generated on the model side.
- Invalid SIDs are dropped silently.
pack_full_key(row_codes)returnsNoneiflen != 6or any code is outside 0..=255, and theskippedcounter goes up then.
- The input format is Arrow Parquet (
A seed's SID is looked up first in the local trie, and if it is missing, a separate gRPC service is asked verified —
src/seed_sids.rs:1-114.- The external service is
SidLookupService(xai-recsys-sid-proto, with the endpoint set by the--sid-endpointflag). It looks up in batches and puts the results in a moka cache (2 million entries, 6-hour TTL). - That is, the service receives seed SIDs from two places. One is its own parquet snapshot (= all corpus-side codes), and the other is the external service (= presumably the real-time code publishing line on the Phoenix side).
Historical context:
SidLookupServiceis, in effect, the lookup service that disappeared in Gen4+15 (2026-09-04, 9b0dc31), when the "SID retrieval sidecar was removed wholesale from the Phoenix serving engine", now reborn here as a separate external service. At the time, the sidecar served as a lookup that "converts each post ID in the user history into an SID" on Phoenix requests; after it disappeared, history SIDs were absorbed into a form that arrives preloaded in the request. Now the shape has changed so that the retrieval server calls that service when a seed → SID conversion is needed.- The external service is
The server emits 3 kinds of metrics verified —
src/metrics.rs.sid_retrieve_requests{caller, result=ok|empty|unavailable}countersid_retrieve_seeds{source=index|sid_service|missing}countersid_retrieve_candidates{caller}andsid_retrieve_latency_seconds{caller}histograms
The server's default behavior is set by run flags. By default it returns up to 800 results and 100 per seed, and searches prefix depths 3–6 verified —
src/main.rs:26-52.--grpc-port=9090 · --http-port=8080 · --snapshot-poll-secs=30 · --sid-timeout-ms=200 · --sid-cache-size=2_000_000 · --max-seeds=200 · --default-max-results=800 · --default-max-per-seed=100 · --default-min-prefix-depth=3 · --default-max-prefix-depth=6
What we learnedB. A source was added that takes posts of popular authors from Thunder and uses them as OON candidates
The new source passes the top authors by follower count to Thunder as if the user follows them, and emits the posts Thunder returns as candidates verified —
home-mixer/sources/popular_posts_source.rs:68-115.PopularPostsSourceruns only when!has_cached_posts && !in_network_only && EnablePopularPostsSource==true.- It calls
PopularAuthorsCache.maybe_spawn_refresh(now_ms). It starts a background refresh while preventing duplicates with a single AtomicBool. top_author_ids(top_k)picks the author_ids of the top K (default 1,000) by follower_count in descending order.- It sends Thunder
GetInNetworkPostsRequest { user_id, following_user_ids=[top K authors], max_results=500, per_author_limits={3, 0}, exclude_tweet_ids=seen_ids, algorithm, debug=false, is_video_request=false }. Thunder treats those K authors as if the user follows them and returns posts by recency from its own in-network post store. - The response posts go out in PostCandidate with the label
served_type=ForYouPhoenixRetrieval. It is the same label as PhoenixSource.
let authors = self.popular_authors.top_author_ids(top_k); ... let request = GetInNetworkPostsRequest { user_id: query.user_id, following_user_ids: authors, max_results: query.params.get(PopularPostsMaxResults), exclude_tweet_ids: query.seen_ids.to_vec(), per_author_limits: Some(PerAuthorLimits { max_posts_per_author: Some(query.params.get(PopularPostsMaxPerAuthor)), max_replies_reposts_per_author: Some( query.params.get(PopularPostsMaxRepliesRepostsPerAuthor), ), }), ... };The popular authors cache is stored in its own binary format v1 verified —
util/popular_authors.rs:64-104.- The cache is
PopularAuthorsCache, and the format isversion=1(1B) + updated_at_ms(8B BE) + count(4B BE) + [author_id(8B BE), follower_count(8B BE)] * count. - For 1,000 authors it is 13 + 16,000 = 16,013 bytes. The design stores everything under a single key in Manhattan (X's internal key-value store).
- The refresh period is 60 seconds when the cache is empty and 60 minutes when it is not. It is a common pattern of retrying aggressively only when the cache is empty.
- The cache is
Five parameters were added, and the source itself is off by default verified —
home-mixer/params/param.rs.
| Parameter | Default | Role |
|---|---|---|
EnablePopularPostsSource |
false | Turns the source itself on |
PopularPostsTopAuthors |
1000 | How many top authors from the KV store to feed in as "virtual following" |
PopularPostsMaxResults |
500 | Cap on the Thunder response |
PopularPostsMaxPerAuthor |
3 | Max posts for 1 author |
PopularPostsMaxRepliesRepostsPerAuthor |
0 | Replies/retweets are excluded from the source entirely |
What we learnedC. The ads brand safety V2 verdict was turned on by default for the first time, and an author label fallback switch was added
The default of the ads brand safety V2 verdict switch changed from false to true verified —
home-mixer/params/param.rs.- The switch is
EnableAdsBrandSafetyVerdictV2. After theBrandSafetyVerdictV2logic was released in Gen4+2 (2026-08-17, b089ce6), the default stayed off until today, when it became on for the first time. - This file is a cron mirror that syncs production config values daily.
- The value arrived at the mirror sync point (
last sync 2026-10-01T16:00:46Z → 2026-10-02T16:00:41Z), and it appears to mirror a state pushed by a production cron at some point during 2026-10-02 inferred.
- The switch is
A new author label fallback switch was added, off by default verified — same file.
- The switch is
EnableAdsAuthorBrandSafetyFallback. When an author-level Grok label (GrokSfa/GrokNsfaLimited/GrokNsfa) exists but a post-level label does not, it is a path that puts the author label in the post label's place as a fallback.
- The switch is
The verdict grades and the author fallback labels are defined as two enums verified —
home-mixer/models/brand_safety.rs:6-80.#[repr(i32)] pub enum BrandSafetyVerdict { Unspecified = 0, Safe = 1, LowRisk = 2, MediumRisk = 3, HighRisk = 4, } pub enum AuthorBrandSafetyFallback { Sfa, NsfaLimited, Nsfa }The verdict has 4 levels: Safe / LowRisk / MediumRisk / HighRisk. It is consumed by the adjacency matrix (
MultiRiskAdsBlenderin Gen4+10) that pairs withAdAdjacencyControl.brand_safety_riskon the ads blender side.- That is, the axis that decides "post safety → whether an ad can be inserted" on ads surfaces is on by default from today.
What we learnedD. VF got a replay and capture subsystem separate from live traffic
- A VF staging folder that did not exist until yesterday was newly created with 6 new files, 1,334 lines verified. Including 3 moved files, the additions total 1,636 lines.
- The folder is
visibility-filtering/staging/. - Changes by file:
recording.rs(+744 new),replay_corpus.rs(+299 new),capture.rs(+221 new),reference/tweetypie.rs(+204/-81 renamed),reference_compare.rs(+63/-39 renamed),reference/mod.rs(+35/-72 renamed),main.rs(+39 new),serve.rs(+29 new),mod.rs(+6 new).
- The folder is
- This subsystem has its own crate entry point and is meant to run VF in a replay and capture mode fully separated from live traffic verified.
- The entry point is
staging/main.rs. - The referential comparison (Tweetypie reference) code that was scattered elsewhere in the VF tree until yesterday was also moved here.
- The entry point is
- Three things to examine in a deep dive in the next window:
- What recording.rs (744 lines) records and in what format (presumably parquet/jsonl)
- How capture.rs samples live traffic
- What the replay contract in replay_corpus.rs looks like
What we learnedOther changes
- The checkpoint restore side of the row-sharded embedding CUDA kernel bundle released in yesterday's commit (Gen4+34) was completed in Python.
- The kernel bundle is
cuda/row_emb/, and the restore file isphoenix/xrex/train/row_embedding_restore.py(+488 new). In effect, yesterday's shared adapter (recsys_async_emb.py) and today's restore utility have both arrived. A deep dive is needed.
- The kernel bundle is
- A module for server-side latency metrics only was added to the Phoenix ranking server, separate from the inflight metrics. It widens operational observation and is not a model-side change.
phoenix/crates/serving/xai-recsys-engine/src/grpc_server_latency.rs(+361 new)
- There was a reorganization that cleanly detaches utilities that the SID retrieval trainer/model, split out yesterday, had shared with the common code. The common code has a net decrease (-477).
trainer_recsys.py(+376/-853),recsys_model.py(+786/-431)
- A client that fetches author-level Grok labels on the ads side was added. The fallback path in C above uses this client.
home-mixer/clients/author_brand_safety_client.rs(+281 new)
is_arm_gated_retrieval, deleted in yesterday's commit (Gen4+34), is used again on the author cold-start side. It was revived as a different branch specific to cold start.home-mixer/scorers/author_cold_start.rs(+53)- That is, yesterday's deletion was not "abandoning this axis itself" but "moving it out of the retrieval branch to the author cold-start side".
- The SimClusters ANN cache client was split out from the shared crate.
home-mixer/clients/simclusters_ann_cache_client.rs(+211 new)
- A pacing-input axis was declared in VMRanker (the stage that reorders the candidate bundle to send to the feed). The linked param
VMRankerSendPacingInputsis off by default.vm-ranker/scoring/value_model.rs(+96/-5),scoring/mod.rs(+85/-2)
Canon update Three sections of services/x.md were revised. In the candidate generation (retrieval) section, the SidSource row gained "server-side algorithm (parquet 30-second polling · reverse depth order · round-robin interleave) + 5 server cap values", and a PopularPostsSource row was created. The safety/VF pipeline section gained a new ### Gen4+35 (2026-10-03, b412112) section (7 steps A–G and 3 semantic stages). In the sources section, b412112 was added at the end of the commit list.
Why it matters This release reveals a design in which the SID retrieval server does no generation (beam search) and only searches, by prefix range, a code table built beforehand on the corpus side. Both new candidate sources (SID, popular posts) are off by default, so the day a cron turns one on is the checkpoint to watch for live traffic. The two ads changes move in the direction of widening brand safety labeling from post level to author level.
- The "actual server crate" was attached today to the three axes of generative SID retrieval released yesterday (home-mixer source, Phoenix model/trainer, proto). It is the third and last piece of a release spread over three days.
- Until yesterday there were only the training recipe and the home-mixer source skeleton, and the server was empty.
- The
xai-recsys-sid-retrievalcrate released today states three things explicitly, so the server's actual query complexity can be measured from released code alone.- It is a fully static index that reloads the whole parquet snapshot every 30 seconds. There are no real-time upserts.
- It does range search over a 6-level trie packed into a 48-bit u64 key, based on
partition_point. - It uses an algorithm that trims recall with reverse depth order and round-robin interleave.
- Until yesterday this slot read as "the server will do generative decoding". Today's release reveals the design choice that generation is done beforehand on the corpus side (a batch code table) and the server only does prefix-range search.
- That is, there is no inference-time beam search on the server. Codes the model produced come in contained in the corpus, and the server is a KNN-like server that searches by seed code and shared prefix length.
- Since
EnableSidSource=falseis still the default, the checkpoint to watch for live traffic is the day it turns ON in the FeatureSwitches cron.
PopularPostsSourcereuses Thunder's in-network index for "global popularity". It places the results under theForYouPhoenixRetrievallabel and mixes them with Phoenix two-tower retrieval (retrieval that embeds users and posts separately and matches them) responses.- It is interesting that it is designed not to be distinguishable from PhoenixSource by label. To a scorer/filter it looks like "Phoenix retrieval returned K more", so it mixes in at the upper ranking stages with the same distribution as PhoenixScore.
- The mechanism is not embedding-based retrieval. It is a retro approach that feeds the "global popularity centroid of the follow graph" into Thunder as "virtual following".
- It is a typical global popularity backfill pattern, used "so that Thunder responds with something even if the user follows no one" or "to fill the gap when other OON sources cannot return results because user engagement signals are too few" inferred.
- Since
EnablePopularPostsSource=falseis still the default, the actual activation time is the checkpoint to watch in the next window.
- Both ads changes expand "SFA (Suitable For Advertising) labeling from post level → author level".
- It is no coincidence that the switch of
EnableAdsBrandSafetyVerdictV2=trueand the introduction ofEnableAdsAuthorBrandSafetyFallbackarrived on the same day. The direction is to use the author's past label pattern as a fallback for posts that do not yet have a post-level Grok label. - This is a signal that they intend to complement, with author-level aggregation, situations where Grok classifier coverage is sparse per post (especially for new posts) inferred.
- It is no coincidence that the switch of
Radar
No papers were added to the radar today. Of the 30 requested in the arXiv cs.IR 2026-10-01–2026-10-02 window, all 30 <entry> items were parsed, and 0 of them cleared all three acceptance criteria.
- ① Platform authors or a production A/B test
- ② Mechanism novelty
- ③ Large-scale results
Three borderline items were also all rejected.
- arXiv 2609.39312 is Learning Multiresolution Relevance for Hierarchical Generative Retrieval by Weihao Shen and 7 others. It learns multiresolution relevance in hierarchical generative retrieval. It was rejected because the platform and deployment axis of ① is empty.
- The author affiliation could not be confirmed directly from arXiv primary metadata, and the abstract has no production/deployment claim.
- arXiv 2609.39319 is Residual Trajectory Distillation for Generative Retrieval by the same 7 authors. It is an approach that distills the intermediate trajectory of SID RQ (residual quantization). It was rejected for the same reason.
- It is an academic line paired with 2609.39312, and neither makes an industry deployment claim.
- arXiv 2609.39828 is KUAISHOU Explorer LLM-Rec Challenge 2026: Reasoning Generative Recommendation by Kuaishou authors. The body is a challenge overview with no mechanism novelty, so it was rejected on ②, mechanism novelty.
- It is merely a source reconfirming that OneRec/OneRec-V2 were deployed at Kuaishou (a line already on the radar).
In the 2609.39xxx number range posted in the window after the Meta ETT% recommendation training infrastructure paper (arXiv 2610.02057) that was rejected from yesterday's radar, no new platform-affiliated mechanism worth tracking was found.
What to check next
- When the SID source and the popular posts source turn on in cron — both were released off by default, so the point at which the FeatureSwitches cron flips either one to true is when live traffic applies.
- The switches are
EnableSidSourceandEnablePopularPostsSource. - The checkpoints to watch are the home-mixer-side
SidSource.retrieved{type=seeds|retrieved|hydrated}and the Thunder-sideGetInNetworkPostsRequestcall pattern.
- The switches are
- Call metrics of the SID retrieval server — two server-side counters reveal the live traffic distribution.
- The endpoint is
xai-recsys-sid-retrieval.prod.fou.twitter.biz. - The counters are
sid_retrieve_requests{caller, result=ok|empty|unavailable}andsid_retrieve_seeds{source=index|sid_service|missing}. - A high share of
source=sid_servicemeans the real-time conversion cost for "posts not yet in the corpus-side snapshot" is large. A high share ofsource=indexmeans the post corpus has stabilized.
- The endpoint is
- Deep dive on the
VF staging/replay and capture subsystem — three things have not yet been examined:- What format and what traffic recording.rs (744 lines) records
- The live sampling strategy of capture.rs
- The replay contract of replay_corpus.rs
- Deep dive on the 488 lines of
row_embedding_restore.py— the checkpoint restore side of the row-sharded embedding CUDA kernel released yesterday, paired with yesterday's shared adapter. - Whether the server side of
SidLookupServiceis released — whether the external service called by the SID retrieval server released today is released as a separate crate in the next commit is the checkpoint to watch in the next window.- The external service is
xai-recsys-sid-proto::SidLookupService. - Currently the default is localhost with
--sid-endpoint=http://localhost:50051, and the name of the externally exposed endpoint is still not visible in the repo.
- The external service is
- Deep dive on the generative SID retrieval model side — questions that yesterday's commit (Gen4+34) deferred to a deep dive remain.
- Beam decoder math (stopping condition of prefix retrieval, score aggregation)
- Structure of the SID → post reverse-resolution corpus view
- Training loss (weights of one positive = FAV and 7 hard negative actions)
- training throughput
- These questions remain open even after today's server crate revealed that "the server does not do beam search; codes are extracted beforehand on the corpus side and pushed in as a batch, and the server only does prefix-range search".
- In the next window we will look at which batch pipeline the model-side code extraction currently runs in. The
max_steps ≈ 3.1Bin the training config is the model-side checkpoint, and the serving side receivespost_sid_snapshot.parquet, which that checkpoint builds by running over the whole corpus.