km.idaeo.ai · IDAEO 知識庫

🏛 Part of the "ai" topic shelf

X Algorithm Evidence Compendium

This article consolidates 58 machine-verified proposition datasets and source-code paths from X's open-source recommendation algorithm project, analyzing behavior weights, filter pipelines, and visibility rules line by line. It is suited to engineers and researchers who need precise source-code evidence, verification scripts, and technical specifications.

xai-org / x-algorithm · HEAD a389166

Every claim is accompanied by a source-code location and a reproduction command. All figures were generated by scripts run against the code, not manually transcribed.

Repository xai-org/x-algorithm (updated 2026-08-13) HEAD a389166f6cf5da70a286b568c87695d4dcdce3a1 Size 2,015 files / 21 MB / 5 commits Analysis 2026-08-14 Verification 58 machine claims, 58 PASS / 0 FAIL Self-test inject an incorrect expected value → correctly produces 1 FAIL (gate works)

① Machine Claim Verification Table

All 58 entries below were generated by an actual run of verify-all.sh. The figures on this page were converted directly from that script’s output into tables, with no manual transcription. The script itself is in the appendix.

should-FAIL: feed in a known-wrong value to prove the checker actually stops it
should-FAIL: feed in a known-wrong value to prove the checker actually stops it
ItemValueNotes
PASS58
FAIL0
should-FAIL counterexample test (an injected wrong value must be caught)1
IDClaimMeasured valueResult
C01HEADa389166f6cf5da70a286b568c87695d4dcdce3a1PASS
C02Number of param! blocks183PASS
C03Total files2015PASS
C04FavoriteWeight0.5PASS
C05ReplyWeight5.0PASS
C06RetweetWeight1.0PASS
C07ShareViaCopyLinkWeight20.0PASS
C08ReportWeight-234.0PASS
C09MuteAuthorWeight-58.8PASS
C10BlockAuthorWeight-31.2PASS
C11NotInterestedWeight-43.2PASS
C12NEW_USER_OON_WEIGHT_FACTOR0.00001PASS
C13NEW_USER_MIN_FOLLOWING5PASS
C14NEGATIVE_SCORES_OFFSET0.001PASS
C15MAX_POST_AGE (seconds)172800PASS
C16NewUserAgeThresholdSecs default0PASS
C17AuthorDiversityDecay0.5PASS
C18AuthorDiversityFloor0.25PASS
C19OonWeightFactor0.75PASS
C20Number of pre-filters17PASS
C21Number of post-filters3PASS
C22Number of filters files (excluding mod)27PASS
C23Number of declarations in mod.rs26PASS
C24Undeclared filterpopular_topics_author_dedup_filterPASS
C25Number of base visibility rules28PASS
C26Number of OON bonus rules26PASS
C27Prediction heads used by the ranker27PASS
C28MinVideoDurationMs10_000PASS
C29EnableQuotedVqvDurationCheckfalsePASS
C30EnableInventoryHoldoutfalsePASS
C31VMRankerSendHeadWeightsfalsePASS
C32Rust-side bridge matches0PASS
C33phoenix [:,:,4] matches0PASS
C34phoenix [:,:,2] matches0PASS
C35phoenix [:,:,3] matches0PASS
C36Rust uses of ContinuousActionName2PASS
C37Unique continuous actions used by Rust2PASS
C38BRIDGE_PROBABILITY enum value4PASS
C39Times the bridge switch is set True0PASS
C40Number of continuous-head types with loss1PASS
C41NUM_CONTINUOUS (reference implementation)8PASS
C42DWELL_INDEX (reference implementation)1PASS
C43history_seq_len1022PASS
C44num_layers8PASS
C45emb_size2560PASS
C46ActionName enum entries204PASS
C47PLACE_HOLDER placeholders8PASS
C48Whether the two proto files are identicalsamePASS
C49ProfileClickWeight0.0PASS
C50DwellWeight0.0PASS
C51QuotedVqvWeight0.0PASS
C52ContClickDwellTimeWeight0.0PASS
C53ContActiveSecs5mResidualNormWeight0.0PASS
C54BidirectionalFollowDwellWeightBoost0.0PASS
C55BidirectionalFollowReplyWeightBoost15.0PASS
C56ContDwellTimeWeight0.004PASS
C57NotDwelledWeight-0.02PASS
C58Number of parameters with Weight in the name33PASS

② Complete Weight Table

Source: home-mixer/params/param.rs. After parsing all 183 param! entries in the file block by block, 33 with Weight in their names remain. The values are built-in code defaults and can be overridden online through feature switches.

Extraction pitfall (I fell into it in the first version): This file has both multiline and single-line param! forms. Scanning only the multiline form silently misses 13 entries—and all actual ranking weights are among those 13. It took the actual count of 170 differing from grep -c '^param!(' at 183 to catch it. The appendix parser handles both forms.
The same thing is written two ways; a template that accepts only one silently misses the other
The same thing is written two ways; a template that accepts only one silently misses the other

Positive weights (17)

LineParameterDefaultCorresponding action
L325ShareViaCopyLinkWeight20.0Share by copying a link
L284BidirectionalFollowReplyWeightBoost+15.0Reply boost for mutual follows
L283ReplyWeight5.0Reply
L319ShareViaDmWeight5.0Direct-message share
L332QuoteWeight5.0Quote repost
L345FollowAuthorWeight4.0Follow the author
L318ShareWeight2.0General share
L296RetweetWeight1.0Repost
L246OonWeightFactor0.75Discount factor for unfollowed content
L282FavoriteWeight0.5Like
L266TopicOonWeightFactor0.5Unfollowed-content discount in topic mode
L309ClickWeight0.4Open a post
L310OpenLinkWeight0.2Open an external link
L297PhotoExpandWeight0.05Expand a photo
L303VideoOpenWeight0.05Open a video
L317VqvWeight0.05Qualified video view
L333QuotedClickWeight0.05Open the original post being quoted
L351PostUnexploredWeight0.02Exploration-content bonus
L375ContDwellTimeWeight0.004Dwell time in seconds (per second)

Negative weights (5)

LineParameterDefaultEquivalent number of likes
L442ReportWeight−234.0468
L436MuteAuthorWeight−58.8117.6
L424NotInterestedWeight−43.286.4
L430BlockAuthorWeight−31.262.4
L443NotDwelledWeight−0.020.04
ItemValueNotes
Total positive weights (19 entries, excluding the two OON discount factors)+43.324
Total negative weights (5 entries)−367.22
Negative/positive multiple8.48×
How to read this 8.48× without getting it wrong: This compares weight pricing, not final scores. The formula receives “weight × model-predicted probability.” Report and block probabilities are extremely small, while like probabilities are much larger, so the actual contribution of a post’s negative terms will not be 8.48× that of its positive terms. Those probabilities are generated by the model and are absent from the open-source code, so they cannot be calculated. The valid statement is: X prices negative signals an order of magnitude higher than positive signals.

Zero weights (6)

LineParameterDefaultCharacteristic (see ⑦)
L311ProfileClickWeight0.0The model is trained; its price is simply 0
L331DwellWeight0.0The model is trained; its price is simply 0
L339QuotedVqvWeight0.0The model is trained; it also has a length gate
L381ContClickDwellTimeWeight0.0No training loss = schema placeholder
L417ContActiveSecs5mResidualNormWeight0.0No training loss = schema placeholder
L290BidirectionalFollowDwellWeightBoost0.0A formally disabled switch with a guard and tests

The 33rd: a 19-dimensional logistic regression

DwellRegretGateWeights (param.rs:534) is not a single value but a string of 19 coefficients. Its input is your account history (interaction-sequence length, likes, replies, negative feedback, 7-day/1-day activity, follower count, following count, account age…), and its output is the probability that you will regret viewing this post. Excerpt:

seq_len:0.530298     n_reply:0.485541     n_click:-0.176675
n_fav:-0.082139      n_1d:-0.241730       account_age_years:-0.045455
n_profile_follow:-0.221285                n_negfb:0.031839

③ The Scoring Formula, Line by Line

Individual term = model predicted probability × action weight                    ranking_scorer.rs:416-418
       fn apply(score: Option<f64>, weight: f64) -> f64 {
           score.unwrap_or(0.0) * weight
       }

26 individual terms form an array                                   ranking_scorer.rs:470-510

Split into positive and negative piles                                        ranking_scorer.rs:513-521
       for t in terms { if t >= 0.0 { pos += t } else { neg -= t } }

combined = pos − neg                                ranking_scorer.rs:427

Final shift                                            ranking_scorer.rs:524-532
       if total_sum == 0.0        → max(combined, 0)
       else if combined < 0.0     → (combined + negative_sum) / total_sum × 0.001
       else                       → combined + 0.001

NEGATIVE_SCORES_OFFSET = 0.001                      params/config.rs:40
negative_sum = −(not_interested+block+mute+report+not_dwelled)   :127
total_sum    = positive_sum + negative_sum                       :128
The real effect of this offset: Every post calculated to have a negative score is compressed into the extremely narrow 0 ~ 0.001 range. Ranking differences among negative-score posts almost collapse to zero: they all sink to the bottom in a clump. This is not “ranked a bit lower”; it is “dropped out of the visible range.”

Two conditional boosts

MechanismBehaviorLocation
Mutual-follow reply boostWhen both accounts follow each other, reply weight 5.0 → 20.0 (5.0+15.0)ranking_scorer.rs:186-193
Mutual-follow dwell boostSame mechanism; default 0.0 (effectively disabled)ranking_scorer.rs:215-222
The bonus fires only when both keys turn at once
The bonus fires only when both keys turn at once

④ Three Adjustments

AdjustmentFormula and coefficientsLocation
Same-author decay(1 − 0.25) × 0.5^k + 0.25, where k is that author’s post number. Post 1 ×1.0, post 2 ×0.625, post 3 ×0.4375… floor 0.25ranking_scorer.rs:614-616 param.rs:229-238
Unfollowed-content discountAuthors you do not follow: ×0.75; with a topic, more severe: ×0.5ranking_scorer.rs:681-698
New-user modeFor new accounts meeting the criteria, unfollowed content ×0.00001. Criteria: account age < NewUserAgeThresholdSecs and following count ≥ 5ranking_scorer.rs:688-698 config.rs:38-39
Do not turn 0.00001 into a sensational conclusion: The default value of NewUserAgeThresholdSecs is 0 (param.rs:273). Account age cannot be below 0 seconds, so this mechanism is disabled by default. The correct statement is: the switch exists, its strength is extreme, and it is currently off.
A control with enormous leverage, latched in the closed position
A control with enormous leverage, latched in the closed position

Three “built but off” switches

SwitchDefaultWhat happens if enabled
NewUserAgeThresholdSecs0 secondsUnfollowed content for new accounts ×0.00001, effectively leaving only the echo chamber
EnableInventoryHoldoutfalseBased on a (post ID × user ID) hash, stably withhold a specified percentage of original posts/replies/reposts. All three percentages default to 0
VMRankerSendHeadWeightsfalseSend the weight table to the vm-ranker reranking service; currently it sends prediction scores only, not weights

⑤ 20 Filters

Source: home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs L344–362 (17 pre-filters) and L417–421 (3 post-filters). All are registered unconditionally; none has a conditional switch.

Filters run in order; the first one that says no ends it
Filters run in order; the first one that says no ends it
#LineFilterWhat it removes
1L345DropDuplicatesFilterThe same post retrieved from different sources
2L346CoreDataHydrationFilterFailed loading of body text or metadata
3L347AgeFilterOlder than 48 hours (MAX_POST_AGE = 172800 seconds)
4L348SelfTweetFilterPosts you made yourself
5L349OONRetweetReplyFilterReposts and replies from unfollowed accounts
6L350OONNsfwSimclustersFilterRetrieved by SimClusters, authored by an account labeled adult content, and not followed by you
7L351RetweetDeduplicationFilterThe same post reposted repeatedly
8L352IneligibleSubscriptionFilterPaid content unavailable because you are not subscribed
9L353PreviouslySeenPostsFilterPosts you have already seen
10L354PreviouslySeenPostsBackupFilterSame as above; blocked again using a second exposure record
11L355PreviouslyServedPostsFilterPosts already served in this session
12L356MutedKeywordFilterPosts matching your muted keywords
13L357AuthorSocialgraphFilterAccounts you have blocked or muted
14L358VideoFilterBlocks video when the request excludes video
15L359TopicIdsFilterNot in specified topics / in excluded topics
16L360NewUserMinEngagementFilterUnfollowed content from new accounts lacks sufficient engagement
17L361InventoryHoldoutFilterDeterministically withhold a percentage by hash (default 0%)
18L418VFFilterItems for which the visibility system says drop
19L419AncillaryVFFilterItems whose parent/quoted/reposted source is dropped
20L420DedupConversationFilterOther branches of the same conversation
Reconciliation result: The 17+3 filters and their order claimed in README match the code entry by entry, with zero displacement. X did not embellish this item.

⑥ 54 Visibility Rules

Source: visibility-filtering/rules/registry.rs (912 lines). The first rule that answers drop governs; later rules are not evaluated.

A mechanism that turns one way only: it can cut, never clear
A mechanism that turns one way only: it can cut, never clear
ItemValueNotes
Rules applying to everyone, base_home_rules() L101–13228
Extra rules for unfollowed content, oon_drops L138–17026
Total rules unfollowed content must pass54

The 28 base rules, in order: 4 author-state rules (suspended/deactivated/erased/offboarded) → protected account → you blocked it → you muted it → muted repost → 8 content-label rules (PDNA, rejected, spam, emergency, hateful conduct, violent conduct, abuse, civic integrity) → not broadcast → expired → 2 legal takedowns → 3 sensitive-content rules for logged-out/minor/no age entered → exclusive content → the final 4 are interstitial warnings, not direct removals.

The 26 additional rules for unfollowed content can all only drop: DMCA media, region-restricted media, 4 adult-content determinations, high-recall adult and spam detection (erring toward false positives), malicious URLs, “do not amplify” labels, and more.

⑦ Three Characteristics of the 6 Zeroed Weights

The distinction is made by whether the model was trained to predict the action. Trained but priced at 0 = a real lever; no training loss at all = merely a placeholder.

Shared mechanism: There is no guard that says “skip when the weight is 0” (the sole exception is F). The formula is always probability × weight; a 0 weight is still calculated and still enters the array of 26 terms, always yielding 0. The model still spends compute predicting it, and the pipeline still carries it; it is only multiplied by zero at the end.

Category 1: The model is trained; its price is simply 0 — real levers

ParameterCorresponding prediction headEffect when enabled
ProfileClickWeightCLIENT_TWEET_CLICK_PROFILE = 30“Click the author avatar” starts counting. It already enters positive_sum (:112); raising it increases both numerator and denominator
DwellWeightCLIENT_TWEET_RECAP_DWELLED = 11“Whether the user dwelled” starts counting. The current state is “seconds are monetized; whether you dwelled is not”
QuotedVqvWeightCLIENT_QUOTED_TWEET_VIDEO_QUALITY_VIEW = 48A qualified video view on the quoted post starts counting, but it also has a length gate
QuotedVqv’s belt and suspenders: util/candidates_util.rs:42-60 has a same-named function, quoted_vqv_weight(), which gates the weight to zero: when EnableQuotedVqvDurationCheck is enabled (default false), a quoted video must be > 10,000 milliseconds or the weight is directly rewritten to 0.0.

Category 2: No training loss is even configured — merely a placeholder

In the entire phoenix/ training configuration, only the continuous head DWELL_TIME has a loss function (recsys_model.py:473-479 and xrecsys.py:566-585; the gallery layout uses tweedie and the rest use mae).

ParameterContinuous headReality
ContClickDwellTimeWeightCLICK_DWELL_TIME = 2No training loss. But a low-like-rate penalty has already been built (baseline 0.01/alpha 0.5/floor 0.01/cap 1.0, ranking_scorer.rs:195-213) onto an item with weight 0
ContActiveSecs5mResidualNormWeightACTIVE_SECS_5M_RESIDUAL_NORM = 3No training loss. Measures “whether this post causes you to stay longer in the App”
These two also have a structural problem: asymmetric numerator and denominator. positive_sum (:104-122) does not include these two continuous terms, but they do enter the numerator in the sum of 26 terms. total_sum is precisely the denominator of the negative-score compression formula (:524-532). Once raised, the numerator grows while the denominator does not, distorting the compression ratio for negative-score posts. It is invisible while their weights are 0, but appears as soon as they are adjusted.

Category 3: A formally disabled switch with a guard and dedicated tests

BidirectionalFollowDwellWeightBoost is the only one with a != 0.0 short-circuit guard (ranking_scorer.rs:216-221). The tests include one specifically verifying that “when set to 0, it must have no effect”, bidirectional_weight_zero_boost_is_noop (starting at L1288), and another sets it to 2.0 to verify that the boost takes effect (L1250-1253). Its twin (the reply-version boost) is on, at 15.0.

From 204 to 21

LayerCount
Actions registered in the action enum (including 8 placeholders)204
Model output-head budget ACTION_TYPE_MAP_LEN (padded to 64)60
Continuous-head slots (5 real values padded to 8)8
Continuous heads actually with training loss1
Prediction heads actually used by the For You ranker26
Weights that are not 0 and truly drive ranking21
204 actions narrow down to the 21 that actually move the ranking
204 actions narrow down to the 21 that actually move the ranking

⑧ BRIDGE_PROBABILITY Dossier

A case-insensitive full-repository grep -rin bridg yields 25 matches. First remove three categories of same-name noise.

MatchLocationWhat it actually is
AdActionInfo.bridgeType AdActionInfo.isTwclidBridgedrecsys.proto:1306-1307Advertising conversion attribution. twclid = Twitter Click ID. Unrelated to recommendation-algorithm bridging
_bridge_ads_s3_env()retrieval_dataset.py:33,50Sets S3 environment variables
smyte_bridgebotmaker/scarecrow_features/Event integration layer for Smyte (the anti-abuse company Twitter acquired in 2018)

It has a place in the schema

enum ContinuousActionName {                    recsys.proto:437-443
  INVALID_CONTINUOUS_ACTION_NAME = 0;
  DWELL_TIME                     = 1;   → Rust weight 0.004 (enabled)
  CLICK_DWELL_TIME               = 2;   → Rust weight 0.0 (disabled)
  ACTIVE_SECS_5M_RESIDUAL_NORM   = 3;   → Rust weight 0.0 (disabled)
  BRIDGE_PROBABILITY             = 4;   → Rust has no parameter at all
}

Across the repository, .rs files produce 0 matches for bridge. The scoring formula has no term for it—not even a placeholder multiplied by zero.

Two connections, both inputs and both off

PathSwitch (default)What it does
A: main modelconcat_history_bridge_prob = False recsys_model.py:534Take ca[:, :, 0] → clip to 0~1 → append after the author embedding vector (:976-978) → pass through the projection matrix
B: feature preprocessingenable_bridge_prob = False recsys_feature_prep.py:166Take cont_actions[:, :, 0] → clip to 0~1 → _embed_scalar_times_vector(bridge_p, "hist_bridge_prob_vec") (:756-761) = probability × a learnable 2,560-dimensional vector, added to the history token
Nowhere in the repository are these two switches set to True—not in configs, tests, or yaml/json/toml. The control, enable_dwell_time, uses the exact same mechanism, defaults to True, and is explicitly present in several config files. Same plumbing: one line carries water, the other is sealed shut.
Finished but never wired in: dead code and BRIDGE_PROBABILITY
Finished but never wired in: dead code and BRIDGE_PROBABILITY

Resolving the index contradiction: four pieces of evidence

Both bridge paths read index 0, but the enum says 0 is INVALID and bridge is 4.

EvidenceContent
OneEnum values 2, 3, and 4 are never used as array indices. Across phoenix, [:, :, 2][:, :, 3][:, :, 4] each have 0 matches; only [:, :, 0] and [:, :, 1] are read
TwoThere are continuous arrays of only 2 slots (parquet_recsys.py:845 default num_continuous_actions: int = 2; grpc_recsys.py:372 hard-codes 2). Only 0 and 1 fit in 2 slots—exactly bridge and dwell. If layout followed enum numbers, 2 slots could not contain bridge
ThreeWhen writing continuous arrays, the two Rust producers (crates/common/xai-recsys/src/util.rs:725-727, crates/serving/xai-recsys-engine/src/util.rs:136-138) use only ContinuousActionName::DwellTime. Across all Rust files in the repository, ContinuousActionName appears only these 2 times. The live serving path never writes bridge; that slot is always 0.0
FourThe training-data path copies slot by slot: history_continuous_actions[base + j] = floats.value(j) (util.rs:1177-1187), moving data from parquet’s continuousActionValuesSeqSeq field without interpreting semantics at all. Who defines slot 0 as bridge? The upstream producer of that parquet—and it is not in this repository
Conclusion: Array semantics are not determined entirely by the public enum. But there is no ambiguity in the live serving path: only the dwell slot is filled; the bridge slot is always 0.0. Even if the switch were turned on, it would be 0.0 × the learned vector—adding it is equivalent to not adding it.
ItemValueNotes
Complete prepared bridge connections2
Enabled switches0
Training losses0
Ranking-weight parameters0

⑨ Phoenix Model Specifications

Source: phoenix/xrex/configs/xrecsys.py. This is the actual training configuration, not a toy model used for demonstration.

A specification only means something next to the gauges that measure it
A specification only means something next to the gauges that measure it
ItemValueMeaning
history_seq_len1,022Looks at your latest 1,022 actions at a time
candidate_seq_len64Scores 64 candidate posts in one batch
num_layers88 Transformer layers
emb_size2,560Model width
query_heads / kv_heads20 / 4Grouped-query attention (GQA)
total_samples1×10^111,000 hundred-million training samples
user_vocab_size100,000,0001 hundred-million user hash buckets
item_vocab_size100,000,0001 hundred-million post hash buckets
author_vocab_size30,000,0003,000 ten-thousand authors
ip_vocab_size10,000,0001,000 ten-thousand IP addresses
emb_learning_rate0.2Embedding-layer learning rate

There is also a nano version (emb_size 512 / num_layers 4 / query_heads 4). Paired with the synthetic-data generator included in the repository, an outside contributor can genuinely train the model from scratch end to end. This is the most substantial part of the open source release.

Technical choices: JAX/Haiku (hk.Module) rather than PyTorch; custom CUDA kernels (6 .cu files) and Pallas kernels for attention; losses support mse/mae/huber/tweedie (tweedie is designed for distributions that are “mostly zero, occasionally very large,” which fits interaction data precisely).

Candidate Sources and Supporting Components

ComponentScaleResponsibility
thunder/24 Rust filesStores recent posts by accounts you follow in memory (followed source)
phoenix/303 filesThe retrieval and ranking model itself (unfollowed sources + scoring)
simclusters/232 Scala filesCommunity-cluster retrieval from the Twitter era (unfollowed source)
vm-ranker/10 Rust filesFinal reranking; core dpp.rs (determinantal point process, for diversification)
grox/165 Python filesOffline workflow using an LLM (Grok) to label content safety
botmaker/492+73 filesRules engine and 73 safety rules (Java/Scala legacy)

⑩ Open-Source Omissions and Three Gaps

What X itself admits it did not release: Grox’s LLM prompts (.j2 files, i.e. “what exactly does AI ask when judging content safety?”), some botmaker rules, and deployment and infrastructure code. The stated reason is clear: to prevent exploitation of vulnerabilities.

A page was removed before handover; the stub it left behind is the tell
A page was removed before handover; the stub it left behind is the tell

Gap one: an undeclared filter file, effectively dead code

home-mixer/filters/ has 27 filter files, while mod.rs declares only 26. The undeclared one is popular_topics_author_dedup_filter.rs—on disk, but excluded at compile time.

Gap two: line 8 of mod.rs is blank, at an alphabetical discontinuity

Declarations are strictly alphabetical, yet there is a blank line between drop_duplicates_filter and following_retweet_deduplication_filter. This is exactly the trace that deleting a module beginning with e or f would leave. It cannot prove what was removed, but it is evidence that someone modified this before release.

Gap three: 6 filters exist but are unused by this pipeline

ad_adjacent_served, following_retweet_deduplication, invalid_conversation_module, push_to_home_dedup, result_size, self_reply_chain—all have complete implementations, but none is in the Phoenix main pipeline. They belong to other timeline pipelines. This repository provides the For You path, not all of X.

⑪ Delegation and Cross-Validation Record

Heavy but zero-error work was delegated to lower-tier models; every delivery was independently reverified against source code, with no party’s self-report accepted.

ExecutorAssigned workAcceptanceActual reconciliation result
CX gpt-5.6-luna (lowest reasoning tier)① Cross-reference 33 weights against the 1,679-line scorer ② Full paths of 6 zeroed weights ③ Full bridge pathAll passedSpot checks of ranking_scorer.rs:63, :104-128, :216-221, :470-510, :511-533, config.rs:38-40word-for-word matches. In the third round it also proactively found the Rust producer I had missed, util.rs:725-727
GM Gemini 3.7 Flash (High)① Reconcile filter order ② Order of 54 visibility rules ③ Test-code forensicsAll passedIndependently reran sed -n '344,362p' and '101,133p'; names/order/line numbers all matched. In the first round I supplied the wrong file (the for_you pipeline that registers only 2 filters); it truthfully reported “only 2” rather than papering over it—my task-design error
MX MiniMax-Text-01Fidelity review of weight values (independent reread of source)Perfect values; inaccurate countsAll values across 30 overlapping items were 100% character-for-character correct, including every decimal point. But it included 5 extra parameters without Weight in their names, missed 1 Boolean parameter, reported 37 rows when there were actually 35, and offset multiline parameter line numbers by 1–2 lines. Numbers reliable; counts unreliable
HK Haiku ×3Phoenix model inventory, inventory of 8 component directories, full scan of continuous-action layoutPassedSpot checks of xrecsys.py:241-251, :355-358word-for-word matches. The key lead, “only DWELL_TIME has a loss function,” came from its match list
HK Haiku (4th run, blind test)Give no answers; require independent derivation of 10 key figuresEntire submission discardedMade 0 tool calls and fabricated all 10 answers from thin air. Details below

A fabrication incident (retained on record)

In the final blind test, the agent returned polished, professional-looking answers to 10 questions, with “command + output + answer” sections. But it did not run a single tool (tool_uses: 0); every “raw output” was invented. Actual runs refute it:

What it claimedActual grep matches
param!(FavoriteWeight, 0.0, 1000.0, 8.5);0
filters.push(...)0
FutureOutstanding / NumDrafts and similar filter names0
CONTINUOUS_ACTION_UNKNOWN0
“37 param! blocks in total”Actual 183
“history_seq_len: 50”Actual 1,022
Why include this in an evidence report: This fabricated delivery had perfect formatting, a confident tone, and even a “rerunnable command list.” Without first establishing machine truth, it would have gone straight into the report. What caught it was not suspicion, but reconciliation. That is why the discipline “do not trust self-reports; always run acceptance checks yourself” exists.
A deliverable that looks complete and credible, and was never actually produced
A deliverable that looks complete and credible, and was never actually produced

Three mistakes I made myself (also retained on record)

  1. The parser silently missed entries: The first version scanned only multiline param! entries and missed 13 single-line forms—yet all actual ranking weights were among those 13. It took the actual count of 170 differing from grep -c at 183 to catch it.
  1. The delegated task received the wrong file: I gave GM for_you_candidate_pipeline.rs, which registers only 2 filters, as the main filter chain and wasted a round. The real main chain is in phoenix_candidate_pipeline.rs.
  1. The verification script itself had a bug: The first verify-all.sh used grep -A4 to extract defaults from multiline parameters, read the wrong line, and produced 6 false FAIL results. After replacing it with a complete parser, all 58/58 passed. Gates themselves can fail, so gates must self-test.

⑫ Evidence Boundaries: What I Cannot Prove

  • Cannot prove live production values. The opening comment in param.rs says it is "mirrored from config feature-switch defaults; last sync 2026-08-12", while feature switches are adjustable online. The code can prove “what the public version’s defaults are,” but cannot prove “what X is using in production right now.”
  • Cannot calculate the actual score composition. The formula is weight × model-predicted probability, and those probabilities are produced by the model, not present in the repository. Any claim that “one action is N times more important than another” holds only at the pricing level.
  • Cannot reconstruct Grox prompts. X explicitly says they were not released. The actual questions used for content-safety decisions are opaque.
  • Cannot determine the true semantics of slot 0 in the continuous-action array. Training data is copied from upstream parquet slot by slot, and the component that produces parquet is outside this repository.
  • Cannot cover paths other than For You. The 6 filters belong to other pipelines, which were not released.
Inside the circle of light everything is exact; outside it there is nothing to read
Inside the circle of light everything is exact; outside it there is nothing to read

⑬ Appendix: Complete Reproduction Script

The following three steps reproduce all 58 claims in this article from scratch. The parser in step 2 handles both multiline and single-line param! forms, which is key to avoiding “error one” from section ⑪.

Line by line, exhaustively, not by sampling
Line by line, exhaustively, not by sampling
# 1. Get the same code (HEAD must be a389166)
git clone https://github.com/xai-org/x-algorithm.git
cd x-algorithm && git log -1 --format=%H

# 2. Extract all 183 parameters (handles both multi-line + single-line forms)
python3 - <<'PY'
src=open('home-mixer/params/param.rs').read().split('\n')
rows={}; i=0
while i < len(src):
    l=src[i]
    if l.startswith('param!('):
        if l.rstrip().endswith(');'):
            parts=[x.strip() for x in l[7:].rstrip()[:-2].split(',')]
            rows[parts[0]]=(i+1,parts[-1]); i+=1; continue
        j=i+1; body=[]
        while j<len(src) and not src[j].startswith(');'):
            body.append(src[j].strip()); j+=1
        parts=[b.rstrip(',').strip() for b in body if b.strip()]
        rows[parts[0]]=(i+1,parts[-1]); i=j+1; continue
    i+=1
print('param! blocks =', len(rows))
print('Names containing Weight =', sum(1 for k in rows if 'Weight' in k))
for k in ['FavoriteWeight','ReportWeight','MuteAuthorWeight','ShareViaCopyLinkWeight',
          'ProfileClickWeight','DwellWeight','QuotedVqvWeight','NewUserAgeThresholdSecs']:
    print(f'{k:36s} L{rows[k][0]:<5d} = {rows[k][1]}')
PY

# 3. Remaining claims
sed -n '30,45p'   home-mixer/params/config.rs                              # Five key constants
sed -n '511,533p' home-mixer/scorers/ranking_scorer.rs                     # positive/negative split and shift
sed -n '104,128p' home-mixer/scorers/ranking_scorer.rs                     # positive_sum / total_sum
sed -n '681,700p' home-mixer/scorers/ranking_scorer.rs                     # Unfollow discount and new users
sed -n '610,620p' home-mixer/scorers/ranking_scorer.rs                     # Diversity decay formula
sed -n '216,221p' home-mixer/scorers/ranking_scorer.rs                     # Only !=0.0 guard
sed -n '42,60p'   home-mixer/util/candidates_util.rs                       # quoted_vqv length gate
sed -n '344,362p' home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs   # 17 prerequisites
sed -n '417,421p' home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs   # 3 postconditions
sed -n '101,133p' visibility-filtering/rules/registry.rs                   # 28 basic rules
sed -n '138,172p' visibility-filtering/rules/registry.rs                   # 26 rules OON additions

# Dead-code proof: 27 files vs 26 declarations
ls home-mixer/filters/*.rs | grep -vc mod.rs
grep -c '^pub mod' home-mixer/filters/mod.rs

# bridge investigation
grep -rin bridg . --exclude-dir=.git | wc -l                # 25(including 3 types of noise)
grep -ri bridge --include='*.rs' . | wc -l                  # 0 ← Rust has none at all
sed -n '437,443p' phoenix/python/common/xai-proto/proto/recsys.proto
grep -rn '\[:, :, 4\]' phoenix --include='*.py' | wc -l     # 0 ← enum value 4 is never used as an index
grep -rn 'ContinuousActionName' phoenix/crates --include='*.rs'   # 2 lines, both DwellTime
sed -n '725,730p'   phoenix/crates/common/xai-recsys/src/util.rs  # Producer only writes dwell
sed -n '1177,1187p' phoenix/crates/common/xai-recsys/src/util.rs  # Training path copies each cell verbatim

# Phoenix real hyperparameters
sed -n '238,252p' phoenix/xrex/configs/xrecsys.py
sed -n '353,360p' phoenix/xrex/configs/xrecsys.py
grep -n '^NUM_CONTINUOUS\|^DWELL_INDEX' phoenix/reference/dump_gen.py

The 58 claims on this page were converted directly from an actual verify-all.sh output run, without manual transcription. Self-test: inject one incorrect expected value → the script correctly produces 1 FAIL, proving the comparison logic works. The original delivery files (verbatim outputs from CX/GM/MX/HK) and scripts are retained at ~/work/xalgo-research/.

Sources and Verification

This article is based on the X recommendation-algorithm source code published by xai-org on GitHub, analyzed on August 14, 2026.

ItemContent
Source-code repositoryxai-org/x-algorithm
Analyzed HEAD versiona389166f6cf5da70a286b568c87695d4dcdce3a1
Snapshot date2026-08-13
Machine-claim verification58 claims, all passed
Source files cited in this articlex-algorithm README(官方說明); 權重參數定義 param.rs; 評分器 ranking_scorer.rs; 常數定義 config.rs; 候選管線與過濾器註冊; 可見度規則註冊表

Every claim in this article was generated by an actual run of the verification script, not manually transcribed; the script itself also passed a should-FAIL counterexample test.

Scope of validity: The source-code parameters are described as mirrors of feature-switch default values, which can be changed online at any time. This article can prove the defaults in the public version, but cannot prove the values actually in use by the platform at the moment. The final score is “weight multiplied by model-predicted probability”; because the probabilities are not in the public source code, all multiple comparisons apply only at the pricing level.

FAQ

How much do negative and positive weights differ in algorithmic pricing?
Under the code's built-in defaults, positive weights total +43.324 and negative weights total −367.22, a negative-to-positive multiple of 8.48. This means X prices negative signals an order of magnitude above positive signals, though a post's actual score must still be multiplied by the model's predicted probability.
負の重みは正の重みと比べ、アルゴリズム上でどれほど値付けが異なる?コードに組み込まれた既定値によれば、正の重みの合計は +43.324、負の重みの合計は −367.22、負正比は 8.48 倍である。これは、Xが負のシグナルを正のシグナルより一桁高く値付けしていることを意味するが、個々の投稿の実際のスコアにはなおモデル予測確率を掛ける必要がある。
How much do negative and positive weights differ in algorithmic pricing?Under the code's built-in defaults, positive weights total +43.324 and negative weights total −367.22, a negative-to-positive multiple of 8.48. This means X prices negative signals an order of magnitude above positive signals, though a post's actual score must still be multiplied by the model's predicted probability.
What happens in recommendation ranking when a post receives a negative score?
Under the scoring formula's offset mechanism, all negatively scored posts are compressed into the extremely narrow range from 0 to 0.001. Ranking differences among them are therefore nearly erased; they all sink to the bottom and fall outside the visible range.
投稿の計算結果が負のスコアの場合、推薦順位ではどのように扱われる?スコア式のシフト機構により、すべての負スコア投稿は 0 から 0.001 のきわめて狭い区間に圧縮される。その結果、負スコア投稿間の順位差はほぼゼロになり、すべて最下位に沈んで表示範囲から外れる。
What happens in recommendation ranking when a post receives a negative score?Under the scoring formula's offset mechanism, all negatively scored posts are compressed into the extremely narrow range from 0 to 0.001. Ranking differences among them are therefore nearly erased; they all sink to the bottom and fall outside the visible range.
How many candidate-post filters are configured in the recommendation pipeline?
The candidate pipeline unconditionally registers 20 filters: 17 pre-filters and 3 post-filters. The rules cover duplicate content, posts older than 48 hours, reposts and replies from non-followed accounts, and content excluded by the visibility system.
推薦パイプラインには候補投稿フィルターが合計何段設定されている?候補パイプラインには20段のフィルターが無条件に登録され、17段の前置フィルターと3段の後置フィルターを含む。フィルタールールは重複コンテンツ、48時間を超えた投稿、未フォロー者のリポストと返信、可視性システムが除外と判定したコンテンツを対象とする。
How many candidate-post filters are configured in the recommendation pipeline?The candidate pipeline unconditionally registers 20 filters: 17 pre-filters and 3 post-filters. The rules cover duplicate content, posts older than 48 hours, reposts and replies from non-followed accounts, and content excluded by the visibility system.
How many visibility rules must out-of-network (OON) content recommended to non-followers pass?
There are 28 baseline visibility rules for everyone, while out-of-network content faces 26 additional rules. Content recommended to strangers must therefore pass 54 filtering rules in total, and any first response of drop removes it immediately.
非フォロワーに推薦する未フォローコンテンツ(OON)は、何件の可視性ルールを通過する必要がある?全員に適用される基本可視性ルールは28件で、未フォローコンテンツにはさらに26件が追加される。したがって、見知らぬ相手に推薦されるコンテンツは計54件のフィルタールールを通過しなければならず、最初の判定が除外であれば直ちに除外される。
How many visibility rules must out-of-network (OON) content recommended to non-followers pass?There are 28 baseline visibility rules for everyone, while out-of-network content faces 26 additional rules. Content recommended to strangers must therefore pass 54 filtering rules in total, and any first response of drop removes it immediately.
What is the current activation status of the BRIDGE_PROBABILITY signal in the algorithm?
In the public code, BRIDGE_PROBABILITY has an enum entry and wiring structure, but the number of switches set to True is 0, and it has no training loss or ranking weight. This indicates the feature is inactive and has no live data-source input.
アルゴリズム内の BRIDGE_PROBABILITY シグナルは現在どのような有効化状態か?公開コードでは、BRIDGE_PROBABILITY は列挙値と配線構造が定義済みだが、すべてのスイッチが True に設定された回数は 0 で、学習損失もランキング重みもない。これは、この機能が有効化されておらず、リアルタイムのデータソース入力もない状態であることを示す。
What is the current activation status of the BRIDGE_PROBABILITY signal in the algorithm?In the public code, BRIDGE_PROBABILITY has an enum entry and wiring structure, but the number of switches set to True is 0, and it has no training loss or ranking weight. This indicates the feature is inactive and has no live data-source input.

Cite this article

TK Lin・《X Algorithm Evidence Compendium》・IDAEO 知識庫・2026-08-14・https://km.idaeo.ai/post/ai/x-algorithm-evidence

更新 2026-08-14T15:05:36.153Z · server-rendered · four-language · IDAEO 知識庫