- Sources read: 2
- Canon files updated: 2
- Radar entries: 1
Other changes reviewed today:
- In the weight mirror of X's home-mixer (the service that assembles the For You feed), the click weight went down and the post-click dwell weight was turned on.
- VF, X's impression verdict service, added Tweetypie, the tweet storage and lookup service, to the targets it compares its own verdicts against in staging.
- An anonymous e-commerce search team released PEAR, an automated research framework that splits verification into four layers, and it was added to the radar.
1The overturn-hold server was released as a read-only lookup service — X
Source xai-org/x-algorithm@a707cc2 — GitHub commit, 2026-09-29T03:06:30Z, 89 files, +9,552/−2,325. These notes number X commits in order after the Gen4 release, and this commit is Gen4+31.
Background In the 2026-09-26 commit (Gen4+30, 4c5cfe8, see the 2026-09-27 daily), a 5,270-line overturn_hold.rs was added to the abuse-enforcement service, which takes actions on accounts based on model scores. The file implemented in code the final pre-check that stops automated verdicts from re-applying a suspension or label that a human reviewer overturned in appeal handling, while the hold is valid. The facts known yesterday went only as far as its client calling POST /v1/holds/lookup on a remote ledger, and the server implementation was not public. This commit released that server (abuse-ledger-service) in full.
What we learnedA. The overturn-hold server is a read-only service that only looks up holds
The server is a read-only lookup service built with axum and PostgreSQL verified.
The service name is
abuse-ledger-service, and the Cargo name isxai-abuse-ledger-service.It consists of 11 files (
api.rs,boot.rs,codes.rs,config.rs,lib.rs,main.rs,metrics.rs,pg.rs,repo.rs,sql.rs,tls.rs) with 2,282 lines in total.The only lookup path in the server API is the
/v1/holds/lookupendpoint that the client in the 2026-09-26 commit called.The path, header names, and limits are fixed as constants.
"pub const LOOKUP_PATH: &str = "/v1/holds/lookup"; pub const DEADLINE_HEADER: &str = "x-request-deadline-ms"; pub const MAX_USER_IDS: usize = 100; pub const MAX_LABELS: usize = 32;" —
abuse-ledger-service/src/api.rs, 2026-09-29The request schema is
{user_ids: Vec<i64>, labels: Vec<String>}, and the response is{holds: Vec<Hold>, db_ms: u64}.One call can look up at most 100
user_idsand at most 32labels.The caller specifies the remaining budget with the
x-request-deadline-msheader.If this value is smaller than the statement timeout, the server uses it as the actual DB budget; if it is larger, the server uses the statement timeout plus
QUERY_DEADLINE_SLACK = 100ms(api::budget).
The server runs only three SQL statements verified —
abuse-ledger-service/src/sql.rs.The table schema being queried is
ledger.enforcement_holds.The lookup SQL is below.
"SELECT user_id, hold_id, head, expires_at, case_group_id, reason, action_kind, label FROM ledger.enforcement_holds WHERE user_id = ANY($1::bigint[]) AND cleared_at IS NULL AND expires_at > now() AND (action_kind = 'suspend' OR (action_kind = 'label' AND label = ANY($2::text[])))" —
abuse-ledger-service/src/sql.rs, 2026-09-29This SQL returns only rows that satisfy all three of the following conditions.
- The hold is still live (
cleared_at IS NULL). - It has not expired (
expires_at > now()). - Either
action_kind='suspend', oraction_kind='label' AND label ∈the requested label set.
- The hold is still live (
Only two values (
suspend,label) are defined foraction_kind.cleared_atis a tombstone filled in when a human reviewer withdraws a hold.At startup there is a
CONTRACT_SQLthat usesSELECT ... LIMIT 0to check that the column projection matches the schema.The health check is only
SELECT 1(PING_SQL).The tests in
sql.rsassert that none of the three SQL strings containsINSERT/UPDATE/DELETE/ALTER/DROP/CREATE. In principle, the server cannot write.
The PostgreSQL client uses deadpool-postgres and TLS uses rustls verified —
abuse-ledger-service/src/pg.rs.- The constants are
POOL_WAIT_TIMEOUT = 1s,POOL_CREATE_TIMEOUT = 1.5s,POOL_RECYCLE_TIMEOUT = 500ms, andQUERY_DEADLINE_SLACK = 100ms. PgErrorhas 7 variants:BadDsn / Connect{Refused|Auth|Tls|Timeout|Other} / Saturated / Transport / Query{sqlstate, message} / Decode / Timeout.- Only SQLSTATE
57014(query_canceled) is classified asErrorCode::StatementTimeout.28P01/28000(auth) is classified separately asErrorCode::AuthFailed. - At boot,
boot::contract_check_until_okstartsAtomicBool<ready>atfalseand keeps/readyzat a non-200 status until the schema check succeeds.
- The constants are
The actual shape of the hold record the server returns (
Hold) was released verified —abuse-ledger-service/src/repo.rs."pub struct Hold { pub user_id: i64, pub hold_id: i64, pub action_kind: String, pub label: Option<String>, pub head: Option<String>, pub expires_at: DateTime<Utc>, pub case_group_id: Option<i32>, pub reason: String, }" —
abuse-ledger-service/src/repo.rs, 2026-09-29headis a field that already appeared as the client-side diagnostic key (overturn_hold_head) in the 2026-09-26 commit, and it is now confirmed asOption<String>in the server schema.- Its content is still not public. It appears to be roughly the identifier of the review tool that issued the hold inferred.
case_group_id: Option<i32>is a group key that ties an appeal case to multiple holds.reason: Stringis a reason string left by the human reviewer. We confirmed that the field exists, but its content format is not public.
The TLS module is a separate 348-line file verified —
abuse-ledger-service/src/tls.rs.- This is the code that uses rustls for the PostgreSQL connection. It parses
sslmode=require|verify-ca|verify-fullfrom the DSN and reads the CA/CRT/KEY paths from separate environment variables. - That is, the ledger store is accessed over mTLS, and this service sits in the role of a read-only proxy for that store.
- This is the code that uses rustls for the PostgreSQL connection. It parses
What we learnedB. VF added Tweetypie to the targets it compares its own verdicts against in staging
visibility-filtering (VF) is the service that decides, for each post, "may this be shown to this viewer." VF has long had a "reference comparison" mechanism (Gen4+7, 2026-08-24), and this time a second comparison target was added on top of it. The only existing target was the Scala VF service. The newly added reference/tweetypie.rs (714 lines, introduced) calls Scala Tweetypie directly. Tweetypie is X's tweet body storage and lookup service, and its responses already contain a tweet-level visibility decision. This decision is diffed against the verdict of the Rust VF itself.
The value that selects the reference comparison target grew from the single Scala VF service to two, adding Tweetypie verified —
visibility-filtering/reference/mod.rs.The value expanded from
vf_serviceto the two valuesvf_service | tweetypie."pub enum Reference { None, VfService, Tweetypie, } ... (_, Some("prod")) => Err(format!( "{ENV_REFERENCE} selects {reference:?} but {ENV_APP_ENV}=prod; reference comparisons are staging-only" ))," —
visibility-filtering/reference/mod.rs, 2026-09-29The Rust VF ↔ Scala VF comparison, which had been switched on with the
dual_call_harnessenvironment variable since the 2026-08-24 commit (Gen4+7), was moved to theVF_REFERENCEenvironment variable in this overhaul.The new variable takes one of three values (
none / vf_service / tweetypie).If the old
ENV_DUAL_CALL_HARNESS_ENABLEDflag remains, startup fails with an error telling the operator to use the new name (migration).With any value, startup is refused under
APP_ENV=prod.
The Tweetypie path calls one column of Strato (the X-internal system that code goes through to read or store data) verified —
visibility-filtering/reference/tweetypie.rs.- The column is
tweetypie/getTweetFields.Tweet. - The call carries
GetTweetFieldsOptions { visibility_policy: VISIBILITY_POLICY_USER_VISIBLE, ... }to this column. Tweetypie then returns the response with the VF decision (Action::Allow | Drop(reason)) already attached. - The constants are as follows.
TWEETYPIE_MAX_BATCH_SIZE = 250: batches up to 250 tweets at a timeMAX_IN_FLIGHT = 32: fan-out cap enforced with a semaphoreTWEETYPIE_TIMEOUT = 1500msSAFETY_LEVEL = SafetyLevel::TimelineHomeHydration: the verdict level requested from TweetypieRECORD_CAP_PER_MINUTE = 20: cap on sample logs, 20 per minuteWEB_CLIENT_APPLICATION_ID = 3_033_300: the client app ID carried in the request
- There are 7 result classes:
Allow / BareDrop / Drop / Suppressed / NotFound / Failed / Missing. - There are 7 failure reasons:
NoValue / StratoError / RateLimited / DecodeError / RpcError / OverCapacity / TweetypieFailed. - The result class and the failure reason are each aggregated as
vf_tweetypie_reference_comparemetric labels.
- The column is
What we learnedC. VF defined its per-client rule switches as a data table
- Which rules to turn on for each client is now fixed in a data table verified —
visibility-filtering/params/client_switches.rs.The new file is VF
params/client_switches.rs(+225 lines, introduced), and it enumerates 6 switches."enum ClientSwitch { AgeVerification(VerifyBlurSupport), ModernBlur, StaleTweetLimits, GoreBlurIgnoresSettings, FosnrRules, FosnrFallbackDrops, }" —
visibility-filtering/params/client_switches.rs, 2026-09-29Each switch maps to a string key of
xai_feature_switches::FeatureSwitches.Each of the 4 states of
VerifyBlurSupport(Unsupported | IosNeedsUpdate | AndroidNeedsUpdate | Supported) uses a differentage_verification_*_tombstone_rule_enabledkey.Three switches are on by default via
default_on(AgeVerification(Unsupported),StaleTweetLimits,FosnrRules).When a request arrives, a
RecipientBuilderis constructed from theTwitterContextViewer(client_application_id, user_agent, country_code) and the viewer_id to do the matching.
What we learnedD. In the home-mixer parameter mirror, the click weight went down and the post-click dwell weight was turned on
Following the long-standing convention of the x-algorithm repository, the home-mixer parameter file is a cron mirror that periodically copies over the production feature-switch defaults. This time the header comment of params/param.rs, // mirrored from config feature-switch defaults; last sync 2026-09-28T16:00:35Z, was updated, and three weights moved together.
- The click weight went down, and the post-click dwell weight that had been off was turned on verified —
home-mixer/params/param.rs.In the production cron mirror,
ClickWeightchanged from 0.4 to 0.3 andContClickDwellTimeWeightfrom 0.0 to 0.4 at the same time.last syncin the header comment of the file was updated from 2026-09-25T16:24:28Z to 2026-09-28T16:00:35Z, and the three values moved together."-param!(ClickWeight, f64, "rust_home_mixer_click_weight", 0.4); +param!(ClickWeight, f64, "rust_home_mixer_click_weight", 0.3); ...
- "rust_home_mixer_cont_click_dwell_time_weight",
- 0.0
- "rust_home_mixer_cont_click_dwell_time_weight",
- 0.4
...
- "rust_home_mixer_not_interested_weight",
- -43.2
- "rust_home_mixer_not_interested_weight",
- -47.52"
—
home-mixer/params/param.rsproduction cron mirror, 2026-09-29The weight of the click itself dropped by 1/4 (0.4 → 0.3).
The continue-click-dwell weight (sessions where the user actually stayed long after the click), which had been off at 0, was turned on at 0.4.
The Not Interested weight was strengthened from −43.2 to −47.52 (exactly 1.1×).
After the
binarycontinuous loss type was applied to production as aclick-dwellBCE in Gen4+22 (2026-09-16), one trained head appears to have shown an effect offline, and that head appears to have been put into the actual serving weighted sum inferred.
What we learnedOther changes
- VF's retweet source merging logic was split into a separate module verified —
visibility-filtering/retweet.rs.- The new module
visibility-filtering/retweet.rsis 318 lines, andevaluate_tweets.rsshrank by +101/−102. - The merge of the two source verdicts, in-batch vs fetched, which was introduced inline in Gen4+28 (2026-09-24), was split out this time into the
evaluate_merging_sourcesfunction. - The structure is the same. If the original is already among the request candidates, it is judged as in-batch; if not, it is fetched through separate hydration, judged as fetched, and then merged.
- Instead, the
evaluate_tweets_retweet_sources { outcome=in_batch | fetched }metric moved toretweet.rs. This metric first appeared inevaluate_tweets.rsin the 2026-09-24 commit (Gen4+28). It shows how often a separate fetch is needed to judge the original of a retweet.
- The new module
- A 705-line age verification golden corpus and a 130-line legacy interstitial were added verified —
visibility-filtering/rules/golden_corpus/age_verification.rsandlegacy_interstitial.rs.- This follows the 15-file split of the golden corpus that began in Gen4+29 (2026-09-25).
age_verification.rsfixes, as a role×tweet matrix, the verdicts that theAgeVerification(*)switches of card C produce in each client state (Unsupported / iOS update needed / Android update needed / Supported).- The roles are
web_in_fr,web_in_us,sensitive_opt_in_web_in_fr, and so on, and the candidates arensfw_admin_flag_media,limited,blurred, and so on. local_regulations.rsandtakedown.rswere also split out as new files in the same commit.
- A new toggle lets retrieval in Phoenix (the ranking and retrieval model of X For You) drop already-seen posts from the candidates verified —
home-mixer/params/param.rs.- The new parameter is
PhoenixRetrievalExcludeSeenPosts, and its default isfalse. - Because the default is false, it is not yet an active policy.
- The new parameter is
- One parameter used for the impression boost was removed from the home-mixer parameter file verified —
home-mixer/params/param.rs.- The removed parameter is
UseEngagementCounterViewCountForImpressionBoost. - Either the view count source for the impression boost moved from the engagement counter to another source, or the boost itself was discarded.
- The removed parameter is
- The
author_policy_zeroedfield was removed entirely from the home-mixer signals verified —home-mixer/models/candidate.rs,scorers/value_model.rs,scorers/vm_ranker.rs,scorers/vm_ranker_request.rs.- This boolean disappeared both from the request shape of VMRanker (the separate ranking service attached after home-mixer that handles diversity reordering) and from the candidate dict.
- This release does not state directly what this signal suppressed, but from its name and where it was used (a boolean carried on the candidate at scoring time), it was a marker for candidates whose score had been pressed to 0 by author policy inferred.
- In Gen4, policy-driven candidate suppression is now decided directly in the VF verdict.
- The Phoenix serving response now also carries two kinds of view-through values verified —
phoenix/crates/serving/xai-recsys-proto/src/purchase_value.rs.- The
PUBLISHEDarray in Phoenixpurchase_value.rsgrew from 4 to 6 entries (=ALL). - In the previous commit (Gen4+30), it was fixed as
PUBLISHED = [PURCHASE_CT, PURCHASE_VT, CONTENT_VIEW_CT, ADD_TO_CART_CT]. This time it changed toPUBLISHED = ALL, soCONTENT_VIEW_VTandADD_TO_CART_VT(the two view-through kinds) are also carried in the serving response. - The assert on the encoded size cap of the proto
PurchaseValueBaselinewas also updated from≤64 bytesto= 73 bytes.
- The
Canon update Added a new "Gen4+31" section to services/x.md. It covers the overturn-hold server release, the VF Tweetypie reference comparison, the move of three home-mixer weights, the retweet.rs extraction, the client_switches 6-axis data lock, and the Phoenix PUBLISHED = ALL update. The related mechanisms described only in part across Gen4+7, +22, and +30 (the dual-call harness, click-dwell BCE training, the overturn-hold client) are each being completed today on the server, policy, and weight sides. This section therefore reads like a provisional end point of the Gen4 surface overhaul.
Why it matters Three changes overlap in this commit.
- The trust layers of the automated suspension policy are now physically separated into two services.
- The abuse-enforcement service decides whether to actually dispatch an action. abuse-ledger-service only looks up the decisions of human reviewers (overturns); the write path is undisclosed.
- The two services use separate DBs. The former uses its own store, and the latter uses
ledger.enforcement_holds. - The latter communicates only through three SQL statements (
PROBE_SQL,CONTRACT_SQL,PING_SQL), and an assert fixes that none of the three contains a write verb. - In other words, it is now possible to audit from outside the code that the dispatch path did not bypass the human verdict. The dispatch system does not hold write permission on the ledger DB.
- The reference comparison target of VF has moved beyond the self-reference, Scala VF, to Tweetypie, the original store of tweets.
- Rust VF and Scala VF are reimplementations of the same policy set in two languages, so their diff leans toward implementation drift rather than policy differences.
- Tweetypie holds the tweet's own visibility decision, which it already attaches at the storage layer, so diffing against it catches drift between the "storage-axis decision" and the "serving-axis decision."
- Pinning to a specific Safety Level with
SafetyLevel::TimelineHomeHydrationand refusing to start in prod show that this harness is a correctness verification tool, not a safety net.
- Ranking has started to trust "clicks" less.
- click_weight 0.4 → 0.3 and cont_click_dwell_time_weight 0.0 → 0.4 changed together in the same cron sync.
- The order appears to be that the click-dwell head, which entered the training loss in Gen4+22 (2026-09-16), passed the training curve and the holdout and was then promoted to a coefficient of the serving weighted sum.
- The two heads have different probability scales, so their exact totals cannot be compared. Even so, the direction in which "stayed after clicking" fits the production objective better than "clicked" is now explicitly fixed.
- Not Interested going from −43.2 to −47.52 (exactly ×1.1) also strengthens the user's explicit negative signal.
2PEAR, which automates industrial search experiments with four-layer verification, was released — arXiv
Source Progressive Evidence-Based AutoResearch for Industrial Search Systems — arXiv 2609.35031, v1 of 2026-09-28. The affiliation of the nine authors is given only as "Global E-Commerce Agentic Search Team" (no company name).
Background PEAR is the second source, after the presumably Kuaishou AgentX-Model added to the 2026-09-27 daily, in the family of autoresearch frameworks that automate recommendation and search experiments. AgentX-Model split the research loop into dual agents (Research and Model). This paper takes a different angle: it keeps a persistent hypothesis state for each task and stacks the verifier ladder in four layers.
What we learnedA. Verification passes through four layers in order, starting from the lowest-cost stage
- The verifier ladder has four layers, and a candidate must pass from low-cost metrics up to high-confidence metrics in order before moving to the next stage verified.
- The order is Offline Replay → Shadow-Traffic Evaluation → Rapid Online Evaluation → Decision-Grade Online Evaluation.
- Whether a candidate passes is decided in a confidence-gated way.
What we learnedB. Main Order/DAU rose in two online A/B tests
In two online A/B experiments, Main Order/DAU rose by +2.7336% and +3.2957%, respectively verified.
"In a real-world industrial search system, strategies optimized with PEAR significantly increased Main Order/DAU by 2.7336% and 3.2957% relative to their respective baselines in two A/B experiments." — PEAR abstract, 2026-09-28
- Giving four decimal places and the metric "Main Order/DAU" (orders / DAU) are the standard KPI notation for e-commerce surfaces.
- The company name is not given. The affiliation matches the "Global E-Commerce" team branding of the Alibaba International Digital Commerce (AliExpress/Lazada) line, so it is likely Alibaba inferred. However, the domain is not rendered in the v1 HTML, so this cannot be confirmed.
Canon update Added a two-line pointer to "new this month" in radar.md. It is not folded into a service canon (no tracked service is directly linked).
Why it matters Claims of deploying an automated research loop to production appeared twice within three days, which is a signal that this approach is settling in as the next axis of experiment infrastructure. AgentX-Model decomposed research orchestration into dual agents, and PEAR decomposed verifier confidence into layers; the two are not mutually exclusive. We do not open a watching thread (it is not directly linked to a tracked service, and two sources are too few to state a direction). If a third source appears from a different company, we will open a thread.
Radar
- PEAR (arXiv 2609.35031) is a progressive autoresearch framework for industrial search systems from an anonymous e-commerce team. The affiliation is likely the Alibaba line inferred. It uses a four-layer verifier ladder and hypothesis-guided per-task state, and reported Main Order/DAU +2.7336% and +3.2957% in two A/B tests. The metrics are verified, and the affiliation is inferred. Added to new this month in radar.md.
What to check next
- Look for the side that puts holds into abuse-ledger-service (the write path) — only the read-only lookup service was released today. The path by which a human reviewer's appeal approval inserts a hold into
ledger.enforcement_holdsis still not public. If that service is opened in a later commit, fold it into the canon. - Check whether home-mixer click_weight 0.3 holds up in the field — the cron mirror is a snapshot of that point in time, so if the next sync reverts it to 0.4, the experiment failed. We will keep tracking the value from the next crontab tick in the daily.
- Wait for a metric report on the VF Tweetypie reference comparison — once we observe how large the diffs are in staging, we will fold it into the canon.
- In the arXiv cs.IR triage, 30 papers were parsed and 1 (PEAR) passed. The other 29 are academic work on RAG, multimodal retrieval, session-based recommendation, and similar topics. EvoSkillRec (2609.34552, mixed Kuaishou and Huawei) did not pass because it has no online A/B.