// Bangermeter

X Changed Three Ranking Weights and Said Nothing

By Ryan Lenk • September 20, 2026 • updated September 20, 2026

Bangermeter 0.10.2: what moved upstream, and one thing we had wrong.

On Tuesday, August 25, 2026, at 23:20 UTC, X pushed a commit to xai-org/x-algorithm carrying the same message as nearly every commit in that repository: "Open-source X Recommendation Algorithm." Three of the twenty-six ranking weights were different afterward. There was no announcement, and no way to tell from the commit that anything had moved.

We had verified all twenty-six as unchanged that same morning, and we were right: the weights file had not been touched since August 20. X changed it about nine hours later.

That is the whole problem with reading an algorithm out of a repository: it is public, it is authoritative, and it is a moving target. Everything below was re-checked against the repo at its September 18 state. Where a number is the kind that moves, we say when we read it.

Three weights moved

Head Was Now
vqv (video quality view) 0.05 0.0
dwell (binary) 0.0 0.05
video_open 0.05 0.07

Finishing a clip stopped paying. The binary dwell head started paying.

Through 0.10.0 this extension told you, in the breakdown panel, that binary dwell paid nothing and that only continuous dwell time was rewarded. That was accurate when it shipped and it is false now. video_open is the only head X has raised from a nonzero value in the published history. That history begins on August 13, so it covers five weeks.

If you make video: you are no longer paid for people watching your clip to a quality-view threshold. You are paid for the open, slightly more than before, and for holding attention, through a head that paid nothing until August 25.

Ten seconds is the number now

The binary dwell head does not mean "looked at it." X's reference data generator (phoenix/reference/world.py) defines it:

DWELL_MARK_SECONDS = 10.0
labels[:, ACTION_DWELLED]     = engaged & (dwell >= DWELL_MARK_SECONDS)
labels[:, ACTION_NOT_DWELLED] = ~engaged

In this code, engaged means the viewer took an action on the post: a like, a reply, a click and so on. Dwelled means the viewer took an action and stayed at least ten seconds. Not-dwelled means the viewer took no action at all. A reader who likes a post and scrolls on after four seconds fires neither head. A reader who reads for four seconds and takes no action fires not-dwelled.

One caution: world.py generates synthetic training data. It shows how X's reference model labels dwell. X has not published the labeling it uses in production.

It is a bar: a nine-second dwell earns none of it.

MinVideoDurationMs is also 10,000, and it is tempting to call that the same rule twice. It is not. That one gates on the clip's own length. Watch time does not enter it. It is also a strict greater-than, so a video of exactly 10.000s scores zero. Same number, different quantity. (It also now gates a weight of 0.0, so it decides whether to add nothing.)

We built this into the estimator only after getting it wrong first. The initial pass modeled dwell as the complement of not-dwelled, one minus the other, which is tidy and wrong. It produced a visibly flattened score spread, which is how the mistake surfaced before it shipped rather than after.

The reply threshold moved nine times

Every version of this extension through 0.10.0 said replies to accounts with over 100,000 followers get scored 0 to 3 by a Grok model. As of September 18 the number is 200,000.

The tempting story is that we mis-sourced it. 100_000 does appear in the published code as an argument to reply_scoring_system_prompt() (text handed to the model, which filters nothing), and it is still 100,000 there today, so anyone deriving this number from the prompt right now would get it wrong. We nearly shipped that as our own mea culpa.

It is not what happened. The gate lives in grox/flows/reply_spam/task_filter.py, and traced commit by commit it reads:

Date Follower gate
Aug 13 (publication) 15,000
Aug 14 30,000
Aug 17 40,000
Aug 18 60,000
Aug 21 80,000
Aug 24 100,000
Aug 25 120,000
Sept 8 150,000
Sept 16 180,000
Sept 17 200,000

On August 24 the gate was exactly 100,000. Our figure was correct the day it shipped, and stale the next, the same way the weights were. Nine raises in five weeks. The longest any value held was 13 days and 20 hours (120,000, from 23:20 UTC on August 25 to 19:23 UTC on September 8).

That is the more useful lesson, and the less flattering one. A number can be correctly sourced, correctly cited, and wrong by the time anyone reads it. The fix is putting a date on anything that moves. Every figure in this piece now carries one.

The current value is the named constant GROK_GEMMA_FOLLOWER_SPLIT, and it routes on whichever is larger: the direct parent's follower count or the thread root's. Below it a lighter Gemma scorer handles the reply, and that one switches to a spam-tuned model above 150,000 thread followers, so the spam-tuned model runs only in the 150,001 to 200,000 band. Above 200,000 the Grok model takes over.

Self-replies remain exempt. The rubric is still withheld by X "to reduce gameability," so this tool still lists the published inputs and still refuses to fake a direction for any of them.

X deleted the alternative scoring modes

Until September 18, value_model_mode selected between the weighted sum everyone models and two dwell-regret variants carrying far deeper negatives: a report at −60,000 rather than −234. This extension caveated them in the panel, because they were real code.

They are gone. X deleted the switch, all seventeen dwell_regret parameters, and value_model_gate.rs outright. That happened three weeks after the weight change, on September 18. The weighted sum is now the only scoring mode published. The caveat has been removed rather than softened.

That deletion took something with it. We used to say bookmarks survived as a user-history feature inside the dwell-regret gate. That gate no longer exists, but the replacement claim needs to be narrower than it is tempting to make it.

There is no bookmark weight. No coefficient multiplies a predicted bookmark, so a bookmark cannot be turned into score the way a like or a reply can. That is the real claim and it holds.

What does not hold is "bookmarks are invisible to the model." bookmark_count is hydrated from engagement counts and sent to Phoenix as a model input in home-mixer/models/candidate.rs, in the same struct literal, twenty-six lines above the content_features call this piece praises two sections later. The model can see your bookmarks. The value model simply has no term to pay them with.

There is a small-account lane, and it ships on

This is new to the extension because it was never surfaced before, and it deserves to be.

EnableViewerColdStart defaults to true. On every For You request, at most one post is lifted to around the 16th slot of the thirty-five you get. If nothing qualifies, nothing is promoted. To be the one:

  • the author has 1,000 followers or fewer
  • it is an original post: not a reply, not a repost
  • it is under 48 hours old (this was 24 hours until September 17)
  • it has fewer than 1,000 Home impressions so far
  • it already ranks inside the top 85% of the candidates with a nonzero score

One post per request. Not per author, not per session. The best-scoring eligible candidate wins and everything else eligible gets nothing.

And it is a score floor. The winner's score is raised to whatever the 16th-highest post scored (index 15, counting from zero: max(own_score, target)), so a post already scoring higher gains nothing, and later scorers can still move it. "Around the 16th slot" describes the shape. Nothing guarantees it.

This is the only published mechanism that deliberately advantages small accounts, and it is narrow. Bangermeter reports the eligibility rules and does not claim your post was the one promoted: the slate is server-side and an extension cannot see it. Anyone telling you they can detect a cold-start promotion from the browser is guessing.

The model's content inputs are published now

home-mixer/models/content_features.rs arrived on September 8, and it names what the ranker receives about your post's content. Seven things:

has_video · max_video_duration_ms · has_photo · media_count · weighted_text_len · newline_count · has_url

Two details worth having. A URL counts as 23 characters regardless of its real length, and CJK characters and emoji count 2 where Latin counts 1, per X's weighted-length rules in simplified form (is_url matches only http:// and https://, so a bare domain is counted character by character). And a media attachment's t.co link is subtracted before has_url is decided, so posting an image does not make your post "have a link."

newline_count is the interesting one. It is the first published evidence that post shape, as well as length, reaches the ranker. No coefficient is attached to any of these, so this extension lists them and does not score them. The difference matters.

Two things in the interface

The breakdown panel docks to the bottom-right corner. It used to open anchored to the badge you clicked, which meant it covered the post it was explaining, and got whatever vertical room happened to be left above or below, which on a mid-screen post was not enough to open fully. It now takes the corner at full height and stays out of the way.

The badge closes the panel. Clicking the badge on a post whose breakdown was already open used to appear to do nothing. It was actually closing and reopening too fast to see: the outside-click handler is bound in the capture phase and ran first, tore the panel down, and the badge rebuilt it a moment later. Now the same badge toggles shut, while a different post's badge still switches the panel over, which is what you want when comparing two drafts.

Also

The Brazil electoral filter covers 2,776 accounts as of September 18, up from 665 when X first published it on August 14. X changed the list six times from August 25 through September 18, and one of those changes made it smaller, so treat it as a dated reading. It is a hard filter that runs before scoring, so no weight offsets it.

The boundary has not moved

The weights are X's. The probabilities are ours. X predicts them with Phoenix, a transformer we do not have; this extension derives three of them from real counts (likes, replies, reposts) and estimates the rest from what a browser can actually see. Every number in the interface is tagged with which layer it came from.

One thing worth stating plainly, because it disciplines everything above. X's own comment block in param.rs says these weights multiply predicted probabilities, not counts, and names the ratio reading (their example is "one report cancels 468 likes") as incorrect. A report's baseline probability is over a thousand times lower than a like's, which is why its coefficient is large. So when this piece says you are "paid" for an open or a dwell, read that as a coefficient on how likely you are to be opened or dwelled on.

That is also the cleanest reason the folklore fails. Not "bookmarks are 20× a like." Not the "−120 block penalty." Not "links cost you 30 to 50%." Those numbers were invented, but even the real ones do not work the way the folklore assumes they do.

What changed this round is that three real weights moved unannounced, a scoring mode we were caveating got deleted three weeks later, and a threshold we had cited correctly went stale in a day. All three are the same failure mode, and the fix is the same: date every number, and re-read the source next release. The sync stamp at the top of param.rs has changed 18 times from August 25 through September 18, and it will change again.


Bangermeter 0.10.2. 255 self-tests, every published weight checked against X's source individually. Free, MIT, no network requests, no telemetry.

Bangermeter is an independent open-source project under the MIT license. It is not affiliated with, endorsed by, or sponsored by X Corp or xAI. The author sells SEO, GEO and AEO services, and this article describes a free tool he built.

Want receipts like this for your own site?

Run the free AI Readiness Scanner: paste in your robots.txt and your homepage source, get a scored report on whether ChatGPT, Perplexity, and Google AI can actually read your business. About two minutes, no signup, and nothing you paste ever leaves your browser.

Run the free scan → Get The Receipts free ▸
Ryan Lenk
Written by Ryan Lenk

I run the SEO and AI visibility for my family's Shopify shop and publish the receipts, good and bad. Every number on this site comes from a named tool export, and corrections get published rather than edited away.

About meRun the free AI Readiness ScannerWork with me

← All articles Subscribe on YouTube ▸