1. X 알고리즘 4세대+3 후속 드랍 — 광고 브랜드 안전 V2 하루 만에 재설계 + Following 파이프라인 VF 분기 + VMRanker에 SID 배선 + Phoenix Kafka V3 + grox reply_spam 통합
자료 xai-org/x-algorithm @ 11a71f8 — "Open-source X Recommendation Algorithm" — 2026-08-18 18:57 UTC, 공개 소스코드. 파일 14개, +208/−70. 4세대+2(2026-08-17, 파일 34개 +751/−162) 대비 규모는 절반 이하로 작아졌지만 성격은 어제 반영한 사실 하나를 하루 만에 뒤집는 dual-write 재설계 + 파이프라인·인덱싱·grox 세 층의 정리가 하나의 커밋에 담긴 형태.
배경
4세대(2026-08-13, 47c1bcd) 초대형 드랍이 3세대까지 블랙박스였던 가중치·안전 파이프라인·labeling 서브레포 15개·Phoenix 학습 코드·SimClusters 부활을 한꺼번에 공개했다. 그 뒤 후속 드랍이 하루 간격으로 붙었다:
- 4세대+1 (2026-08-14,
c65aa17, 파일 21개 +2,271/−32) — 가중치 해석 규칙·브라질 선거 필터·Thompson sampling·Phoenix bool 3종·BDSM enforcement note 부분 공개 (상세는 daily/2026-08-16.md) - 4세대+2 (2026-08-17,
b089ce6, 파일 34개 +751/−162) — 광고 브랜드 안전 V2 판정 함수 (compute_verdict_v2+strip_v1_grok_written+PROMPT_OWNED_RULES7개), 성인 컨텐츠 Grok 4.5 심판 계층, Phoenix retrieval 인덱싱 게이트 제거 (상세는 daily/2026-08-19.md)
이 흐름의 다음 드랍 (2026-08-18, 11a71f8)이 오늘의 자료다. 어제 canon이 상세하게 반영한 strip_v1_grok_written + PROMPT_OWNED_RULES 7개 rule id 마이그레이션 헬퍼가 이 커밋에서 완전히 삭제됐다는 점이 이 저장소가 다루는 유일한 "매일 커밋" 오픈소스 서비스에서 처음 있는 사건이다. 어제 자 daily에 남긴 "다음 커밋에서 어떤 param이 어떻게 바뀌는지가 곧 A/B 결과의 지연 지표가 된다"는 관찰이 하루 만에 실물 근거를 얻었다.
다섯 방향이 canon에 영향을 준다.
첫째, 광고 브랜드 안전 V2의 dual-write 시맨틱이 하루 만에 재설계됐다. 4세대+2 원안은 V2 판정 로직이 V1 라벨을 볼 때 특정 botmaker rule id 7개(PROMPT_OWNED_RULES)에 소속된 dual-write 라벨만 필터링한 뒤 V2 라벨 세트로 판정하는 접근이었다. 오늘 커밋은 이 헬퍼(strip_v1_grok_written)와 Cow import까지 완전 삭제하고, 대신 새 상수 V2_WRITTEN_LABELS(V2 라벨 4개)를 게이트로 삼는다. compute_verdict_v2 초입에서 라벨 집합에 이 4개 중 하나라도 있는지 검사해 (a) 없으면 곧장 compute_verdict(v1)으로 폴백, (b) 있으면 순수 V2 라벨 세트(NSFA_HIGH_PRECISION·NSFA_LIMITED_INVENTORY 제거된 세트)로 판정한다. 이 시맨틱 변경의 결정적 증거는 새 테스트 케이스 v2_defers_to_v1_when_v2_has_not_ruled에서 assert되는 케이스 하나 — NSFA_HIGH_PRECISION + GROK_NSFA + GROK_SFA_V2 라벨 세트에서 V1은 MediumRisk, V2는 Safe. 즉 V2가 판정 신호(GROK_SFA_V2 하나)를 보내면 V1의 전통 heuristic 라벨(NSFA_HIGH_PRECISION)까지 무시하고 V2 결정이 완전히 지배.
둘째, reverse-chron(Following) 파이프라인이 전용 VF hydrator로 분기됐다. 신규 파일 home-mixer/candidate_hydrators/vf_following_candidate_hydrator.rs(95줄)가 배선되고 reverse_chron_posts_pipeline.rs가 기존 VFCandidateHydrator 대신 이 새 하이드레이터를 쓴다. 새 하이드레이터의 시그니처는 기존과 거의 같지만 반환하는 PostCandidate에 visibility_reason과 drop_ancillary_posts 두 필드만 값을 넣고 나머지는 ..Default::default()로 재설정한다는 차이가 있다. 즉 Following 파이프라인은 VF 하이드레이션 산출을 이 두 필드로 축소하고 나머지 후보 필드는 다른 하이드레이터가 채우는 구조. should_drop_ancillary 헬퍼는 기존 파일에서 pub(crate)로 공개돼 새 파일에서 재사용된다.
셋째, VMRanker 요청에 SID(semantic ID) 필드 7개가 신규 배선됐다. home-mixer/scorers/vm_ranker.rs의 build_request가 각 source의 k, pool_rank, pool_rank_gap 옆에 sid_known, sid_k1/k2/k3(3-level 계층 SID codeword), sid_gap1/gap2/gap3(각 레벨 이웃 gap)을 함께 실어 보내기 시작했다. 3세대~4세대+2까지 SID는 Phoenix retrieval의 window 이름(video_14day·video_48h·nsfw_video_48h/168h/14day)에만 등장했는데, 이제 재순위(VMRanker) 쪽에서도 SID를 명시 소비하기 시작한 첫 흔적이다. 3-level 코드북(coarse → fine)이 존재한다는 아키텍처 사실도 이 배선으로 새로 확정.
넷째, Phoenix postCreationEventForwarder가 unified posts topic V3로 마이그레이션됐다. Kafka topic이 content_understanding_realtime_unified_posts → ..._v3로, Kafka 클러스터가 /s/kafka/main-2:kafka-tls(X 메인) → /s/kafka/phoenix-kafka-external-bootstrap(Phoenix 전용 별도 클러스터)로, 매핑이 ValueOnly(Thrift value만) → KeyValue({key = Long, keyEncoding = NativeEncoding, ...})(postId를 Kafka key로 명시)로 이동했다. postCreationEventProcessor.strato의 시그니처도 Op.execute[Event, Unit] → Op.execute[(Long, Event), Unit]로 바뀌어 튜플에서 온 postId를 우선 사용한다. Consumer group도 post_creation_event → post_creation_event_v3 (+ develConsumerGroupId = Some("post_creation_event_v3_devel")) 신설.
다섯째, grox reply_spam이 별도 흐름을 삭제하고 단일 통합 흐름으로 압축됐다. 이전에는 PostStreamTaskGenerator(전체 post 스트림에서 스팸 감지)와 ReplyRankingTaskGenerator(답글 랭킹) 두 개가 나란히 있었는데, 오늘 커밋은 PostStreamTaskGenerator를 삭제하고 TOPIC_UNIFIED_POSTS(v1 topic) 상수까지 지운 뒤, ReplyRankingTaskGenerator가 PlanReplyRanking(기존) + PlanSpamComment(신규 흡수) + PlanCoordinatedSpam(신규 흡수) 세 플랜을 함께 주입하는 형태로 통합됐다. 같은 파일의 follower 임계도 30k → 60k로 다시 두 배 상향 (4세대+1의 15k → 30k에 이어 나흘 만에 두 번째 두 배). 즉 grok으로 스팸/답글 랭킹을 채점하는 대상이 "top 30k 팔로워는 안 채점"에서 "top 60k는 안 채점"으로 축소됐고, 채점 파이프라인 자체도 단일 topic (v3) 단일 흐름 하나로 정리됐다.
새로 알게 된 것 — brand safety V2의 재설계
핵심 변경 [확인] — home-mixer/models/brand_safety.rs:
새 상수:
const V2_WRITTEN_LABELS: &[SafetyLabelType] = &[
SafetyLabelType::GROK_SFA_V2,
SafetyLabelType::GROK_NSFA_V2,
SafetyLabelType::GROK_NSFA_LIMITED_V2,
SafetyLabelType::GROK_NSFA_EXPANDED_V2,
];
새 게이트:
pub(crate) fn compute_verdict_v2(
labels: &HashMap<SafetyLabelType, SafetyLabel>,
tweet_id: u64,
) -> BrandSafetyVerdict {
if !V2_WRITTEN_LABELS.iter().any(|l| labels.contains_key(l)) {
return compute_verdict(labels, tweet_id);
}
if MEDIUM_RISK_LABELS_V2.iter().any(|l| labels.contains_key(l)) {
return BrandSafetyVerdict::MediumRisk;
}
// ... low risk 검사, 그 외 Safe
}
V2 라벨 세트 축소:
MEDIUM_RISK_LABELS_V2에서NSFA_HIGH_PRECISION제거 (이전 16개 중 하나였다)LOW_RISK_LABELS_V2에서NSFA_LIMITED_INVENTORY제거 (이전 3개 중 하나였다)
새 테스트 v2_defers_to_v1_when_v2_has_not_ruled가 시맨틱을 케이스 6개로 명시:
let v1_safe = labels_with(&[SafetyLabelType::GROK_SFA]); assert_eq!(compute_verdict_v2(&v1_safe, PRE_CUTOFF_ID), compute_verdict(&v1_safe, PRE_CUTOFF_ID)); assert_eq!(compute_verdict_v2(&v1_safe, PRE_CUTOFF_ID), BrandSafetyVerdict::Safe); // 결정적 케이스: V2가 SFA 라벨을 보내면 V1의 heuristic까지 무시 let freed = labels_with(&[ SafetyLabelType::NSFA_HIGH_PRECISION, SafetyLabelType::GROK_NSFA, SafetyLabelType::GROK_SFA_V2, ]); assert_eq!(compute_verdict(&freed, PRE_CUTOFF_ID), BrandSafetyVerdict::MediumRisk); assert_eq!(compute_verdict_v2(&freed, PRE_CUTOFF_ID), BrandSafetyVerdict::Safe);
어제 canon이 반영한 것 대비 무엇이 뒤집혔나:
| 항목 | 4세대+2 원안 (b089ce6, 2026-08-17) | 4세대+3 재설계 (11a71f8, 2026-08-18) |
|---|---|---|
| 마이그레이션 헬퍼 | strip_v1_grok_written이 PROMPT_OWNED_RULES = [1400, 1410, 1420, 1500, 1510, 1610, 1700] 소속 V1 dual-write 라벨을 필터 |
완전 삭제. 헬퍼도 상수도 없음 |
| V2 판정 조건 | 항상 V2 라벨 세트로 판정 (필터 후) | V2_WRITTEN_LABELS 4개 중 하나가 있어야 V2 판정, 없으면 V1 폴백 |
| V2 라벨 세트 | Medium 16개(NSFA_HIGH_PRECISION 포함), Low 3개(NSFA_LIMITED_INVENTORY 포함) |
Medium 15개(NSFA_HIGH_PRECISION 제거), Low 2개(NSFA_LIMITED_INVENTORY 제거) |
| 검증 테스트 | v2_mirrors_v1_across_tier_matrix (V1/V2가 6가지 라벨 조합에서 정확히 일치해야) |
그 테스트도 라벨 세트 변경에 맞게 필터가 확장됨 + 새 테스트 v2_defers_to_v1_when_v2_has_not_ruled 추가 |
이 재설계의 방향은 훨씬 명확하다 — dual-write 마이그레이션의 정공법인 "V2가 판정했으면 V2가 지배, 아니면 V1 안전망 유지". 어제 반영한 rule id 7개 필터 접근은 하루짜리 실험이었다는 뜻이다.
새로 알게 된 것 — Following 파이프라인의 VF 분기
파일 신규 [확인] — home-mixer/candidate_hydrators/vf_following_candidate_hydrator.rs:
pub struct VFFollowingCandidateHydrator {
pub strato_vf_client: Arc<dyn VfClient + Send + Sync>,
pub xai_vf_client: Arc<dyn VfClient + Send + Sync>,
}
#[async_trait]
impl Hydrator<ScoredPostsQuery, PostCandidate> for VFFollowingCandidateHydrator {
async fn hydrate(&self, query: &ScoredPostsQuery, candidates: &[PostCandidate])
-> Vec<Result<PostCandidate, String>> {
// ... post_ids 수집, client.get_result(post_ids, TimelineHome, query.user_id, context)
let hydrated = match primary_result {
Some(Err(err)) => Err(err.to_string()),
_ => Ok(PostCandidate {
visibility_reason,
drop_ancillary_posts: Some(drop_ancillary),
..Default::default()
}),
};
// ...
}
}
reverse_chron_posts_pipeline.rs의 배선 교체 [확인]:
- Box::new(VFCandidateHydrator::new(strato_vf_client, xai_vf_client).await),
+ Box::new(VFFollowingCandidateHydrator::new(
+ strato_vf_client,
+ xai_vf_client,
+ )),
SafetyLevel::TimelineHome유지 — Following과 For You가 안전 레벨은 공유 (TimelineHomeRecommendations가 아님을 재확인)should_drop_ancillary헬퍼는 기존vf_candidate_hydrator.rs에서pub(crate)로 승격돼 두 하이드레이터에서 공유- 반환값 차이: 기존 하이드레이터는 후보 전체 필드를 채워 반환, 새 하이드레이터는 두 필드(
visibility_reason+drop_ancillary_posts)만 채우고 나머지는 default. 즉 Following 파이프라인은 다른 하이드레이터가 이미 나머지 필드를 채워둔 상태에서 VF만 얹는다는 뜻으로 읽힌다[추정]
의미 For You와 Following의 파이프라인이 이제 하이드레이션 단계에서 명시적으로 갈리기 시작. Following은 인네트워크 전용이라 필요한 후보 필드 세트가 다르니 그에 맞춰 하이드레이터를 분화한 것으로 보인다.
새로 알게 된 것 — VMRanker에 SID 필드 7개 배선
[확인] — home-mixer/scorers/vm_ranker.rs:194-206:
Some(Source {
k: s.k,
pool_rank: s.pool_rank,
pool_rank_gap: s.pool_rank_gap,
sid_known: s.sid_known,
sid_k1: s.sid_k_l1,
sid_k2: s.sid_k_l2,
sid_k3: s.sid_k_l3,
sid_gap1: s.sid_gap_l1,
sid_gap2: s.sid_gap_l2,
sid_gap3: s.sid_gap_l3,
}),
- 3-level 계층 SID:
sid_k_l1(coarse) →sid_k_l2(mid) →sid_k_l3(fine)의 세 codeword를 각 후보 소스가 실어 온다 sid_gap_l{1,2,3}: 각 레벨 코드북에서의 이웃 gap.pool_rank_gap과 짝을 이루는 필드명이라 신뢰도/희소성 대리 지표로 보이는 방향[추정]sid_known: SID가 known/valid인지 bool 플래그. unknown이면 VMRanker가 다른 필드로 fallback하는 것으로 추정[추정]- 기존
k·pool_rank·pool_rank_gap(4세대에서 확정된 후보 소스 rank 정보) 옆에 나란히 배선. 즉 VMRanker가 다양성/재순위 판단에서 (a) 후보 소스별 collaborative rank + (b) 3-level semantic ID 위치 + (c) 이웃 gap의 3축 정보를 갖게 됐다
의미 X가 3세대 이래 유지한 SID(semantic ID) 인프라의 rank/rerank 쪽 소비자가 처음으로 배선된 사례. Phoenix retrieval 쪽 SID window(4세대+1에서 video_14day 추가된 그 SID)와 이 vm_ranker 필드가 같은 코드북을 공유하는지, VMRanker가 SID gap을 DPP 커널의 유사도 성분으로 쓰는지가 다음 커밋의 관전 포인트. Kuaishou HD-Rec/PushDualGen이 관찰된 SID 계열(radar.md 관찰 중) 3층 코드북 접근과의 대응 관계도 관전 포인트 (X는 코드북 구성·훈련·vocabulary 미공개).
새로 알게 된 것 — Phoenix Kafka V3 마이그레이션
[확인] — phoenix-rankall-strato/stream_forwarders/postCreationEventForwarder.strato:
-import <content_understanding/sharedConfig>
+import <hydra/sharedConfig>
StreamForwarder({
source = KafkaTopic({
- topic = "content_understanding_realtime_unified_posts",
- dest = "/s/kafka/main-2:kafka-tls",
- consumerGroupId = "post_creation_event",
- develConsumerGroupId = None,
- mapping = ValueOnly({
+ topic = "content_understanding_realtime_unified_posts_v3",
+ dest = "/s/kafka/phoenix-kafka-external-bootstrap",
+ consumerGroupId = "post_creation_event_v3",
+ develConsumerGroupId = Some("post_creation_event_v3_devel"),
+ mapping = KeyValue({
+ key = Type(Long),
+ keyEncoding = NativeEncoding,
value = Type(...ContentUnderstandingMetadataV2),
- encoding = ThriftEncoding
+ valueEncoding = ThriftEncoding
})
}),
postCreationEventProcessor.strato의 시그니처 대응 변경 [확인]:
-val executeOp = Op.execute({ idempotent = true })[Event, Unit] { ctx =>
+val executeOp = Op.execute({ idempotent = true })[(Long, Event), Unit] { ctx =>
+ val (postId, _) = ctx.arg
val request = {
- postId = ctx.arg.postMetadata.post.postId,
+ postId = postId,
eventSource = PostCreation
}
의미 Phoenix retrieval의 upstream 이벤트 스트림이 (a) 콘텐츠 이해 스키마 v1 → v3로, (b) X 메인 Kafka 클러스터에서 Phoenix 전용 격리 클러스터로, (c) 매핑에서 postId를 Kafka key로 승격해 파티셔닝·중복제거·역인덱스 조회를 순수 key 기반으로 가능하게 하는 세 방향 개편. hydra/sharedConfig로의 import 이동은 Phoenix 계열이 hydra 인프라 하위로 편입되고 있다는 신호. 4세대+2의 "인덱싱 게이트 제거"와 결이 통함 — 인덱싱 처리량이 커지므로 별도 Kafka 클러스터가 필요해진 방향 정합 [추정].
새로 알게 된 것 — grox reply_spam 흐름 통합 및 임계 재조정
[확인] — grox/flows/reply_spam/generators.py:
-@register
-class PostStreamTaskGenerator(StreamTaskGenerator):
- TASK_GENERATOR_TYPE = POST_STREAM
- PLANS_TO_INJECT = {PlanSpamComment.KEY, PlanCoordinatedSpam.KEY}
-
- def _get_loader(self):
- return KafkaPostLoader(TOPIC_UNIFIED_POSTS)
-
@register
class ReplyRankingTaskGenerator(StreamTaskGenerator):
TASK_GENERATOR_TYPE = REPLY_RANKING
- PLANS_TO_INJECT = {PlanReplyRanking.KEY}
+ PLANS_TO_INJECT = {
+ PlanReplyRanking.KEY,
+ PlanSpamComment.KEY,
+ PlanCoordinatedSpam.KEY,
+ }
def _get_loader(self):
return KafkaPostLoader(TOPIC_UNIFIED_POSTS_V3)
task_filter.py의 두 임계 상수 재조정 [확인]:
TaskSpamFilter.FOLLOWER_COUNT_THRESHOLD_FOR_SPAM_DETECTION: 30,000 → 60,000TaskReplyRankingFilter.FOLLOWER_COUNT_THRESHOLD_FOR_REPLY_RANKING: 30,000 → 60,000
constants.py 정리 [확인]: POST_STREAM, TOPIC_UNIFIED_POSTS 상수 삭제. 남은 상수는 REPLY_RANKING, REPLY_RANKING_RECOVERY, TOPIC_UNIFIED_POSTS_V3, TOPIC_REPLY_RANKING_RECOVERY.
의미 grox의 스팸 판정 파이프라인이 (a) 채점 대상 계정을 60k 미만으로 축소 + (b) 흐름을 답글 랭킹과 통합해 단일 흐름으로 압축. 같은 커밋의 Phoenix unified_posts_v3 마이그레이션과 짝지어 grox와 Phoenix 양쪽이 v3 콘텐츠 이해 스트림으로 동시 이동했다는 관찰이 붙는다. follower 임계의 4일 만의 두 번째 두 배는 grok 리소스가 large-follower 계정에 태우지 않는 방향으로 계속 이동 중이라는 신호.
새로 알게 된 것 — 콜드스타트 계측 단순화
[확인] — home-mixer/scorers/author_cold_start.rs:100-107:
let source = c.served_type.map(|t| t as i32).unwrap_or(0);
- if tracked.contains(&c.tweet_id) {
- *counts.entry((c.tweet_id, source)).or_insert(0) += 1;
- }
- if tracked.contains(&c.author_id) {
- *counts.entry((c.author_id, source)).or_insert(0) += 1;
- }
+ *counts.entry((c.tweet_id, source)).or_insert(0) += 1;
tracked리스트 필터 제거 +author_id카운트 삭제 → 모든 후보의tweet_id를 항상 카운트- 즉
home_mixer.cold_start_tracked_ids_total{id, source}계측이 이제 특정 tracked ID의 등장 횟수가 아니라 콜드스타트 파이프라인이 소스별로 후보를 몇 개 흘려보내는지의 전수 관측으로 성격이 바뀌었다 ColdStartTrackedIdsparam 자체는 그대로 남아 있으나 이 함수의 사용처와의 배선이 바뀐 셈. 관찰 훅의 목적이 "특정 계정 A/B" → "전체 소스별 트래픽" 방향으로 이동
추가 관찰 — 마이너 변경 4건
이 커밋에는 아래 변경도 함께 있다:
- grox adult content cross-validation 로깅 개선
[확인]— 어제 반영한 심판 태스크의Metrics.counter(metric)outcome attribute이"hard"|"soft"이분 라벨에서 판정된policyType.value원본으로 변경. 심판이 다른 성인 카테고리 세분화를 반환하는 경우에도 손실 없이 계측 가능 home-mixer/candidate_hydrators/mod.rs에vf_following_candidate_hydrator모듈 등록[확인]visibility-filtering/server_deps.rs에 Gizmoduck Strato 클라이언트 타임아웃 80ms 명시[확인]—const GIZMODUCK_STRATO_REQUEST_TIMEOUT_MS: u64 = 80상수 신설 +request_timeout_ms: Some(GIZMODUCK_STRATO_REQUEST_TIMEOUT_MS)로 클라이언트 설정에 배선. 이전엔 timeout 명시 없음home-mixer/candidate_hydrators/ads_brand_safety_vf_hydrator.rs+1/−1[확인]— 상위 하이드레이터의 미세 조정 (V2 판정 재설계와 짝지어 배선 유지)
정본 반영 services/x.md — 8개 절 갱신:
- 한 줄 요약: 최종 갱신 2026-08-19 → 2026-08-20, 4세대+3 서술 추가
- 공개 소스 4세대 테이블: 4세대+3 행(11a71f8) 추가
- 광고 절 (
brand_safetyV2): 4세대+2/4세대+3 두 세대를 나눠 서술하는 형태로 확장.strip_v1_grok_written삭제와V2_WRITTEN_LABELS게이트 재설계 상세, 새 테스트v2_defers_to_v1_when_v2_has_not_ruled의 결정적 케이스 인용 - 랭킹 절 (VMRanker + DPP): SID 필드 7개 배선 표 추가, 3-level 계층 코드북 사실 반영
- 콜드스타트 절:
count_tracked_ids단순화 반영 - Phoenix 학습·인덱스 절:
postCreationEventForwarderV3 마이그레이션 상세 표 - Grox 절: 4세대+3 재조정 + 흐름 통합 (
PostStreamTaskGenerator삭제, follower 30k → 60k), 4세대+2 심판 태스크 로깅 개선 반영 - 피드 지면 절 (Following):
VFFollowingCandidateHydrator분기 반영 - 열린 질문 절: "4세대+3 드랍으로 해소된 것 / 뒤집힌 것 (2026-08-20)" 신설,
⚠ 테제 변경 (하루 만에)항목으로 어제 반영한PROMPT_OWNED_RULES라인이 뒤집혔음을 명시 - 출처 절: 4세대+3 관련 파일 6개 링크 추가
의미
⚠ 테제 변경 (하루 만에): 어제 canon이 상세히 반영한 광고 브랜드 안전 V2의 strip_v1_grok_written 헬퍼와 PROMPT_OWNED_RULES = [1400, 1410, 1420, 1500, 1510, 1610, 1700] 7개 rule id 마이그레이션 로직이 오늘 완전히 사라졌다. 어제 daily에 심층 분석 필요로 남겼던 "PROMPT_OWNED_RULES 7개 rule id ↔ botmaker-rules/ 실 규칙 파일 매핑" 질문은 자동 해소된 게 아니라 질문 자체가 없어졌다 — 그 상수가 코드에서 지워졌기 때문. 이 사건이 canon에 두 가지 관찰 근거를 남긴다:
X의 4세대 릴리즈 리듬이 "매일 커밋 + 하루 만의 재설계도 흔한 일"이라는 관찰이 확정됐다. 4세대(8-13) → 4세대+1(8-14) → 4세대+2(8-17) → 4세대+3(8-18)이 나흘 사이 배포된 4번 드랍이고, 그중 마지막 두 개(어제/오늘)는 어제 넣은 dual-write 로직이 오늘 완전 대체된 하루짜리 실험이다.
docs/BIDIRECTIONAL_BOOST_CHANGE.md가 4세대에서 도입한 "코드 diff로 파라미터 변경을 문서화하겠다"는 관행이 4일 만에 실물 사례를 두 번 만들었다.canon이 새 dual-write 로직을 반영할 때는 "이 로직 자체가 다음 커밋에서 바뀔 수 있음"을 프레이밍에 넣는 게 맞다. 어제 daily의
strip_v1_grok_written서술은 상세 근거·rule id 나열·미공개 항목 목록까지 완벽했지만, "이 로직이 안정판이 될 것"이라는 함의가 은연중에 실려 있었다. 오늘 이후는 새 dual-write 배선을 볼 때 "이번 커밋의 특정 헬퍼가 다음 세대에서 지워질 수 있다"는 관찰을 항상 병기한다.V2_WRITTEN_LABELS4개 라벨의 유래가 새 심층 분석 대상이다.GROK_SFA_V2·GROK_NSFA_V2·GROK_NSFA_LIMITED_V2·GROK_NSFA_EXPANDED_V2— 이 네 개는 어제 등장한 3개(GROK_NSFA_V2·GROK_NSFA_EXPANDED_V2·GROK_NSFA_LIMITED_V2) +GROK_SFA_V2(신규). 새로 붙은GROK_SFA_V2가 "safe for ads V2"라는 명시적 positive 라벨을 뜻하는지, Grok 프롬프트 재훈련이 어제와 오늘 사이에 배포된 것인지, 아니면 V2 라벨러가 자산의 다른 파이프라인에서 온 것인지 미확인.
검증에서 뒤집힌 것
⚠ 4세대+2 dual-write 로직 서술 (어제 daily/2026-08-19의 상세 인용 포함): strip_v1_grok_written + PROMPT_OWNED_RULES 7개 rule id 라인이 오늘 코드에서 삭제됐다. 정본에서는 4세대+2 서술을 삭제하지 않고 "하루 만에 재설계됨" 주석을 붙여 세대 대비의 근거로 남겼다 (열린 질문 절과 4세대 테이블 두 곳에 명시). 이는 CLAUDE.md의 "정본은 결론만 담아 압축" 원칙을 살짝 위반하는 것처럼 보이지만, 여기서는 하루짜리 dual-write 실험 자체가 X의 릴리즈 리듬을 관찰하는 데 결정적 근거이므로 세대 대비로 남긴다.
레이더
없음. 오늘 arXiv cs.IR triage 30건 중 프로덕션·A/B 진술이 있는 신규 통과 항목 0건. UniDot (arXiv 2608.16797, 2026-08-17)이 industrial recommender 통합 아키텍처를 다루지만 KDD Cup 2026 runner-up 결과 진술만 있고 프로덕션 배포·저자 소속·A/B 지표 어느 것도 미공개라 레이더 수용 기준 미달. 산업 블로그 스윕은 오늘 목요일이라 스킵 (월요일에만 수행).
다음에 볼 것
- [다음 X 커밋] V2 dual-write 시맨틱이 다시 바뀌는지,
V2_WRITTEN_LABELS4개 라벨이 어떤 파이프라인 변경과 짝짓는지 — 지금까지의 리듬대로면 오늘·내일 후속 드랍 가능성 높다 - [다음 X 커밋] SID vm_ranker 필드 소비 확장 — VMRanker가 SID gap을 DPP 커널의 유사도 성분으로 쓰는지, 다른 스코어러(예:
ranking_scorer.rs)에도 SID 필드가 들어가는지 - [다음 X 커밋] Phoenix Kafka v3 마이그레이션 후속 — v1 forwarder shutdown 시점, v2 존재 여부, 다른 stream_forwarder들의 v3 이동
- [Meta 블로그] ai.meta.com/blog / engineering.fb.com 다음 랭킹·retrieval 계열 글 (마지막 관련 글 2026-08-05)
- [Netflix 블로그] GenRec 후속 또는 real-time graph part 4 (마지막 관련 글 2026-08-07)