Skip to content

Commit f9aba57

Browse files
algorithm alignment: 19-signal model, author diversity, distribution gate
Updated agent-info + preflight to match the January 2026 xai-org/x-algorithm source code (weighted_scorer.rs) and April 2026 empirical observations: agent-info: - 19 signals (was 15): added cont_dwell_time, quoted_click, share (separated from share_via_dm/share_via_copy_link), vqv, photo_expand - source field now explicitly states weights are from the unpublished params module — magnitudes are community estimates, signal list is confirmed from actual code - time_decay_halflife_minutes set to 360 (~6h) with note about the 30-60 min Phoenix distribution gate - reply signal now documents reply_engaged_by_author = ~150x (the combined effect of an author replying back to a reply on their post) - usage hints updated with distribution gate timing, author diversity mechanics, link suppression Q1 2026 data - measurement_coverage: url_clicks added to measurable, share split into 3 variants in proxy_only, cont_dwell_time added preflight: - New author_diversity_penalty warning: fires when >=3 posts in 6h, citing author_diversity_scorer.rs exponential decay behavior - New recent_post info: fires when >=1 post in last hour, warns about splitting the 30-60 min traction window - link_in_body message updated with Q1 2026 suppression data - reply question prompt now cites reply_engaged_by_author +75 post.rs: - Cannibalization warnings now included in JSON output (were stderr only) so agents see them: accelerating_post window + 6h diversity penalty both surface as [WARN] entries in the warnings array Grounded in: home-mixer/scorers/weighted_scorer.rs (19 P(action) terms), author_diversity_scorer.rs (exponential decay + floor), oon_scorer.rs (OON_WEIGHT_FACTOR), and April 2026 user reports of 30-60 min distribution gate behavior.
1 parent 050a768 commit f9aba57

3 files changed

Lines changed: 95 additions & 41 deletions

File tree

src/commands/agent_info.rs

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -212,63 +212,73 @@ pub fn execute(format: OutputFormat) {
212212
error: "{ version, status: \"error\", error: { code, message, suggestion } }",
213213
},
214214
algorithm: AlgorithmInfo {
215-
source: "xai-org/x-algorithm (January 2026, Grok-based transformer). Exact weights unpublished — estimates below from code structure + empirical data.".into(),
215+
source: "xai-org/x-algorithm (January 2026 release, Grok-based transformer in Rust). Weights are in the unpublished `params` module (excluded 'for security reasons'). Numeric values below are community estimates — the signal LIST is confirmed from home-mixer/scorers/weighted_scorer.rs but magnitudes are NOT.".into(),
216216
weights: vec![
217-
SignalWeight { signal: "follow_author".into(), weight: 30.0, ratio_to_like: "~30x (estimated)".into() },
218-
SignalWeight { signal: "share_via_dm".into(), weight: 25.0, ratio_to_like: "~25x (estimated)".into() },
219-
SignalWeight { signal: "reply".into(), weight: 20.0, ratio_to_like: "~20x (estimated)".into() },
220-
SignalWeight { signal: "share_via_copy_link".into(), weight: 20.0, ratio_to_like: "~20x (estimated)".into() },
221-
SignalWeight { signal: "quote".into(), weight: 18.0, ratio_to_like: "~18x (estimated)".into() },
222-
SignalWeight { signal: "profile_click".into(), weight: 12.0, ratio_to_like: "~12x (estimated)".into() },
223-
SignalWeight { signal: "click".into(), weight: 10.0, ratio_to_like: "~10x (estimated)".into() },
224-
SignalWeight { signal: "share".into(), weight: 10.0, ratio_to_like: "~10x (estimated)".into() },
225-
SignalWeight { signal: "dwell".into(), weight: 8.0, ratio_to_like: "~8x (estimated)".into() },
226-
SignalWeight { signal: "retweet".into(), weight: 3.0, ratio_to_like: "~3x (estimated)".into() },
227-
SignalWeight { signal: "favorite".into(), weight: 1.0, ratio_to_like: "1x (baseline)".into() },
228-
SignalWeight { signal: "not_interested".into(), weight: -20.0, ratio_to_like: "~-20x (estimated)".into() },
229-
SignalWeight { signal: "mute_author".into(), weight: -40.0, ratio_to_like: "~-40x (estimated)".into() },
230-
SignalWeight { signal: "block_author".into(), weight: -74.0, ratio_to_like: "~-74x (estimated)".into() },
231-
SignalWeight { signal: "report".into(), weight: -369.0, ratio_to_like: "~-369x (estimated)".into() },
217+
// The 19 signals from weighted_scorer.rs, ordered by estimated impact.
218+
// reply_engaged_by_author (+75 from empirical sources) is not a separate
219+
// Phoenix score but the combined effect of the reply being in conversation
220+
// context with the author — it amplifies both reply_score and dwell_score.
221+
SignalWeight { signal: "follow_author".into(), weight: 30.0, ratio_to_like: "~60x (estimated, highest positive)" .into() },
222+
SignalWeight { signal: "share_via_dm".into(), weight: 25.0, ratio_to_like: "~50x (estimated)".into() },
223+
SignalWeight { signal: "share_via_copy_link".into(), weight: 20.0, ratio_to_like: "~40x (estimated)".into() },
224+
SignalWeight { signal: "share".into(), weight: 15.0, ratio_to_like: "~30x (estimated)".into() },
225+
SignalWeight { signal: "reply".into(), weight: 13.5, ratio_to_like: "~27x (estimated, reply_engaged_by_author = ~150x)".into() },
226+
SignalWeight { signal: "profile_click".into(), weight: 12.0, ratio_to_like: "~24x (estimated)".into() },
227+
SignalWeight { signal: "click".into(), weight: 11.0, ratio_to_like: "~22x (estimated)".into() },
228+
SignalWeight { signal: "dwell".into(), weight: 10.0, ratio_to_like: "~20x (estimated, binary 2+ min threshold)".into() },
229+
SignalWeight { signal: "cont_dwell_time".into(), weight: 8.0, ratio_to_like: "~16x (estimated, continuous seconds)".into() },
230+
SignalWeight { signal: "quote".into(), weight: 8.0, ratio_to_like: "~16x (estimated, separate from retweet in 2026)".into() },
231+
SignalWeight { signal: "quoted_click".into(), weight: 5.0, ratio_to_like: "~10x (estimated, click into quoted tweet)".into() },
232+
SignalWeight { signal: "photo_expand".into(), weight: 4.0, ratio_to_like: "~8x (estimated)".into() },
233+
SignalWeight { signal: "vqv".into(), weight: 3.0, ratio_to_like: "~6x (estimated, gated by MIN_VIDEO_DURATION_MS)".into() },
234+
SignalWeight { signal: "retweet".into(), weight: 1.0, ratio_to_like: "~2x (estimated)".into() },
235+
SignalWeight { signal: "favorite".into(), weight: 0.5, ratio_to_like: "1x (baseline)".into() },
236+
SignalWeight { signal: "not_interested".into(), weight: -20.0, ratio_to_like: "~-40x (estimated)".into() },
237+
SignalWeight { signal: "mute_author".into(), weight: -40.0, ratio_to_like: "~-80x (estimated)".into() },
238+
SignalWeight { signal: "block_author".into(), weight: -74.0, ratio_to_like: "~-148x (estimated)".into() },
239+
SignalWeight { signal: "report".into(), weight: -369.0, ratio_to_like: "~-738x (estimated)".into() },
232240
],
233-
time_decay_halflife_minutes: 0, // Not published in 2026 code — removed from agent-info
234-
out_of_network_reply_penalty: 0.0, // Replaced by OON_WEIGHT_FACTOR (multiplicative, value unpublished)
241+
time_decay_halflife_minutes: 360, // ~6h half-life from empirical analysis; hard cutoff in first 30-60 min via Phoenix distribution gate
242+
out_of_network_reply_penalty: 0.0, // Replaced by OON_WEIGHT_FACTOR (multiplicative, value unpublished) in oon_scorer.rs
235243
media_hierarchy: vec![
236-
"text (highest avg engagement)".into(),
244+
"text (highest avg engagement, maximises dwell + reply probability)".into(),
237245
"native_image (triggers photo_expand_score)".into(),
238246
"native_video (requires MIN_VIDEO_DURATION_MS for vqv_score)".into(),
239-
"thread (maximises continuous dwell_time)".into(),
247+
"thread (maximises continuous cont_dwell_time_weight)".into(),
240248
],
241-
best_posting_hours: "9-11 AM local time (empirical)".into(),
249+
best_posting_hours: "8-11 AM local time (empirical, aligns with engagement velocity gate)".into(),
242250
best_posting_days: "Tuesday, Wednesday, Thursday (empirical)".into(),
243251
},
244252
measurement_coverage: MeasurementCoverage {
245253
measurable: vec![
246254
"favorite".into(), "retweet".into(), "reply".into(),
247255
"quote".into(), "impressions".into(), "bookmarks".into(),
248-
"profile_click".into(),
256+
"profile_click".into(), "url_clicks".into(),
249257
],
250258
proxy_only: vec![
251259
ProxySignal { signal: "follow_author".into(), proxy_method: "profile_click correlation".into(), confidence: "low".into() },
252-
ProxySignal { signal: "share_via_dm".into(), proxy_method: "save-worthy content heuristics".into(), confidence: "medium".into() },
253-
ProxySignal { signal: "share_via_copy_link".into(), proxy_method: "quotability heuristics".into(), confidence: "medium".into() },
254-
ProxySignal { signal: "dwell".into(), proxy_method: "word count + line breaks".into(), confidence: "high".into() },
260+
ProxySignal { signal: "share".into(), proxy_method: "quotability + save-worthiness heuristics".into(), confidence: "medium".into() },
261+
ProxySignal { signal: "share_via_dm".into(), proxy_method: "insider/practical content markers".into(), confidence: "medium".into() },
262+
ProxySignal { signal: "share_via_copy_link".into(), proxy_method: "quotability + data content markers".into(), confidence: "medium".into() },
263+
ProxySignal { signal: "dwell".into(), proxy_method: "word count + line breaks (binary 2+ min threshold)".into(), confidence: "high".into() },
264+
ProxySignal { signal: "cont_dwell_time".into(), proxy_method: "estimated read time in seconds".into(), confidence: "medium".into() },
255265
ProxySignal { signal: "photo_expand".into(), proxy_method: "media attachment detection".into(), confidence: "high".into() },
256-
ProxySignal { signal: "negative_risk".into(), proxy_method: "sentiment + combative tone analysis".into(), confidence: "medium".into() },
266+
ProxySignal { signal: "negative_risk".into(), proxy_method: "sentiment + combative tone analysis (Grok does this live since Jan 2026)".into(), confidence: "medium".into() },
257267
],
258268
blind: vec![
259269
"report".into(), "block_author".into(), "mute_author".into(),
260270
"not_interested".into(), "vqv".into(),
261271
"click".into(), "quoted_click".into(),
262-
"good_click".into(), "cont_dwell_time".into(),
263272
],
264273
},
265274
usage_hints: vec![
266275
"Always run 'xmaster analyze' before posting — it checks for common issues that hurt reach".into(),
267276
"Use 'xmaster search-ai' over 'xmaster search' — cheaper and smarter (xAI vs X API). Supports from:username for hard author filtering (e.g. 'xmaster search-ai \"from:elonmusk AI\"')".into(),
268-
"Reply to larger accounts in your niche — replies are a high-value signal (estimated ~20x a like)".into(),
269-
"Create content people want to DM to friends — DM shares are estimated ~25x a like".into(),
270-
"Never put external links in the main tweet body — put them in the first reply".into(),
271-
"Space posts 2+ hours apart — the feed diversifies repeated authors".into(),
277+
"Reply to larger accounts in your niche — and REPLY BACK when people reply to you. reply_engaged_by_author (+75) is the single highest algorithmic signal, ~150x a like".into(),
278+
"Create content people want to DM to friends — share_via_dm is one of the top scoring signals in weighted_scorer.rs".into(),
279+
"Never put external links in the main tweet body — non-Premium gets near-zero reach, Premium loses 30-50%. Links go in the first reply".into(),
280+
"Space posts 2+ hours apart — author_diversity_scorer.rs applies exponential decay for repeated authors per feed session. The algorithm only shows your top 2-3 posts; extra posts dilute your average without adding reach".into(),
281+
"The first 30-60 minutes are critical — Phoenix makes its biggest distribution decision in this window. Each post is shown to ~1500 candidates; if it doesn't get traction it stops being served. Time your posts when your audience is online".into(),
272282
"Use 'xmaster timeline --sort impressions' to find your best-performing posts".into(),
273283
"Use 'xmaster timeline --since 24h' to check recent post performance".into(),
274284
"Use 'xmaster engage recommend --topic \"your niche\"' to find high-ROI reply targets".into(),

src/commands/post.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,20 +109,32 @@ pub async fn execute(
109109
}
110110

111111
// ── Cannibalization check (is a recent post still gaining traction?) ──
112+
// The algorithm's author_diversity_scorer applies exponential decay to
113+
// repeated authors in a feed session. Posting while a previous post is
114+
// in its 30-60 min traction window actively hurts both posts.
112115
if let Ok(store) = IntelStore::open() {
113-
let velocity = store.get_recent_post_velocity();
114-
if let Ok(v) = velocity {
116+
if let Ok(v) = store.get_recent_post_velocity() {
115117
if let Some(ref accel_id) = v.accelerating_post {
118+
let warning = format!(
119+
"Your post {} is still gaining traction — posting now splits the 30-60 min distribution window",
120+
&accel_id[..accel_id.len().min(12)]
121+
);
122+
warnings.push(format!("[WARN] {warning}"));
116123
if format == OutputFormat::Table {
117-
eprintln!(
118-
"Note: Your post {} is still gaining traction. Consider waiting.",
119-
&accel_id[..accel_id.len().min(12)]
120-
);
124+
eprintln!("Warning: {warning}");
125+
}
126+
}
127+
if v.posts_6h >= 3 {
128+
let warning = format!(
129+
"{} posts in last 6h — author diversity penalty means only your top 2-3 get shown",
130+
v.posts_6h
131+
);
132+
warnings.push(format!("[WARN] {warning}"));
133+
if format == OutputFormat::Table {
134+
eprintln!("Warning: {warning}");
121135
}
122136
}
123137
}
124-
} else if format == OutputFormat::Table {
125-
eprintln!("Warning: Could not open intelligence store");
126138
}
127139

128140
// ── Execute the post ──

src/intel/preflight.rs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ pub fn analyze(text: &str, ctx: &AnalyzeContext) -> PreflightResult {
199199
issues.push(Issue {
200200
severity: Severity::Critical,
201201
code: "link_in_body".into(),
202-
message: "External link in tweet body kills reach — X suppresses linked tweets".into(),
202+
message: "External link in tweet body kills reach — non-Premium accounts get near-zero engagement, Premium accounts lose 30-50% reach (Q1 2026 data)".into(),
203203
fix: Some("Move the link to a reply instead".into()),
204204
});
205205
score -= 30;
@@ -282,7 +282,7 @@ pub fn analyze(text: &str, ctx: &AnalyzeContext) -> PreflightResult {
282282
issues.push(Issue {
283283
severity: Severity::Info,
284284
code: "no_question".into(),
285-
message: "No question mark — questions drive replies (~20x weight, estimated)".into(),
285+
message: "No question mark — questions drive replies (reply_engaged_by_author is +75, the single highest signal)".into(),
286286
fix: Some("Consider ending with a question to invite discussion".into()),
287287
});
288288
score -= 5;
@@ -332,6 +332,38 @@ pub fn analyze(text: &str, ctx: &AnalyzeContext) -> PreflightResult {
332332
score += 5;
333333
}
334334

335+
// --- Author diversity penalty warning ---
336+
// The algorithm only shows 2-3 of your posts per feed session.
337+
// Posting more than 3x in 6h dilutes your average performance without
338+
// adding reach. Check the store for recent posting velocity.
339+
if let Ok(store) = crate::intel::store::IntelStore::open() {
340+
if let Ok(velocity) = store.get_recent_post_velocity() {
341+
if velocity.posts_6h >= 3 {
342+
issues.push(Issue {
343+
severity: Severity::Warning,
344+
code: "author_diversity_penalty".into(),
345+
message: format!(
346+
"{} posts in the last 6h — author diversity scorer limits you to 2-3 per feed session, extra posts dilute your average without adding reach",
347+
velocity.posts_6h
348+
),
349+
fix: Some("Wait at least 2 hours between posts — fewer, better posts outperform high volume".into()),
350+
});
351+
score -= 15;
352+
} else if velocity.posts_1h >= 1 {
353+
issues.push(Issue {
354+
severity: Severity::Info,
355+
code: "recent_post".into(),
356+
message: format!(
357+
"You posted {} time(s) in the last hour — the algorithm's 30-60 min distribution gate means your previous post may still be in its critical traction window",
358+
velocity.posts_1h
359+
),
360+
fix: Some("Consider waiting — posting now may split attention from your previous post's traction window".into()),
361+
});
362+
score -= 5;
363+
}
364+
}
365+
}
366+
335367
// --- Sentiment check ---
336368
if features.sentiment == "negative" {
337369
issues.push(Issue {

0 commit comments

Comments
 (0)