← Back to writing

X open-sourced its moderation stack: a guide to the August 2026 update

August 13, 2026 Versão em português

X open-sourced its moderation stack: a guide to the August 2026 update

In January I wrote about how the X algorithm really works. In May, the rulebook got thrown out. The August 13 drop is the biggest one yet. The repo grew from around 216 files to 2,015, across 25 services, and this time the story isn't ranking. It's everything X does to your post before and after ranking: visibility, moderation, bot detection, and a transparency report that shows you your own file.

TL;DR

  • May was the ranking release. August is the moderation release. The new services are the ones people have theorised about for years: visibility-filtering, abuse-enforcement-service, botmaker, scarecrow, and under-the-hood, the system behind the report you can pull at x.com/i/under_the_hood.
  • The proto file that was missing in May is now published. The ranker sees exactly six boolean facts about your post. A link flag is not one of them.
  • Links are not penalised. They are measured, with second-level granularity, and opening a link carries a positive weight. The strongest positive signal in the entire system is someone copying your post's link to share it: +20.0, four times a reply.
  • The full scoring weight table is public. One report outweighs 468 favorites. A reply from a mutual follower is worth 4x a reply from anyone else.
  • Bot and spam detection runs on a rules engine called botmaker, and 20 production rules ship in the repo, including one that explicitly exempts high-reputation accounts from a spam check.
  • "Shadowban" was always the wrong word. The system is per-label, per-day, compared against accounts your size. And it's now partially inspectable, by you, about you.

Where we left off

The May release replaced a decade of hand-tuned heuristics with a transformer. Phoenix, ported from Grok-1, reads your engagement history as a sequence and predicts what you'll do with a post. Grox, a separate content-understanding service, reads the post itself. The hand-engineered rules (the 70% attenuation on your second post, the fixed out-of-network penalty) became learned parameters.

But May had two conspicuous holes.

The first was a missing file. The code referenced a protobuf crate, xai-recsys-proto, that defined what the ranker actually receives about each post. That crate wasn't in the repo. Anyone trying to answer "does the model see X about my post?" hit a wall exactly where it mattered.

The second was everything after the score. Ranking decides what your post deserves. A separate machinery decides what each viewer actually gets: filtering, safety labels, spam verdicts, bot detection. None of that was public. It was the part of the system people meant when they said "shadowban", and it was the part you couldn't check.

August fills both holes.

The proto that was missing, and what it settles

In July, a reader asked me whether writing "link in comments" gets your post down-ranked. The folk theory is that X punishes links, so people smuggle them into replies. My answer at the time had to lean on inference: the proto wasn't public, and the code set four boolean flags and then ..Default::default(), which proved more fields existed without revealing which ones.

The proto is now in the repo, at phoenix/crates/serving/xai-recsys-proto/proto/recsys.proto. Here is the complete set of boolean facts the ranker receives about your post:

message TweetBoolFeatures {
  bool is_for_you_page = 1;
  bool is_promoted_tweet = 2;
  bool is_reply = 3;
  bool is_retweet = 4;
  bool is_quote = 5;
  bool has_media = 6;
}

Six fields. Both halves of my July answer held up: there were more fields than the four visible in the code, and there is no link flag. The model cannot see, as a structural fact, whether your post contains a link.

The same file confirms the May finding that surprised people most. TweetInfo still carries no text tensor. A candidate is IDs, embeddings, engagement counts, these six booleans, and a language code. The ranking model never reads your words. Your account, meaning who engages with you and how, is still the content.

Links aren't penalised. They're measured.

Here's where I have to correct myself.

On July 18 I posted that my link posts got half the median impressions of my linkless ones, and drew the rule: "The algorithm wants dwell. A link is an exit. New rule: idea in the post, link in the first reply".

The data was real. The interpretation was too crude. The action taxonomy in the proto shows what the system actually tracks about links:

CLIENT_TWEET_OPEN_LINK
CLIENT_TWEET_EXTERNAL_LINK_LONG_DWELLED
CLIENT_EXTERNAL_LINK_SESSION_LESS_THAN_3_SEC
CLIENT_EXTERNAL_LINK_SESSION_LESS_THAN_5_SEC
CLIENT_EXTERNAL_LINK_SESSION_LESS_THAN_10_SEC
CLIENT_EXTERNAL_LINK_SESSION_MORE_THAN_30_SEC
CLIENT_EXTERNAL_LINK_SESSION_MORE_THAN_60_SEC

There is no "post contains link, so down-rank" switch. There is "did the link session last 3 seconds or 60". The system distinguishes a link that sends someone somewhere worth staying from a link that bounces them straight back.

And it's not just tracked. It's rewarded. In the scoring weights (more on those in a second), open_link carries a positive weight of +0.2: half a click, 40% of a favorite. Better still, share_via_copy_link, which fires when someone copies your post's URL to send it elsewhere, carries +20.0, the single largest positive weight in the system.

The honest picture has two layers. The ranker rewards link engagement. The spam layer polices link quality: there's a production rule (we'll get to botmaker below) that labels posts as spam when their URL's redirect chain gets a LOW_QUALITY verdict. Good links are rewarded, bad links are policed. "Links are penalised" was always the wrong compression of those two facts.

So, revised rule: don't hide your links. Earn them. If my link posts underperformed, the more likely reading is that the median link post, mine included, sends people somewhere they don't stay.

The full price list is public now

The scoring weights live in home-mixer/params/param.rs, as defaults for the final weighted sum. This is the table everyone has guessed at for years:

SignalWeight
Share via copy link+20.0
Reply+5.0
Quote+5.0
Share via DM+5.0
Follow author+4.0
Share+2.0
Retweet+1.0
Favorite+0.5
Click+0.4
Open link+0.2
Photo expand / video open+0.05
Dwell time (continuous)+0.004 per second
Not dwelled−0.02
Block author−31.2
Not interested−43.2
Mute author−58.8
Report−234.0

A few things jump out of that table.

One report cancels 468 favorites. A mute cancels about 118. Back in January I wrote that "one block might undo dozens of likes". That was an underestimate. The asymmetry between annoying people and pleasing them is more brutal than anyone guessed.

Dwell went continuous. The binary dwell head, the one asking "did this person stop on your post?", is weighted at exactly 0.0. What's weighted instead is continuous dwell time, at 0.004 per second. They stopped asking whether you paused and started counting how long you stayed. Sixty seconds of actual reading is worth more than a drive-by favorite.

Relationships are now a number. There's a parameter called BidirectionalFollowReplyWeightBoost, set to +15. If you and the author follow each other, a reply is worth 20 instead of 5. A conversation between mutuals is, mechanically, the most valuable ordinary interaction on the platform. Every "build real relationships" platitude now has an exchange rate.

A clickbait damper exists, and it's switched off. ClickDwellLowFavRatePenalty down-weights posts that get lots of click-dwell but few favorites, the signature of content that baits attention without earning approval. It ships disabled by default. Present, armed, off. Worth watching in future drops.

The head count grew from 15 to 26 since May. Quoted-post interactions, DM shares, copy-link shares, and continuous time signals all became first-class predictions.

One caveat, stated plainly: these are the open-source defaults. Production values can differ via feature switches, and X doesn't publish those. But the ratios are the best public evidence that exists, and the shape of the table (tiny positives, catastrophic negatives) is not an accident of tuning.

Botmaker: the biggest directory in the repo

The single largest directory in the August drop isn't the ranking model. It's botmaker: 492 files, bigger than Phoenix itself, plus a companion directory of actual production rules.

Botmaker is a rules engine. Detection rules are written in a domain-specific language, stored as .bot files, and evaluated against events in real time. Twenty production rules ship in botmaker-rules/scarecrow/bot/, and reading them is the closest thing yet to watching X's immune system work:

  • URL redirect chains get quality verdicts. Posts carrying LOW_QUALITY links while @-mentioning non-followers get labelled SPAM_HIGH_RECALL. That's the classic reply-guy spam pattern, encoded as a rule.
  • Duplicate text detection catches accounts posting the same reply over and over.
  • Image models classify NSFW and violence content before distribution.
  • X polices its own model's output. There's a rule specifically for detecting NSFW images generated by Grok (NSFW_Grok_Generated_Image_To_URL_Verdict). The platform runs moderation against its own generator.

And then there's the detail I haven't seen anyone write about. The low-quality-URL rule explicitly exempts certain accounts from the check:

!IsHighPageRankUser(spammerId) &&
!IsUserGrayVerified(spammerId)

If your account has high PageRank (X runs reputation scoring over the follow graph) or carries gray verification, this spam rule doesn't apply to you. The spam net has holes cut for accounts with standing. To be precise: this rule carves out those exemptions, and I can't claim all rules do. Twenty rules are also the sample X chose to publish, not the full production set. But the mechanism is now on the record. Reputation buys you out of at least some automated enforcement.

Visibility filtering: the pipeline between the score and the screen

Ranking decides what your post deserves. visibility-filtering decides what each viewer actually sees.

The service fetches safety labels per post from a dedicated store (with a cache in front and a fallback path behind it), hydrates them onto candidates, and applies visibility policies before anything renders. This is the machinery people gestured at for years with the word "shadowban": a post that ranks fine but doesn't appear, or appears for some viewers and not others.

Two details matter.

First, moderation isn't only a gate after scoring. It's a feature during scoring. TweetInfo, the message the ranker receives, carries a safetyLabelMask field. The label set travels into Phoenix. A labelled post isn't just filtered downstream, it's scored differently upstream.

Second, the label taxonomy itself is still not public. The SafetyLabelType enum lives in a proto crate (xai_visibility_filtering_proto) that isn't in the repo. That's exactly the withholding pattern of May, when xai-recsys-proto was missing and shipped three months later. If the pattern holds, the taxonomy ships in a future drop; consider that a dated prediction. Meanwhile, individual label names leak through the rules and code: SpamHighRecall, NsfwHighPrecision, UserNsfwAgathaScore, TweetNsfwAdminDropRule. The shape is visible even where the enum isn't.

Under the Hood: X shows you your own file

The August drop includes the service behind a feature most people haven't noticed: x.com/i/under_the_hood, a personal transparency report you can pull for your own account, backed by the under-the-hood directory in the repo.

The data schema (it's a thrift file, under-the-hood/thrift/uth_serving.thrift) reveals what the report is actually built from:

  • Monthly aggregates per account: eligible posts per day, post-level safety labels (with counts carried and removed per day), and account-level labels with the days they were active.
  • Labels have an observation window. A post's labels are tracked for a number of days (postObservationDays, with an isFinal marker). A label can be applied and later removed, and the report distinguishes the two. Moderation, in this system, is reversible and dated, not a permanent mark.
  • You're compared against accounts your size. The reference statistics are bucketed by follower class (under 1K, 1K–10K, 10K–100K, over 100K) with percentiles from p10 to p99. When X says your label rate is normal or elevated, "normal" means relative to your cohort.
  • A brand-safety categorisation exists in the schema but is marked experimental and unused. It's always empty in served reports.

I pulled my own report for July: 181 posts, zero post labels, zero account labels. Which is the honest limitation of this section. A clean report means the interesting parts of the schema are invisible to me. If your report does carry labels, that JSON now has a public schema to decode it against, and public rules upstream that explain how labels get applied.

This is also the quiet burial of a word. "Shadowban" implied a single secret switch. What the code shows is per-label, per-day, cohort-relative, partially reversible bookkeeping, plus a report where you can check your own. Most complaints about reach now have a testable answer, and for most accounts the answer will be what mine was: no labels. Your reach problem is content-market fit, not moderation.

What this means if you're trying to grow

Stop hiding links. Start earning them. The July folk-remedy of putting the link in the first reply solves a problem the ranker doesn't have. What it does measure is whether the link was worth opening. Send people to things that hold them for a minute, and the link is an asset: opened (+0.2), long-dwelled (tracked), and maybe copied and shared (+20, the jackpot).

Optimise for the share, not the like. A favorite is +0.5. Someone copying your link to send to a group chat is +20. The most valuable post is the one people forward, not the one they applaud. Write things worth forwarding.

Talk to your mutuals. A reply from a mutual follower is worth 20. Conversations inside your network are, by the numbers, the strongest ordinary signal you can generate.

The negative asymmetry got quantified. Respect it. One report undoes 468 favorites. One mute undoes 118. The advice from January stands, with harder numbers: not annoying people matters more than pleasing them.

Media still counts structurally. has_media is one of only six boolean facts the ranker sees about your post. It earned its place in a six-field struct, and that says something.

Check your own file monthly. Pull under_the_hood. If it's clean, and it probably is, you've eliminated moderation as an explanation. You can stop blaming the shadowban and start fixing the content.

The bigger picture

Each release has moved the boundary of what's checkable. January opened the rules. May opened the model. August opened the moderation: the layer that was always the most theorised-about and least verifiable, the one where every reach complaint went to live because nobody could prove anything either way.

What's still withheld: the production weight values, the full label taxonomy, the complete rule set, and Grox's training data. The taxonomy, if May's pattern repeats, is one release away.

The repo refreshes on a cadence. I'll be back when it drops.

Bottom line

  • The moderation layer is no longer a black box. It's the largest part of the public repo: rules, labels, exemptions and all.
  • There is no link penalty. There is link measurement, and a +20 reward for the link people share. Write accordingly.
  • You can now read your own moderation file. For most accounts it will be empty, which is its own answer: the algorithm isn't hiding you. The audience is choosing.