How to actually get more reach on X — the playbook from 24,914 lines of leaked source code

We just spent 22 articles walking through every line of X's leaked For You recommendation system. This is the practical playbook that fell out of it: which signals the banger classifier scores you on, which booleans kill your reach, why replies to big accounts get a different scoring path than replies to small ones, and which features the reply ranker logs about every single one of your replies.

May 15, 2026·13 min read

The companion to the 22-part line-by-line analysis of xai-org/x-algorithm. There I explained how every component works. Here I distill what those components do to your posts — so you can write posts they reward instead of posts they suppress.

Every claim below cites the specific code path. No vibes, no folklore.


The journey of one post

Before tactics, the path. You post a tweet. Then:

  1. X core writes it to the unified-posts Kafka topic.
  2. Thunder consumes the event, stores the post in-memory (Sessions 02–03).
  3. Grox consumes the event from multiple Kafka topics in parallel (Session 18). Different topics route to different classifiers:
    • The unified posts topic → spam-comment detection + reply ranking.
    • The min-traction-for-grox topic → banger initial screen classifier (only posts that already have some engagement reach here — that's the first filter).
    • The min-traction-for-grox-ptos topic → safety PTOS classifier.
    • The post-safety-popular topic → post safety screen (extra scrutiny for popular posts).
    • The multimodal-embedding-requests topic → multimodal embedding (V3 or V5).
  4. Grox runs the appropriate plan (Session 19) for each topic. The plan is a DAG of tasks: filter → rate-limit → media hydration → classifier → publish.
  5. Classifier outputs get written to Manhattan (X's KV store) as UnifiedPostAnnotations, SafetyPostAnnotationsResult, and multimodal-embedding rows (Session 22).
  6. When a user opens For You, home-mixer (Sessions 04–14) builds their slate by running pipelines that read these annotations from Manhattan, score candidates with Phoenix ML models (Session 15), filter on safety + language + blocked-by, and rank.

Everything downstream of step 5 reads what Grox wrote in steps 3-5. So your reach is largely determined by what Grox's LLM classifiers labeled your post. Engineer your posts for those classifiers and the rest of the system follows.


1. Survive the banger classifier — quality_score ≥ 0.4

This is the gatekeeper for the initial-screen surface. From grox/classifiers/content/banger_initial_screen.py:

score = result.quality_score
banger_initial_positive = score >= 0.4

A BangerInitialScreenClassifier call returns:

class BangerInitialScreenResult(BaseModel):
    quality_score: float       # 0.0 to 1.0 — must be ≥ 0.4
    description: str           # LLM's one-line summary of your post
    tags: list[str]            # topic tags
    taxonomy_categories: list[dict] | None
    tweet_bool_metadata: TweetBoolMetadata | None
    is_image_editable_by_grok: bool | None
    slop_score: int | None     # 1-3
    has_minor_score: float | None

Practical implications:

  • If the LLM can't summarize your post in one sentence, you fail. The classifier emits a description field. Cryptic memes, pure shitposts, ambiguous one-word replies, ironic posts where the meaning is in the negative space — these struggle here. Posts with a clear, summarizable thesis pass.

  • slop_score == 1 is the goal. This is X's AI-slop detector. Recognizable AI-output patterns (ChatGPT cadence, generic ledes like "In today's fast-paced world", uniform paragraph length, summary openings, em-dashes everywhere) push you toward slop_score = 3. Even good takes written in slop style get penalized.

  • is_image_editable_by_grok rewards posts where the image is "Grok-editable" — a coherent subject in a clean composition, not a screenshot, not a meme template. Native photography and AI-generated images with clear subjects win here.

  • 0.4 is liberal but you have to clear it. This is just the banger surface gate. The actual ranking signal is the continuous quality_score itself — push for 0.6+ and you become preferentially distributed.


2. Avoid the seven kill-booleans

The classifier also emits TweetBoolMetadata. From task_pub.py the production counters tell us exactly which fields exist:

if grok_response.tweet_bool_metadata.isHighQuality: ...
if grok_response.tweet_bool_metadata.isNsfw:     ...
if grok_response.tweet_bool_metadata.isGore:     ...
if grok_response.tweet_bool_metadata.isViolent:  ...
if grok_response.tweet_bool_metadata.isSpam:     ...
if grok_response.tweet_bool_metadata.isSoftNsfw: ...
if grok_response.tweet_bool_metadata.isAdult:    ...

Of these, six are negative-signaling: isNsfw, isGore, isViolent, isSpam, isSoftNsfw, isAdult. One is positive: isHighQuality.

Why "isSpam" hits even non-promotional posts: the classifier flags as spam things that read like engagement bait, AI-templated reply chains, generic "agree, well said" patterns, repetitive posting at high frequency. If your last 20 posts have similar structure, the classifier picks it up. Variety helps.

Why "isSoftNsfw" catches more than you think: suggestive language, thirst traps, certain meme formats. Soft NSFW posts still get downranked but not blocked. Borderline content lives in this bucket.

isHighQuality is the binary you want true. From the prompt structure (we can infer from outputs), this rewards: substantive arguments, clear thesis statements, novel observations, well-sourced claims.


3. Don't trigger the safety pipeline — it's a separate, more severe path

Safety PTOS is the second classifier system (Session 20). It's two-stage:

  1. Category classifier detects which of seven categories your post might violate: ViolentMedia, AdultContent, Spam, IllegalAndRegulatedBehaviors, HateOrAbuse, ViolentSpeech, SuicideOrSelfHarm.
  2. Per-category policy classifier then checks the specific policy for that category.

For posts that go popular, a deluxe version of both runs (task_safety_ptos_category.py) using Grok 4.2 reasoning for adult-content and violent-media categories. So:

  • A post that cleared the standard classifier can still be flagged by the deluxe one once it goes viral. This is why some posts get throttled mid-flight after initial reach.
  • Adult content has a third check: the safemodel sex-and-nudity CV classifier runs alongside. If safemodel says yes but PTOS said no, the result sink (Session 22) injects a violation anyway and marks isNsfw=True.

The safety result feeds back to home-mixer via the ads_brand_safety filter (Session 05) and the safety-labels filter. A post tagged via PTOS gets suppressed across multiple surfaces, not just one.

Implication: edginess that survived in 2023 doesn't necessarily survive in 2026. The deluxe pipeline runs the same content through a more capable model with reasoning enabled. If your post is borderline, popularity is the trigger that gets it re-examined.


4. Use video with audio — it gets the richest embedding

From the multimodal embedding pipeline (Session 22, TaskMultimodalPostEmbeddingV5):

transcripts = []
if post.media:
    for m in post.media:
        if (
            isinstance(m, Video)
            and m.convo_video
            and m.convo_video.asr_transcript
        ):
            transcripts.append(m.convo_video.asr_transcript)
transcript = "\n".join(transcripts) if transcripts else None
_, embedding = await cls.embedder.embed(post, transcript=transcript)

The V5 embedder uses:

  1. Your text + images via a chat-template LLM call.
  2. Plus the ASR transcript of any videos with audio.

This produces a 1024-d normalized vector that drives retrieval. The richer the embedding, the more contexts you can be retrieved into.

From task_asr.py:

if is_animated_gif:
    Metrics.counter("task.asr_transcription.skipped.count").add(
        1, attributes={"reason": "animated_gif"}
    )
    continue

Animated GIFs are explicitly skipped for ASR. So:

  • Video with spoken content → ASR transcript → richer embedding → retrieved into more contexts. Direct path to broader reach.
  • GIFs, silent videos → no ASR. Same effective signal as a still image.
  • Image-only posts go through V3 with an LLM-generated summary; less rich than V5's direct multimodal attention.

This is why creators who switched from photo-with-caption to short-form video with voiceover saw step-function reach increases. The architectural change in 2025 was V5's debut as the production embedder.


5. Reply strategy is bifurcated by target follower count

This is one of the most important takeaways and almost no one knows it.

From task_filters.py:

class TaskSpamFilter(TaskFilterWithPost):
    FOLLOWER_COUNT_THRESHOLD_FOR_SPAM_DETECTION = ""  # redacted, ~1000
    # ... runs ONLY when reply target is LOW follower

class TaskReplyRankingFilter(TaskFilterWithPost):
    FOLLOWER_COUNT_THRESHOLD_FOR_REPLY_RANKING = ""  # same threshold
    # ... runs ONLY when reply target is HIGH follower

The filter logic is mutually exclusive. Your reply gets one of two completely different scoring paths based on who you're replying to:

Replying to high-follower accounts → reply ranker

The LLM scores your reply 0-3 (Session 20, ReplyScorer). The scoring prompt considers content quality and conversational fit. From task_pub.py:

if score == 0.0:
    action_result = (
        await cls._strato_grok_reply_spam_action_with_labels.execute(
            int(post.id)
        )
    )
    # ... apply spam labels

Score of 0 → automatic spam labels. Yes, the LLM giving you a single bad reply score can apply labels that throttle your visibility.

Score of 3 (max) → permissive default. This is also the fallback when the classifier failed entirely. Aim for 2-3.

What the LLM grades you on (from the actual log line in task_rank_replies.py):

logger.info(
    f"[task_rank_replies] {post.id=} "
    f"is_pasted={post.is_pasted} "
    f"user_agent={post.user_agent!r} "
    f"composition_source={post.composition_source!r} "
    f"app_attestation_status={post.app_attestation_status!r} "
    f"has_risky_user_safety_label={user.has_risky_user_safety_label if user else None} "
    f"num_legit_blocks_received_last_24hrs={user.num_legit_blocks_received_last_24hrs if user else None}"
)

Every reply you make gets logged with these six features. The LLM and downstream scoring models see them:

  • is_pasted: Did you paste this from clipboard? Pasting reduces your score.
  • user_agent: Which client did you use? Official app > third-party. Bot-detected user agents tank you.
  • composition_source: First-party composition vs scheduled vs third-party API.
  • app_attestation_status: Did your client app pass attestation? Modified clients fail this.
  • has_risky_user_safety_label: Are you already on a risky-user list? Once flagged, every reply suffers.
  • num_legit_blocks_received_last_24hrs: How many real users blocked you in the last 24h? A burst here destroys reach.

Replying to low-follower accounts → spam classifier

A simpler binary classifier (Session 20, SpamEapiLowFollowerClassifier). Outputs a spam / not spam decision. Positive triggers grokReplySpamActionWithLabels — the same action endpoint that the reply ranker uses for score-0 cases.

Why this matters: the same reply ("nice 🔥") sent to a 10-follower account vs a 10-million-follower account goes through completely different scoring. Generic engagement bait gets you spam-flagged in both paths, but the low-follower path is faster to trigger.

The reply playbook

  1. Reply via the official app. user_agent matters.
  2. Don't paste. Type your reply.
  3. Don't reply en-masse with templates. Each reply is scored. Bad replies stack.
  4. Add substance. The LLM is looking for replies that contribute to the conversation. "Great point" gets you 0-1. A counter-argument or extension gets you 2-3.
  5. Replying to big accounts is the high-leverage play because the reply ranker is more discriminating — a good reply scores high and gets distributed. Replying to small accounts puts you in the spam-classifier path where the ceiling is lower.

6. The reputation features that follow you across every post

The reply ranker isn't the only place these features are read. They're attached to your User object and propagate to multiple ranking stages.

From the same log line above and from the home-mixer scorers (Session 10):

  • has_risky_user_safety_label — set by the moderation pipeline once you trip enough flags. Hard to unset. Affects ranking on every future post.
  • num_legit_blocks_received_last_24hrs — direct ranking feature. The "legit" qualifier means blocks from real, non-spam users count more than blocks from suspected spam accounts. A viral post that triggers a backlash with mass-blocks can crater your reach for 24 hours.

The implication: avoid posting controversial content during the same window when you're trying to push something else. A flop drags your other posts down for a day.


7. Hard exclusions

Some configurations get you skipped entirely. From task_filters.py:

if post.user.is_protected:
    Metrics.counter("task.filter.skipped.count").add(
        1,
        attributes={
            "filter": "post_embedding_with_summary",
            "reason": "private_account",
        },
    )
    return False
  • Protected (private) accounts: completely excluded from the rec system. Your posts don't get embedded, classified for banger, or distributed beyond your followers.
  • Posts with no user: edge case but worth noting.
  • System accounts (user.id == 0): skipped.
  • Self-replies: spam classifier skips when replier == parent or root author. Self-replies aren't penalized but also don't get the same scoring boost.

If you have a private account and want reach, this is the single largest lever you can pull.


8. The first-traction problem

A subtle but critical detail from Session 18. The Kafka topics that feed the banger classifier:

KafkaTopicName.CONTENT_UNDERSTANDING_REALTIME_UNIFIED_POSTS_MIN_TRACTION_FOR_GROX
KafkaTopicName.CONTENT_UNDERSTANDING_REALTIME_UNIFIED_POSTS_MIN_TRACTION_FOR_GROX_PTOS
KafkaTopicName.CONTENT_UNDERSTANDING_REALTIME_UNIFIED_POSTS_MIN_TRACTION_FOR_GROX_MULTI_MODAL

The "MIN_TRACTION" prefix means only posts that already have some engagement reach Grox's classifiers. The threshold is upstream and not in the source we analyzed, but the pipeline is clear: a post must clear an initial traction bar from its existing network before Grox even gets a chance to evaluate it for wider distribution.

Implication: Your in-network (followers + mutuals) provides the launchpad. Without initial engagement from people who follow you, your post never gets to the point of being evaluated for breakout reach.

The home-mixer in-network pipeline (Session 07, in_network candidate hydrator) is what surfaces your post to your followers in the first hour. Engagement here generates the traction signal that puts you in the MIN_TRACTION topics.

So: build your in-network audience first. A 500-follower account with 50 engaged followers outperforms a 50,000-follower account with 50 engaged followers, because the engaged followers create the traction that triggers wider analysis.


9. Served history works against your re-distribution

From the side-effects layer (Session 12), every post you serve to a user gets recorded in served_history. The retrieval pipeline reads this and suppresses posts the user has already seen.

This means a single banger post doesn't get repeatedly served to the same user. Once it's shown, it's down-weighted for that user for some window.

Implication: the reach of a single post saturates. If you have an evergreen take, posting it once and hoping it spreads forever doesn't work — the system actively dedupes for any given user. To stay in front of your audience, you need a fresh post.


10. The Phoenix retrieval model rewards niche match

Phoenix's two-tower retrieval (Session 15) embeds you as a user from your recent history and finds candidate posts whose embeddings are close. The user embedding uses right-anchored RoPE positions — recent interactions dominate.

Implication for posting:

  • Post in a coherent niche. Random topic-hopping makes your post embedding far from any single user-history embedding, reducing your retrieval candidate frequency.
  • Post when your audience is active. Right-anchored history means the last hour of someone's activity dominates their user embedding. Posting when your audience is scrolling = matching their recent embedding.

11. Topic taxonomy match

Session 21 TaskBangerScreen caches the live topic taxonomy hourly from StratoGrokTopics. The classifier prompt includes this list. The classifier output's taxonomy_categories get validated against this list (Session 22):

if topic_id in id_to_name:
    topic_name = id_to_name[topic_id]
    category_id = name_to_category_id[topic_name]
    # ... use this topic
else:
    Metrics.counter(
        "task.publish_unified_post_annotations.invalid_grok_topic.count"
    ).add(1)
    continue

Topics the model invents that aren't in the cached taxonomy get dropped. Your post needs to land in a topic that already exists in the system's taxonomy.

The implication is non-obvious: chasing a brand-new trending topic that hasn't been added to the taxonomy yet gets you no topic-entity-tags written to UPA, which means downstream surfaces that filter by topic don't surface you. Topics that have been in the taxonomy for a while have more code paths consuming them.


The playbook

Pulling it together. In priority order:

High-impact moves

  1. Be public. Private accounts are excluded.
  2. Build in-network traction first. Engaged followers create the initial signal that puts you in MIN_TRACTION topics.
  3. Use video with spoken audio. Richest embedding via V5 + ASR.
  4. Reply to high-follower accounts with substance. Reply ranker has higher ceiling than spam classifier.
  5. Use the official app, don't paste, don't use modified clients. user_agent, is_pasted, app_attestation_status are direct features.

Medium-impact moves

  1. Write posts that pass quality_score ≥ 0.4: clear thesis, substantive, summarizable in one sentence.
  2. Match the topic taxonomy. Generic hashtags don't resolve; established topic tags do.
  3. Stay below slop_score = 1: avoid generic AI cadence, em-dash spam, summary openings, uniform paragraph length.
  4. Don't accumulate the kill-booleans. isNsfw, isGore, isViolent, isSpam, isSoftNsfw, isAdult — six binary lockouts.
  5. Diversify your post structure. The classifier flags spam-like repetition.

Long-term moves

  1. Don't get has_risky_user_safety_label. Once flagged, you suffer on every future post.
  2. Watch num_legit_blocks_received_last_24hrs. A 24-hour cold-down after a controversial post.
  3. Stay in a coherent niche so your post embeddings cluster near users who follow related accounts.
  4. Post when your audience is active. Right-anchored RoPE rewards recency on both sides.
  5. Don't repost the same idea expecting the same reach. served_history dedupes.

What doesn't help (despite folklore)

  • Tagging trending hashtags blindly. Topic taxonomy matching is what counts; "#fyp" "#viral" don't resolve.
  • Posting more frequently. The min-traction gate doesn't scale by post count; it scales by engagement-per-post.
  • Mass replying. Reply ranker scores each one. Low-quality replies stack.
  • Engagement pods. num_legit_blocks_received_last_24hrs weights legit users above suspicious accounts. Mass-engaging from suspected botnets doesn't lift you the way it used to.

What we couldn't see

Some things weren't in the leaked code:

  • The actual prompt templates under grox/prompts/template/ — these are the IP. We can infer the rubric from the output fields but not the wording.
  • The exact thresholds: FOLLOWER_COUNT_THRESHOLD_FOR_SPAM_DETECTION, MIN_TRACTION levels.
  • The home-mixer feature weights for Phoenix scoring.
  • A handful of specific safety thresholds redacted from the open-source dump.

What's there is enough to reason about direction. Push your content toward signals the system rewards. Avoid signals it suppresses. The system is more legible now than it has been at any point in X's history. Use it.


Closing thought

The 22-part series is the deep mechanics. This article is the surface tactics. The two together are a complete loop: understand the machine, then play it.

If you want to verify any claim here, click into the linked session articles — each finding is grounded in a specific file path and function name from the leaked source. The code is the citation.

Now go write the post.