siddhm11 Claude Opus 5.5 commited on
Commit
ce8a24c
Β·
1 Parent(s): 9856058

docs: log the 2026-09-30 multi-interest and starter-feed decisions

Browse files

Doc 06 changelog entry for the four approved changes, with the replay
measurements behind them; CLAUDE.md sections 3.3, 3.5 and the quick
reference, and the CURRENT-STATE contract rows, updated to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

CLAUDE.md CHANGED
@@ -106,7 +106,8 @@ If you find `alpha_long = 0.10` anywhere in code or config, it is a bug from doc
106
  - Algorithm: **Ward hierarchical agglomerative** via `scipy.cluster.hierarchy.ward`.
107
  - Code lives in: `app/recommend/clustering.py`.
108
  - **L2-normalize embeddings BEFORE Ward, then use Euclidean distance.** Cosine Ward via sklearn is mathematically not Ward (Murtagh and Legendre 2014). L2-norm + Euclidean is monotonically equivalent to cosine and gives the intended behavior. This normalization is already in the code.
109
- - **No fixed K.** Cut the dendrogram by adaptive gap-based threshold (see `_adaptive_threshold()`). Cap at `K_max = 7` (currently; doc 06 says `K_max = 20` for heavy users β€” raise this when users exist).
 
110
  - **Medoid, not centroid.** Medoid = arg min over cluster members of sum of squared distances. Cache medoid paper IDs. This is implemented in `_find_medoid()`.
111
  - **Hungarian-match cluster IDs across reclusterings** β€” implemented via `stabilize_cluster_ids`.
112
  - Recompute on each feed request currently (not nightly batch β€” no batch job infrastructure yet).
@@ -144,7 +145,7 @@ If you find `alpha_long = 0.10` anywhere in code or config, it is a bug from doc
144
  took a correct 61/39 pool and selected 39/1, dumping 60 minority papers into the
145
  exploration pool where no later quota stage could recover them. See doc 06,
146
  2026-08-21.
147
- - Exploration injection: 2 serendipitous papers per feed. Code in `app/recommend/diversity.py` via `inject_exploration()`.
148
  - Quota (3.1) handles cross-cluster diversity. MMR handles within-quota redundancy.
149
  This is a statement about WHERE each runs, not just what it is for.
150
  - Do NOT use DPPs in v1.
@@ -479,7 +480,7 @@ If a topic is too large for a 06 changelog entry, create `docs/research/07-[topi
479
  | Reranker? | Personalized heuristic by default; optional LightGBM/auto. |
480
  | Latency budget? | <30ms end-to-end (compute only; metadata I/O excluded). |
481
  | Cold start? | Hybrid: categories + seed papers + popularity fallback (Phase 5 complete). Author import removed from current roadmap. |
482
- | When does behavioral take over? | ~10 saved papers. Currently activates at 5 (clustering) / 3 (EWMA) / 1 (BEST_SCORE). |
483
  | When to add CF? | 500+ users (Phase 9). |
484
  | Current status? | Local implementation and validation are distinct from deployed status. See `docs/CURRENT-STATE.md`. |
485
  | ArXiv ID type? | String. Always. `dtype=str` in pandas. |
 
106
  - Algorithm: **Ward hierarchical agglomerative** via `scipy.cluster.hierarchy.ward`.
107
  - Code lives in: `app/recommend/clustering.py`.
108
  - **L2-normalize embeddings BEFORE Ward, then use Euclidean distance.** Cosine Ward via sklearn is mathematically not Ward (Murtagh and Legendre 2014). L2-norm + Euclidean is monotonically equivalent to cosine and gives the intended behavior. This normalization is already in the code.
109
+ - **No fixed K.** Cut the dendrogram by adaptive gap-based threshold (see `_adaptive_threshold()`). Cap at `K_max = 7` (currently; doc 06 says `K_max = 20` for heavy users β€” raise this when users exist) and at ceil(N/2) (`AVG_CLUSTER_SIZE_FLOOR = 2`). Take the finest cut within the cap that leaves no singleton; never fold a split pair into another cluster (doc 06, 2026-09-30).
110
+ - **From 2 saves, not 5.** Below `MIN_PAPERS_FOR_CLUSTERING = 5` each save is its own interest (`per_save_clusters`) on the same quota/MMR path. Do not average unrelated early saves into one vector; Tier 2 EWMA is only the fallback.
111
  - **Medoid, not centroid.** Medoid = arg min over cluster members of sum of squared distances. Cache medoid paper IDs. This is implemented in `_find_medoid()`.
112
  - **Hungarian-match cluster IDs across reclusterings** β€” implemented via `stabilize_cluster_ids`.
113
  - Recompute on each feed request currently (not nightly batch β€” no batch job infrastructure yet).
 
145
  took a correct 61/39 pool and selected 39/1, dumping 60 minority papers into the
146
  exploration pool where no later quota stage could recover them. See doc 06,
147
  2026-08-21.
148
+ - Exploration: 2 serendipitous papers per page, drawn in `_build_page()`. The pool is starter papers from ticked interests that no cluster medoid covers (`_uncovered_interest_papers`), otherwise the far half of the reader's own candidates. Draws stay uniform so propensities hold.
149
  - Quota (3.1) handles cross-cluster diversity. MMR handles within-quota redundancy.
150
  This is a statement about WHERE each runs, not just what it is for.
151
  - Do NOT use DPPs in v1.
 
480
  | Reranker? | Personalized heuristic by default; optional LightGBM/auto. |
481
  | Latency budget? | <30ms end-to-end (compute only; metadata I/O excluded). |
482
  | Cold start? | Hybrid: categories + seed papers + popularity fallback (Phase 5 complete). Author import removed from current roadmap. |
483
+ | When does behavioral take over? | ~10 saved papers. Currently: 1 save = similar-to, 2–4 = one interest per save, 5+ = Ward clustering; EWMA single-vector is a fallback. |
484
  | When to add CF? | 500+ users (Phase 9). |
485
  | Current status? | Local implementation and validation are distinct from deployed status. See `docs/CURRENT-STATE.md`. |
486
  | ArXiv ID type? | String. Always. `dtype=str` in pandas. |
docs/CURRENT-STATE.md CHANGED
@@ -1,6 +1,6 @@
1
  # Current technical contract
2
 
3
- Updated: 2026-09-28. Deployed to the Hugging Face Space at `67cba21` on
4
  2026-09-28; the live checks are in DISCOVERY-PLAN.md (deployment record).
5
  Re-verify with `/healthz/*` rather than trusting this date.
6
 
@@ -19,10 +19,11 @@ and deployed. Never copy old corpus counts or benchmark results into current cla
19
  | Search | Original + optional rewritten query; dense shard fanout and independent FTS5; Zilliz without FTS; RRF; optional top-10 MiniLM; title/citation boost | `app/hybrid_search_svc.py`, `app/config.py` |
20
  | Missing encodings | Available FTS query forms still run; actual contributing arms determine `retrieval_mode` | `app/hybrid_search_svc.py` |
21
  | Search total miss | arXiv keyword fallback; no invented hits for unfamiliar concepts | `app/routers/search.py` |
22
- | Detailed interests | Saved papers -> long/short/negative EWMA + Ward medoids; parameters unchanged | `app/recommend/profiles.py`, `clustering.py` |
23
  | Multi-interest balance | Importance-weighted quota, within-cluster MMR, quota-bound served order | `fusion.py`, `app/routers/recommendations.py` |
24
  | Scoring | Personalized heuristic default; optional citation-trained LightGBM | `reranker.py`, `config.py` |
25
- | Cold start | Category popularity candidates; local per-category interleaving when sidecar exists; epsilon-greedy fresh block and deterministic recycled block | `app/discovery_svc.py`, recommendation router |
 
26
  | Refresh | Personalized candidate retrieval excludes last seven days of served papers; one exhaustion retry allows labelled repeats | recommendation router, `app/db.py` |
27
  | Paging | Cached ordered pool per query ID, bound to user; per-user lock includes serving/impression write in one process | recommendation router |
28
  | Preferences | Editable `/interests`; zero to eight known categories; no deletion of existing saves | `app/routers/onboarding.py` |
 
1
  # Current technical contract
2
 
3
+ Updated: 2026-09-30 (feed changes not yet deployed). Deployed to the Hugging Face Space at `67cba21` on
4
  2026-09-28; the live checks are in DISCOVERY-PLAN.md (deployment record).
5
  Re-verify with `/healthz/*` rather than trusting this date.
6
 
 
19
  | Search | Original + optional rewritten query; dense shard fanout and independent FTS5; Zilliz without FTS; RRF; optional top-10 MiniLM; title/citation boost | `app/hybrid_search_svc.py`, `app/config.py` |
20
  | Missing encodings | Available FTS query forms still run; actual contributing arms determine `retrieval_mode` | `app/hybrid_search_svc.py` |
21
  | Search total miss | arXiv keyword fallback; no invented hits for unfamiliar concepts | `app/routers/search.py` |
22
+ | Detailed interests | Saved papers -> long/short/negative EWMA. 2–4 saves: one interest per save; 5+: Ward medoids, K ≀ min(7, ceil(N/2)), finest singleton-free cut (2026-09-30) | `app/recommend/profiles.py`, `clustering.py` |
23
  | Multi-interest balance | Importance-weighted quota, within-cluster MMR, quota-bound served order | `fusion.py`, `app/routers/recommendations.py` |
24
  | Scoring | Personalized heuristic default; optional citation-trained LightGBM | `reranker.py`, `config.py` |
25
+ | Cold start | Balanced per ticked interest; citations per month over 24 months, every third slot from the corpus's last 3 months (needs refreshed citations to rank those); epsilon-greedy fresh block and deterministic recycled block | `app/discovery_svc.py`, `local_meta.py`, recommendation router |
26
+ | Exploration | 2 per page; from ticked interests no medoid covers, else the far half of own candidates | recommendation router |
27
  | Refresh | Personalized candidate retrieval excludes last seven days of served papers; one exhaustion retry allows labelled repeats | recommendation router, `app/db.py` |
28
  | Paging | Cached ordered pool per query ID, bound to user; per-user lock includes serving/impression write in one process | recommendation router |
29
  | Preferences | Editable `/interests`; zero to eight known categories; no deletion of existing saves | `app/routers/onboarding.py` |
docs/research/06-Deep-Research-Verdict.md CHANGED
@@ -450,3 +450,33 @@ needed before HF candidates can enter the live feed. Bound retries, invalidate
450
  changed-text embeddings, and reject stale/future candidates. Report source failures.
451
  **Action items:** Implemented locally with pipeline regressions. Deploy a durable
452
  worker and establish real-model/relevance evidence before an opt-in serving pilot.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
450
  changed-text embeddings, and reject stale/future candidates. Report source failures.
451
  **Action items:** Implemented locally with pipeline regressions. Deploy a durable
452
  worker and establish real-model/relevance evidence before an opt-in serving pilot.
453
+
454
+ ### 2026-09-30 β€” Multi-interest from two saves, finer interest cap, grounded exploration
455
+ **Decision:** The user approved four changes after a trace of their own interests
456
+ (NLP, CV, ML, AI, Robotics, Software Engineering) with production vectors:
457
+ 1. The multi-interest path (quota + per-cluster MMR) starts at 2 saves. Below 5,
458
+ each save is its own interest (`per_save_clusters`); Ward still starts at 5.
459
+ 2. `AVG_CLUSTER_SIZE_FLOOR` 4 β†’ 2, so K ≀ ceil(N/2) (still ≀ `K_max = 7`), and
460
+ `compute_clusters` takes the finest dendrogram cut at or below K that leaves
461
+ no singleton before the existing singleton merge.
462
+ 3. Exploration draws from ticked interests that no cluster medoid lists in its
463
+ categories. Otherwise it keeps the far-half-of-own-candidates pool.
464
+ 4. Tier 0 balances per ticked interest instead of per arXiv code. It ranks by
465
+ citations per month since publication and gives every third slot per
466
+ interest to a paper from the corpus's last three months.
467
+ **Supersedes:** Tier 2 EWMA single-vector search as the 3–4-save tier (it remains
468
+ the fallback); the soft cap of ceil(N/4); exploration drawn only from the reader's
469
+ own retrieval neighbourhoods; Tier 0 per-code round-robin by raw citations.
470
+ **Rationale:** Measured: averaging Llama 3 + Ο€0 + OpenHands returned generic
471
+ "LLM agents" surveys matching none of them. Seven saves across five interests
472
+ allowed only 2 clusters, and 12 across six allowed only 3 (5 at the new floor),
473
+ while single-interest readers are unchanged. Lowering the floor alone collapsed
474
+ a noisy 3 + 2 reader into one cluster (the singleton merge folded the split
475
+ pair into the majority), which the singleton-free cut fixes. Tier 0 held 0 of
476
+ 200 papers from 2026 and gave Computer Vision 22 of 200. Replay method and
477
+ figures are in the 2026-09-30 feed trace. There is no judged relevance set yet,
478
+ so these rest on the replay and on the invariants, not on an offline metric.
479
+ **Action items:** Implemented with regression tests. Refresh citation counts
480
+ (zero for every paper since 2025-06) and ingest papers after 2026-07-30, or
481
+ Tier 0's fresh lane has no quality signal. Build a small judged set before
482
+ tuning further; multi-medoid retrieval for loose clusters is deferred until then.