返回 last30days-skill
2026-08-14-feat-x-retrieve-judge-retry-plan.md
根目录 / docs / plans / 2026-08-14-feat-x-retrieve-judge-retry-plan.md
1 # Retrieve-Judge-Retry for X Search
2
3 **Date:** 2026-08-14
4 **Status:** Completed
5
6 ## Problem Statement
7
8 X search results become off-topic when multi-word search queries are phrase-quoted. The Rome failure (2026-08-14) demonstrated this:
9
10 1. Planner generated `search_query: "Rome Italy"` (phrase-quoted)
11 2. Phrase-quoting returned thin hits with engagement bait (pretty-cities, geopolitics accounts)
12 3. `entity_extract` ranked off-topic handles (PrettyCitiesX, visegrad24) by frequency
13 4. `pipeline.py` promoted those handles to the FROM lane
14 5. FROM lane filled the 40-slot X budget with off-topic timelines
15
16 ## Solution
17
18 Implement retrieve-judge-retry for X search:
19
20 1. **Query Compilation (R2):** X now uses `raw_topic` like Reddit/YouTube, not the planner's `search_query`
21 2. **Fanout Queries (R3):** Multi-word topics use unquoted AND as first variant; phrase-quote only for proper names
22 3. **Corpus Judging (R7):** New `x_judge.py` module evaluates corpus on-topic ratio after retrieval
23 4. **Retry (R1):** If off-topic flood detected (ratio < 0.4), retry ONCE with simplified keyword query inside the X stream (not `_retry_thin_sources`)
24 5. **Split FROM Promotion (R4):**
25 - Explicit handles (--x-handle): always FROM, no AND topic
26 - Extracted handles: FROM only if ≥2 on-topic hits AND ≥50% ratio, and they DO AND the topic
27 6. **First-Party Exemption (R5):** Floor immunity stays conservative (explicit handles only)
28 7. **Status Reporting (R6):** Off-topic floods emit artifact warning, not `record_failure(PARTIAL)`
29
30 ## Implementation Details
31
32 ### New Module: `x_judge.py`
33
34 - `judge_x_corpus(items, topic, ranking_query)`: Returns on_topic_ratio, is_off_topic_flood, on_topic_items, handle_stats
35 - `promotable_handles(items, topic, extracted_handles, explicit_handles)`: Returns (explicit_promotable, extracted_promotable)
36 - `should_retry_x_search(items, topic, depth)`: Returns True if retry warranted
37 - `prune_off_topic_items(items, topic)`: Returns only on-topic items
38
39 ### Key Changes
40
41 - `grok_x._fanout_queries()`: No phrase-quote for place/disambiguation strings
42 - `grok_x._is_proper_name()`: Detects title-cased proper names for phrase-quoting
43 - `grok_x.search_handles()`: Added `and_topic` parameter (default False)
44 - `pipeline._fetch_x_backend()`: Accepts query directly, not subquery
45 - `pipeline._retrieve_stream_impl()`: X source uses raw_topic, judges corpus, retries if needed
46 - `pipeline._run_supplemental_searches()`: Uses `promotable_handles` for split FROM logic
47
48 ### Tests
49
50 - `tests/test_x_judge.py`: New test file for x_judge module
51 - `tests/test_grok_x.py`: Added fanout and and_topic tests
52 - `tests/test_pipeline_v3.py`: Updated fixtures to have promotable content
53
54 ## Success Criteria
55
56 - [ ] `_fanout_queries("Rome Italy")` has no `"Rome Italy"` variant
57 - [ ] `search_name("Peter Steinberger")` still phrase-quotes
58 - [ ] `search_handles(["steipete"], "topic")` does not AND topic by default
59 - [ ] `search_handles(["visegrad24"], "Rome", and_topic=True)` does AND Rome
60 - [ ] Explicit --x-handle always gets FROM lane
61 - [ ] Off-topic handles (visegrad24) not promoted to FROM lane
62 - [ ] On-topic handles (mamboitaliano__) promoted to FROM lane
63 - [ ] X source status is artifact warning, not PARTIAL failure
64 - [ ] All tests pass
65
66 ## Out of Scope
67
68 - No live Grok calls in tests
69 - No auth/doctor touch
70 - No collision lexicon (AS Roma / Odunze still appear; judge + ranking_query drop them)
71 - bird_x quote-preserving `build_topic_query` (follow-up if needed)
72
72 lines MARKDOWN