0x00 한

Research / Daily reports

  • Sources read: 2
  • Canon files updated: 1
  • Radar entries: 0
In one lineX added a new reply spam verdict path to grox, its LLM annotation pipeline. It judges a chain in which one author keeps replying to their own replies as a whole thread, and the root follower threshold is 1,000.

The 9b0dc31 commit that introduced this path also made large changes to the grox PTOS (policy-based violation category verdict) judge stack and to the serving and training stack of Phoenix, the For You ranking model. Multi-step reply spam is today's headline, but the matching canon (the services/ notes holding the current understanding of each service) received five more updates, so each got its own section. Other changes reviewed today are as follows:

  • The first concrete case of per-category models in the PTOS judge stack was confirmed. Only the judge for the illegal and regulated behavior category moved to Grok 4.1-fast.
  • The SID (discrete code representing a post) lookup sidecar was removed entirely from the Phoenix serving engine. History SIDs now arrive in the request.
  • A new rematerialization policy for H100 appeared in the Phoenix training code. It is the first code signal that training also runs on H100.

1A fourth grox path for thread-chain reply spam verdicts is wired in — X

Source xai-org/x-algorithm@9b0dc31 — public source code, 2026-09-04 00:37 UTC, X.AI Corp. These notes number X commits after the Gen4 release in order, and this commit is Gen4+15.

Background grox is X's nearline LLM annotation pipeline. On the reply-quality axis, grox had three verdict flows so far verified — PLANS_TO_INJECT in generators.py. PlanReplyRanking scores a single reply, PlanSpamComment judges whether a single reply is spam, and PlanCoordinatedSpam catches repeated replies coordinated by multiple accounts. All three flows have a long unification history in Gen4.

  • In Gen4+3 (2026-08-18, 11a71f8), the separate spam, comment, and reply-ranking generators were merged into a single ReplyRankingTaskGenerator.
  • The follower gating threshold for the reply spam and reply ranking tasks rose six times from 2026-08-13 to 08-25: 15k → 30k → 40k → 60k → 80k → 100k → 120k verified — task_filter.py.
    • The commits that raised it were, in order, c65aa17, b089ce6, 11a71f8, 28e414f, d011592, and 0d3cdd8, and the value as of this commit is 120k.
    • The comparison is against the follower count of the root and reply-target accounts, not the reply author.
  • Reliability mechanisms such as Kafka-first storage order, Manhattan monotone-only write, and an option to put follower count and bio in the LM prompt were added in Gen4+9 (45b48ba) and +10 (24c6094).

This commit attached a fourth verdict path, PlanMultiStepReplySpam, to these flows. The "Multi-step" in the name means that the verdict unit is not an individual reply but several reply chains.

What we learnedA. Only chains where one author replies again to their own reply enter the verdict

  • The entry filter passes only replies that satisfy all 8 conditions below verified — task_multi_step_reply_spam.py. The filter class is TaskMultiStepReplySpamFilter.

    1. post.ancestors exists and none of them is deleted (None). That is, it is a reply and the thread is intact.
    2. The users of the target post, the direct parent, and the thread root are all alive.
    3. The reply author differs from the thread root author. Replies that the original poster adds to their own thread are skipped.
    4. GROK_USER_ID and GORK_USER_ID are skipped. Replies posted by Grok and Gork themselves are not targets.
    5. Thread depth is 2 or more. A one-step "root → first reply" does not enter.
    6. The direct parent's author is the same as the target post's author. Only chains where the same person replies again to their own reply enter.
    7. The root author has at least 1,000 followers ("blast radius"). Only threads with meaningful root impressions are admitted.
    8. If the reply author is a high page-rank v2 user or a grey badge user, the reply is skipped. Established accounts are not targets. Both checks run in parallel by calling UserStratoLoader.is_high_page_rank_v2_user and is_grey_badge_user through asyncio.gather.

    Of the 8 conditions, number 6 in particular defines the domain of this pipeline. That domain is reply spam chains that steal root impressions through self-conversation chains. For example, A replies to a famous person's tweet, A replies to that reply, and A replies again, extending the chain. The target is the pattern that pulls the root tweet's impressions toward the author's own ads or lures.

    The threshold of 1,000 in condition 7 is two orders of magnitude lower than the 120k of the single-reply verdict. This pipeline runs broadly over threads whose root impressions are substantial.

    class TaskMultiStepReplySpamFilter(TaskFilterWithPost):
        FOLLOWER_COUNT_THRESHOLD_FOR_SPAM_DETECTION = 1000
        ...
        if post.ancestors[-1].user.id != post.user.id:
            Metrics.counter("task.filter.skipped.count").add(
                1, attributes={"filter": cls.FILTER_NAME, "reason": "parent_not_same_author"},
            )
            return False
        root_user_follower_count = post.ancestors[0].user.follower_count or 0
        if root_user_follower_count < cls.FOLLOWER_COUNT_THRESHOLD_FOR_SPAM_DETECTION:
            Metrics.counter("task.filter.skipped.count").add(
                1, attributes={"filter": cls.FILTER_NAME, "reason": "low_blast_radius"},
            )
            return False
    

    — task_multi_step_reply_spam.py, 2026-09-04

What we learnedB. Gemma 2 reads the whole thread and picks the post numbers that are spam

  • The structure of the human message sent to the LM is visible directly in the code verified — classifier_multi_step_reply_spam.py.

    • The function that builds the message is MultiStepReplySpamScorer._to_convo.
    • The system prompt was left out of the export under the README policy (gaming prevention).
    • The "Reply Author Info" section renders the reply author's user profile (handle, follower count, name, bio).
    • The "Thread" section lists all posts in the thread with index numbers in the #### Post N format. The root gets the tag "(root post — never flag)" and the last post gets the tag "(the reply being evaluated)".
    • If the thread exceeds MAX_THREAD_SIZE = 10, only the first 5 and last 5 posts are kept and the middle is cut out.
    • include_follower_count=True and max_media=2 are applied to each post, and include_bio=True is applied only to the root.

    That is, this pipeline does not ask the LM whether each post is spam. It gives the LM a task of looking at the whole thread and extracting a set of spam indexes. The response is the JSON {"spam_post_indexes": [1, 2, 3], "reason": "..."}.

    content.append(
        "\n\n# Thread\n\nThe reply thread is listed below; each post is labeled with a `#### Post N` index. "
        "Post 0 is the original (root) post and the highest-numbered post is the reply being evaluated. "
        "Refer to posts only by their integer index.\n\n"
    )
    for i, p in enumerate(posts):
        if i == 0:
            tag = " (root post — never flag)"
        elif i == last:
            tag = " (the reply being evaluated)"
        else:
            tag = ""
        content.append(f"\n\n#### Post {i}{tag}\n\n")
        content.extend(
            PostRenderer.render(
                p,
                max_media=MAX_MEDIA_PER_POST,
                include_follower_count=True,
                include_bio=i == 0,
            )
        )
    

    — classifier_multi_step_reply_spam.py, 2026-09-04

  • The verdict LM is Gemma 2 verified.

    • The code has model_name = GEMMA_2 and calls it through OaiSampler.
    • Since Gen4+9 (45b48ba) added Spam to grox's USE_GEMMA_CATEGORIES, grox has been pushing Gemma, and this choice extends that trend.
    • That Multi-step reply spam is Gemma-only from the start fits the direction of saving grok API budget inferred.

What we learnedC. The LM's answer is filtered in three stages, then stored with a spam label and a score of 0

  • LM output is not trusted as is; it is filtered by a 3-stage safeguard verified.
    1. _drop_if_newest_reply_not_flagged discards the entire result if the index set the LM flagged does not include the verdict target (the last post of the thread). The task entry condition is that this reaction may be spam for the pipeline to run, so a result that treats the last reaction as not spam violates consistency.
    2. _drop_other_author_posts removes a post if the LM flags another author's post in the thread as spam. This pipeline targets chains where "the same author replies to their own reply", so posts by other authors that appear in the result are ignored.
    3. _drop_singleton discards the result if it contains exactly 1 post. Chain spam by definition targets "several follow-up replies", so a result with only one is not valid.
  • The storage task attaches a spam label to each post flagged in the filtered result and forces the reply ranking score to 0.0 when saving verified.
    • The task name is TaskWriteMultiStepReplySpamReplyRanking.
    • _apply_reply_spam_label(post_id, author_id) is applied to each flagged post. It is the same helper as in the single-reply verdict.
    • save_reply_ranking_score(..., ReplyRankingScore(score=0.0, reasoning=reasoning[-500:])) forces the score to 0.0, which means the lowest trust. Only the last 500 characters of reasoning are kept.
    • It emits to v2 Kafka with save_reply_ranking_kafka_v2(...).
    • DISABLE_RULES = [DisableTaskForNonProd] means no real label is attached in dev and staging.

What we learnedD. The new pipeline consists of 4 new files and 2 modified existing files, and runs alongside the existing verdicts from the same event source

  • 4 new files (474 lines) were created and 2 existing files were modified verified — grox/flows/reply_spam/.

    • The pipeline name is PlanMultiStepReplySpam.
    • task_multi_step_reply_spam.py (206 lines)
    • plan_multi_step_reply_spam.py (29 lines)
    • classifier_multi_step_reply_spam.py (226 lines)
    • state_multi_step_reply_spam.py (13 lines)
    • prompts.py +5 lines: added multi_step_reply_spam_system_prompt.
    • generators.py +7 lines: put this plan key into PLANS_TO_INJECT of the two stream generators.
  • The Plan DAG runs in the order filter, media hydration, verdict, storage verified — plan_multi_step_reply_spam.py.

    task_multi_step_reply_spam_filter →
      task_media_hydration →
        task_multi_step_reply_spam_detection →
          task_write_multi_step_reply_spam_reply_ranking
    

    It stands out that TaskMediaHydration runs only after the filter. Media hydration is costly, so it runs only after the filter has cut out a substantial share.

  • Two reply ranking stream generators add the new Plan to their injection list verified — generators.py.

    • The two generators are ReplyRankingTaskGenerator (kafka TOPIC_UNIFIED_POSTS_V3) and ReplyRankingRecoveryTaskGenerator, and the injection list is PLANS_TO_INJECT.
    • That is, the three existing verdicts (single-reply reply_spam, coordinated_spam, spam_comment) and this multi-step verdict run in parallel from the same v3 event source.

Canon update Created a new section "Resolved by the Gen4+15 release / new (2026-09-04)" in services/x.md. Under "Grox (LLM annotation)" it added a new subsection ### Gen4+15 (2026-09-04, 9b0dc31) — grox thread-level multi-step reply spam classifier introduced + PTOS .... It raised Last updated in the header from 2026-09-04 to 2026-09-05 and added links to the 6 new files in the sources section as well.

Why it matters A third axis was added to grox's reply spam verdicts: time-axis coordinated spam in which one author steals root impressions through their own reply chain.

  • The previous canon knew two flows. The reply spam verdict targets individual replies, and coordinated_spam targets "repeated replies coordinated by multiple accounts". If coordinated_spam is the account-collusion axis, multi-step is the time-axis chain collusion.
  • Because the gating threshold is set low at 1k root followers, this pipeline appears to run broadly over threads where the root author's impression assets are substantial. And leaving the verdict to Gemma fits the direction of saving grok API budget.
  • It does not conflict with the canon's existing understanding of grox reply_spam. Rather, it can be understood as widening into a two-layer structure: the 120k follower threshold of the single-reply verdict that the canon has covered is kept, and a chain verdict with a much lower threshold (≥ 1k) is added as a new layer.

↑ Contents

2The first concrete case of PTOS judge models diverging by category is confirmed — X

Source xai-org/x-algorithm@9b0dc31 — grox/flows/ptos/classifier.py and grox/flows/ptos/task_safety_ptos_policy.py in the same commit (Gen4+15).

Background In grox's PTOS (layer that judges violation categories based on policy) stack, a judge (cross-validator) layer was gradually added above the first-pass classifier during Gen4.

  • Gen4+2 (2026-08-17, b089ce6) introduced an adult content disagreement judge. Its name is SafetyPtosAdultContentCrossValidationJudge, and its model was EAPI_GROK_4_5_X_ALGO (classifier.py:557-561).
  • In Gen4+4–+6 (2026-08-19–21), the rate (dial) at which child safety cross-validation and deluxe reclassification call Grok 4.6 internal rose 10% → 30% → 100%. It replaced 4.5 internal within two days, and 4.5 internal was deleted from the code entirely (classifier.py:114, 281, 477).
  • Gen4+7 (2026-08-24, d011592) introduced SafetyPtosPolicyCrossValidator, which shares the EAPI_GROK_4_5_X_ALGO used by the adult content judge. This judge handled all 3 categories, ChildSafety, ViolentMedia, and IllegalAndRegulatedBehaviors, with a single model (4.5-X_ALGO).
  • The canon summarized this state as "a dual stack in which X keeps the primary (4.6-internal) and the judge (4.5-X_ALGO) on different model generations and tuning". It also noted that per-category model divergence in the judge stack would be the next checkpoint to watch.

This commit confirmed that divergence as the first concrete case.

What we learnedA. Only the IllegalAndRegulatedBehaviors judge moved from Grok 4.5 to 4.1-fast

  • Grok 4.1-fast-X_ALGO was newly registered as a model for the judge verified — grox/flows/ptos/classifier.py.

    • The model name is EAPI_GROK_4_1_FAST_X_ALGO.
    • SafetyPtosPolicyCrossValidator.__init__ initializes a 4.1-fast-X_ALGO sampler next to the 4.5-X_ALGO sampler.
    eapi_4_1 = grox_config.get_eapi_model(ModelName.EAPI_GROK_4_1_FAST_X_ALGO)
    self.eapi_4_1_x_algo = EapiSampler(EapiModelConfig(**eapi_4_1.model_dump()))
    

    — grox/flows/ptos/classifier.py, 2026-09-04

  • Only the judge for the IllegalAndRegulatedBehaviors category moved to 4.1-fast verified.

    • The changed function is _validate_illegal_and_regulated_behaviors.
    -        metric = "safety_ptos.illegal_and_regulated_behaviors_cross_model_validate_with_grok_4_5"
    +        metric = "safety_ptos.illegal_and_regulated_behaviors_cross_model_validate_with_grok_4_1"
             ...
    -            async with _eapi_4_5_x_algo_breaker.guard():
    -                raw = await self.eapi_4_5_x_algo.sample(
    -                    convo.interleaveToEapi(), conversation_id=convo.conversation_id
    -                )
    +            raw = await self.eapi_4_1_x_algo.sample(
    +                convo.interleaveToEapi(), conversation_id=convo.conversation_id
    +            )
    

    — grox/flows/ptos/classifier.py, 2026-09-04

    • The circuit-breaker guard is also gone. 4.5-X_ALGO was called wrapped in _eapi_4_5_x_algo_breaker, but 4.1-fast is called directly without a breaker.
    • ChildSafety and ViolentMedia still call 4.5-X_ALGO with the breaker. That is, judge models are now assigned differently per category.

What we learnedB. First-pass for the same category now goes to Gemma without a probability gate

  • IllegalAndRegulatedBehaviors was added to the list of categories whose first-pass is judged by Gemma verified.

    • The list is USE_GEMMA_CATEGORIES.
     USE_GEMMA_CATEGORIES = {
         SafetyPolicyCategory.Spam,
    +    SafetyPolicyCategory.IllegalAndRegulatedBehaviors,
     }
    
  • All gates that decided by probability whether first-pass routing sends a request to Gemma were removed verified.

    -        if (
    -            sample_for_gemma
    -            and not self.deluxe
    -            and self.use_gemma
    -            and random.random() < self.use_oai_gemma4_dial
    -        ):
    +        if sample_for_gemma:
                 try:
                     return await self.oai_gemma4.sample(
                         convo.to_openai_messages(), conversation_id=convo.conversation_id
                     )
    

    — grox/flows/ptos/classifier.py, 2026-09-04

    • That is, for the two categories Spam and IllegalAndRegulatedBehaviors, first-pass is routed 100% to Gemma, and grok handles only the fallback.
    • The constant initialization self.use_oai_gemma4_dial = 1.0 was also deleted.

What we learnedC. IllegalAndRegulatedBehaviors verdicts now pass through three layers: Gemma, reclassification, and the 4.1-fast judge

  • IllegalAndRegulatedBehaviors was added to the categories that get reclassification verified — task_safety_ptos_policy.py.

    • The changed place is the reclassification trigger in task_safety_ptos_policy.py.
    -            elif violation.category == SafetyPolicyCategory.ViolentMedia:
    +            elif violation.category in (
    +                SafetyPolicyCategory.ViolentMedia,
    +                SafetyPolicyCategory.IllegalAndRegulatedBehaviors,
    +            ):
                     policy = await active_classifier.classify_policy_for_violation(
                         post, violation
                     )
    
    • Previously only ViolentMedia took this reclassification path (re-checking the category verdict with deluxe 4.3).
    • That is, with this commit the IllegalAndRegulatedBehaviors flow became a three-layer stack: first-pass Gemma → deluxe cross-model reclassification (expensive) → Grok 4.1-fast judge.

Canon update Added these 3 items to the "Resolved by the Gen4+15 release / new" section of services/x.md. The fact that per-category judge model divergence was confirmed as a first concrete case updates the "next category for judge expansion" checkpoint to watch under the Gen4+7 canon cleanup.

Why it matters It is now confirmed that judge models are tuned separately per category inside the PTOS judge layer. The previous canon knew only that "X keeps the primary and the judge on different models".

  • The judge model choice appears to be set by three factors: the verdict difficulty of each category, the judge latency requirement, and the disagreement characteristics between primary and judge inferred.
  • Given that the fast suffix is on the 4.1 judge and that it is called without a breaker, the judgment is that IllegalAndRegulatedBehaviors verdicts are relatively clear (for example drugs, weapons, and child exposure, where the label itself carries a strong signal), so a fast, cheap judge suffices inferred.
  • In contrast, ChildSafety and ViolentMedia verdicts are subtle (for example, telling cartoons and games from real footage in violent media), so the policy is that a 4.5 judge is needed.
  • The promotion of first-pass Gemma to 100% for Spam and IllegalAndRegulatedBehaviors fits the direction of saving grok API budget. Violent and hateful speech appear to remain as the next promotion candidates.

↑ Contents

3The SID lookup sidecar was removed entirely from the Phoenix serving engine — X

Source xai-org/x-algorithm@9b0dc31 — the phoenix/crates/serving/xai-recsys-engine/ subtree and phoenix/xrex/inference/ in the same commit (Gen4+15).

Background Semantic ID (SID) is a technique that represents post embeddings as discrete codewords with a hierarchical codebook of the RQ-VAE family.

  • At X, Gen4+3 (2026-08-18, 11a71f8) wired 7 SID fields into VMRanker requests, which first confirmed that a 3-level hierarchical codebook exists.
    • VMRanker is the stage that reorders, by diversity criteria, the candidate bundle to put in the feed.
    • The fields are sid_known, sid_k_l1/l2/l3, and sid_gap_l1/l2/l3.
  • Gen4+13 (2026-09-01, 7ba7768) released code that adds dense reconstruction to the ranker post tower, only wired and switched off (wired-but-off).
    • This code loads an RQ-VAE-style stacked residual codebook from an offline .safetensors.
    • The file is phoenix/xrex/models/recsys_sid.py (72 lines, new), and the default is sid_embedding_mode="learned".
  • And the Phoenix serving engine had a sidecar gRPC client, sid_client.rs. It was an optional path that filled in codewords by querying the SID service separately for each post_id in the user history. In the Gen4+3 canon cleanup this sidecar was recorded only as "optional".

This commit removed this sidecar entirely.

What we learnedA. The SID client and its wiring code were deleted from Rust, Python, and the CLI

  • Both the Rust implementation and the Python binding of the SID lookup client were deleted verified — xai-org/x-algorithm@9b0dc31.

    • The deleted files are sid_client.rs (189 lines) and sid_retrieval_runner.py (17 lines).
    • What disappeared is the SemanticIdClient Rust client and the Python binding PySemanticIdClient.
    • The xai-recsys-sid-proto dependency was removed from Cargo.toml and pyproject.toml, and the pub mod sid_client; declaration in src/lib.rs was deleted too.
  • The SID client field was dropped from the Python structs and the server macro verified — phoenix/crates/serving/xai-recsys-engine/src/python.rs.

    • The field sid_client: Option<Arc<crate::sid_client::SemanticIdClient>> was removed from the two structs RecsysPredictorImpl and RecsysRetrievalPredictorImpl.
    • The sid_client: Option<&crate::sid_client::PySemanticIdClient> argument that the server_impl macro accepted and the add_class::<PySemanticIdClient>() registration were deleted as well.
  • The SID endpoint setting (--sid_endpoint) disappeared from the inference CLI and the model runner verified — phoenix/xrex/inference/launch_inference.py.

    • In the CLI, the --sid_endpoint argparse argument and the assignment runner.sid_endpoint = args.sid_endpoint were removed.
    • In phoenix/xrex/inference/model_runner.py, the field sid_endpoint: str | None = None was removed, and 12 lines of SID client assembly code in create_server disappeared.
    • The two deleted branches and the newly added sid_num_levels line reveal the new contract.
    -        if _use_post_sid and self.sid_endpoint and sid_num_levels > 0:
    -            sid_client = xai_recsys_engine.PySemanticIdClient(
    -                self.sid_endpoint,
    -                sid_num_levels,
    -            )
    -            logger.info(
    -                "SID client connected: endpoint=%s, sid_num_levels=%d",
    -                self.sid_endpoint,
    -                sid_num_levels,
    -            )
    -        elif _use_post_sid:
    -            logger.info(
    -                "Parsing history SIDs from the request (sid_num_levels=%d); no sid_endpoint",
    -                sid_num_levels,
    -            )
    +        user_tower = self.model_config.user_tower_config
    +        sid_num_levels = user_tower.sid_num_levels if user_tower.use_post_sid else 0
    

    — phoenix/xrex/inference/model_runner.py, 2026-09-04

    That is, of the two execution modes (SID client attached, or parsed from the request), the former is gone and only the latter remains. The new contract is that history SID arrives in the request.

What we learnedOther changes

  • A flag that uses CUDA pinned host memory for D2H transfer is now on by default verified.
    • The flag is use_pinned_d2h, and its default changed from False → True. It changed incidentally in the file covered by the last bullet of card A.
    • This option was already there at the Gen4 release (2026-08-13), off by default, and it became the default after 22 days verified — model_runner.py:492.
    • The help text says: "Use CUDA pinned host memory for D2H transfer (~50 GB/s vs JAX's ~3 GB/s). Saves ~29ms/inference. Default True; pass False to opt out."

Canon update Added this fact to "Resolved by the Gen4+15 release / new" in services/x.md. The Gen4+3 record "Phoenix serving SID sidecar is optional" is updated to "the option is removed entirely; SID is a request contract".

Why it matters The architecture of the Phoenix retrieval SID pipeline was tidied up considerably. The responsibility for SID codeword lookup moved out of online serving.

  • The previous canon knew three things:
    • phoenix-rankall manages the SID index window (video_14day and others).
    • Seven SID fields are wired into VMRanker.
    • The serving engine has an optional SID sidecar.
  • With this commit, it is confirmed that the SID sidecar is gone from the serving engine entirely and that SID is carried directly in the request.
  • The home-mixer (or candidate hydration layer) that handles For You feed requests extracts SID in advance at the candidate hydration stage and puts it in the request, and phoenix-rankall consumes it as is. One extra gRPC call per request is gone, and the fault domain of the SID infrastructure narrows.
  • However, this diff does not yet confirm the specification of the SID hydrator on the home-mixer side. The name and source of the hydrator that fills in SID and the storage backend (Manhattan? Strato? phoenix-rankall itself?) are follow-up checkpoints to watch.

↑ Contents

4First code signal that Phoenix training also runs on H100, besides GB300 — X

Source xai-org/x-algorithm@9b0dc31 — phoenix/xrex/models/remat.py (+17 lines) and phoenix/xrex/cutedsl/ranker_attention_fa4.py (+139/−75) in the same commit (Gen4+15).

Background The canon on Phoenix training hardware had already confirmed that "Phoenix production training runs on GB300". That confirmation came with Gen4+6 (2026-08-21, 28e414f), where _GB300_OVERRIDES appeared.

  • The rematerialization policy (a technique that saves memory by recomputing some activations during backpropagation instead of storing them) was only SAVE_GB300_RECSYS = 29.
  • The GB300 override assigned a preset tuned to GB300 hardware. The values are bs_per_device=512, attn_impl="cutedsl_ranker_varlen_attn", and remat_policy=RematType.SAVE_GB300_RECSYS.
  • The FA4 (flash attention v4 based on NVIDIA CUTLASS/CUTe DSL) kernel assumed that the history tile is always padded to a multiple of the tile size (128). Under this assumption, the layout was built in advance on the python host and stored in a cache (_DENSE_BS_LAYOUT_CACHE).

This commit registers a new remat policy for H100 and changes the FA4 kernel to handle variable-length history directly.

What we learnedA. An H100 remat policy now exists, but this commit has no H100-specific config bundle yet

  • A new remat policy for H100 was registered verified — phoenix/xrex/models/remat.py.

    • The new value is SAVE_H100_RECSYS = 32.
    class RematType(enum.IntEnum):
        WHOLE = 0
        SAVE_GB300_RECSYS = 29
    +    SAVE_H100_RECSYS = 32
    
    • A new branch was added to custom_remat_policy(policy). This branch saves the same name list as GB300 as rematerialization checkpoint targets.
      • Name list: attn_outputs, dense_outputs, dense_outputs_individual, attn, gate_up_proj, dense_up_proj, query_heads_rope, key_heads_rope, cutedsl_attn_outputs, query_heads, key_heads, value_heads, scalar_stats
    • The two policies share the name list but have separate enum values; this structure leaves room for different tuning (for example, a saved-tensor priority preset) to be attached to GB300 and H100 later.
    • A hardware-specific config block such as _H100_OVERRIDES is not in this commit yet.

What we learnedB. The FA4 attention kernel takes a different effective history length per item and masks accordingly

  • The FA4 attention kernel was changed to support variable-length history directly verified — phoenix/xrex/cutedsl/ranker_attention_fa4.py.

    • The build_dense_block_sparse_layout signature changed as follows.
    -def build_dense_block_sparse_layout(batch_size, seq_len, hist_len, num_q_heads):
    +def build_dense_block_sparse_layout(seq_len, hist_len, num_q_heads, hist_valid_len):
    
    • The new argument hist_valid_len receives the actual number of valid history tokens for each item in the batch.
    • The computation moved from host-side precomputation based on import numpy as np to JIT-friendly code based on jnp.where, jnp.arange, and jnp.broadcast_to. The _DENSE_BS_LAYOUT_CACHE cache was removed entirely.
    • The reason for removing the cache is clear. Because hist_valid_len differs per batch, the layout became a function of batch data rather than a deterministic function, and caching became meaningless.
    • The new fields valid_block_upper and valid_block_lower pass the upper and lower bounds of the attention mask per batch. With these values, the kernel masks partial-history blocks (where the actual valid tokens in a history tile number at most the tile size) exactly.
    • is_partial = is_hist & (t == n_full) & (has_partial == 1) marks the position of the partial block.
    • The ranker_attention_fa4 signature was widened to accept the optional arguments valid_block_upper=None and valid_block_lower=None.
    • custom_vjp packs and unpacks the new arguments through bs_args so that they are included in the gradient computation.
    • input_output_aliases shifted from {12: 0, 13: 1, 14: 2} to {14: 0, 15: 1, 16: 2}.

Canon update Added these two facts to "Resolved by the Gen4+15 release / new" in services/x.md. Below the canon entry that confirmed at Gen4+6 that "Phoenix production training runs on GB300", we append "first signal that it also runs on H100".

Why it matters The code now confirms that X's training hardware is not only GB300.

  • GB300 may be used for flagship training, and H100 for secondary, backfill, and research work inferred.
  • If _H100_OVERRIDES appears in a later commit, the differences in batch, attention, and remat tuning between H100 and GB300 will become visible.
  • FA4 attention's support for variable-length history can be understood as extending the cutedsl_ranker_varlen_attn varlen semantics of Gen4+6 to the history axis.
  • Training and inference accuracy improve for new users and short sessions. Previously, short history was padded up to the tile size, which may have increased the attend targets or polluted the attention semantics.
  • This observation attaches to the attn kernel axis of the sequence-transformer-ranking canon note.

↑ Contents

5visibility-filtering code shrank by more than 800 lines net (overview only) — X

Source xai-org/x-algorithm@9b0dc31 — several files in the visibility-filtering/ subtree of the same commit (Gen4+15).

Background visibility-filtering (VF) is X's fail-close filter stack. During Gen4, this subtree has changed in two main directions.

  • Rule refactoring moves policy into a data-oriented representation. The RuleContext split in Gen4+11 and the removal of Box<dyn Rule> in +14 belong here.
  • Safety-net removal strips out safety code from the early rewrite after it has been proven unnecessary. The deletion of 128 lines of VMRanker in Gen4+8 and 292 lines of the media fallback cache in +9 belong here.

This commit continues the safety-net removal direction at large scale.

What we learnedA. Hydration and safety-label code shrank substantially, while the regression safety net got thicker

Per-file changes in the VF subtree are as follows verified — file delta of xai-org/x-algorithm@9b0dc31.

File +/−
visibility-filtering/config.rs +7/−118
visibility-filtering/rules/registry.rs +8/−378
visibility-filtering/rules/tweet_rules.rs +50/−129
visibility-filtering/hydration/mod.rs +11/−90
visibility-filtering/hydration/fallback_cache.rs +25/−87
visibility-filtering/hydration/batch.rs 0/−19
visibility-filtering/hydration/gizmoduck_hydrator.rs +36/−42
visibility-filtering/hydration/tes_hydrator.rs +3/−33
visibility-filtering/get_safety_labels.rs 0/−40
visibility-filtering/safety_label_source/codec.rs +15/−206
visibility-filtering/safety_label_source/manhattan.rs 0/−10
visibility-filtering/models/safety_labels.rs +13/−19
visibility-filtering/filter_tweets.rs +17/−65
visibility-filtering/rules/author_rules.rs +3/−27

The regression safety net got thicker verified — same commit. Lines increased in visibility-filtering/rules/fixtures.rs (+81) and golden_corpus.rs (+102). The balance between simplification and regression defense was maintained.

Canon update Added only an overview to "Resolved by the Gen4+15 release / new" in services/x.md. The semantics of each deletion point remain as a deep-dive item: which wire format and label source were removed, and which hydrator entries were deleted.

Why it matters The pattern of stripping out safety nets once the rewrite has stabilized is now firmly established. Across five commits, Gen4+8, +9, +11, +14, and +15, visibility-filtering has been getting thinner through controlled net deletions.

  • The previous version of the canon held that "Gen4 VF has many safety nets because the Rust rewrite is early". This observation updates that judgment to "a stage where the rewrite has matured and safety nets are being removed".
  • The 206-line deletion in safety_label_source/codec.rs suggests unification on the wire format axis, so it is a target for a later deep dive.

↑ Contents

6The Brazil election filter got its regular update, and external PR #88 was merged — X

Source A xai-org/x-algorithm@9b0dc31 — home-mixer/filters/brazil_2026_election_filter.rs in the same commit (Gen4+15).

Source B xai-org/x-algorithm@e4dcedd — PR #88 merged, external contribution (joshs1017dev), 2026-09-03 20:28 UTC.

Background Brazil2026ElectionFilter is a political and regulatory-compliance filter that removes accounts reported to the Brazilian electoral court (TSE) from the home feed. This filter is the basis for the observation that X's regular maintenance continues every week. The canon records the history of this filter as follows.

  • Gen4+1 (2026-08-14): filter introduced (665 accounts)
  • Gen4+8 (2026-08-25): folded in the TSE dadosabertos 2026-08-25 edition (+3,379 lines)
  • Gen4+10 (2026-08-28): comments reorganized

This commit updated the account list of this filter again.

What we learnedA. The candidate account list was verified again, and the file's behavior is unchanged

  • The hardcoded account list grew from 2,388 to 2,554, a net increase of 166 verified — test assert in brazil_2026_election_filter.rs.
    • The patch added 167 // @handle entries and removed 1. The newly added accounts include @nanacachen, @AecioNeves, @lgmbrasilia, @crismonteirosp, @rodrigolimamdh, @samiabomfim, and @gabrielmagno_13.
    • Each account appears with an obfuscated user_id and the @username in a comment.
  • Two new "not the candidate's actual account" verdicts appeared: @BENICIODIAS and @CARLOSSAMPAIO verified — same patch.
    • This is the result of X's internal verification that the account owners were judged not to be candidates.
  • Two entries were removed from the "no live account found" list and three were added verified — same patch. A removed entry means the account came back to life, and an added entry means the account disappeared.
  • The file's behavior (the follow exception kept, obfuscated user_id exposed, @username comments published) is unchanged.

What we learnedB. External contributor PR #88 was actually merged

  • A PR (e4dcedd) from the external contributor joshs1017dev added two lines that dedup before the VF lookup.
    • The location is just before the in_network_ids VF lookup in home-mixer/candidate_hydrators/vf_candidate_hydrator.rs.
  • This is evidence that Gen4's open-source stance does accept external PRs.
  • It is a single performance optimization, so there is no change to canon semantics.

Canon update Added both items to "Resolved by the Gen4+15 release / new" in services/x.md.

Why it matters The practice of continuously verifying the mapping of politicians' X accounts every week continues. This regular-update practice itself confirms that the filter is an active production code path. Merging external PRs reconfirms that Gen4 is maintained open source that accepts real external contributions, not "throw the code over the wall and abandon it".

↑ Contents

Corrections

None. Today's commits extend the canon and do not overturn it. However, the canon entry from Gen4+3 saying "the Phoenix serving SID sidecar is optional" is updated to "the option was removed entirely; SID is part of the request contract". This is a change that came with time rather than an overturning.

↑ Contents

Radar

None. The 30 arXiv cs.IR items were parsed successfully, and industry blogs were skipped because it was Saturday. Meta and Netflix had no new sources. Triage of the 30 items passed 0.

  • Eight items were recommendation or CTR related: CORE, EPIC, LLM4AIGQ, HypRQ-VAE, UniCon CTR, Slow-Fast Thinkers Rec, SelfDR, and DoPR. However, all were mostly by academic institution authors and had no production deployment or A/B claims, so they were dropped.
  • The other 22 items were dropped in three groups.
    • Benchmark and evaluation methodology: SHELF, MultiGhostBench, Incremental Pooled Eval, Training seeds stability
    • Domain-restricted: POI, legal, medical, science, baseball
    • LLM usage methodology: RAG, private retrieval, essay grading

↑ Contents

What to check next

↑ Contents