Skip to content

spec(retrieval): top-k is crowded by multiple rows of the same underlying entity #189

Description

@liplus-lin-lay

目的

#180 の事実2 を分割して実装対象にする。同一の実体が複数行として索引され、同一プールで枠を奪い合うため、top_k の数字ほど情報の幅が出ない。

前提(2026-08-01 再実測)

#182 / #183(日本語 sparse の修正)と #184 / #185(wiki カバレッジの修正)でプールの構成が二回変わったため、#180 記載の数字を取り直した。

dense 軸は当時と完全に同一

判断の記録は状態形式で書く / fusion: dense_only / rerank: false / top_k: 5:

rank type 実体 dense_score
1 diff skills/evolution-decision-structure-write/SKILL.md (2df5052) 0.585
2 diff docs/5.-Notifications.md (4e4f22a) 0.577
3 diff docs/F.-Behavior-First.md (4e4f22a) 0.573
4 doc skills/evolution-decision-structure-write/SKILL.md 0.572
5 diff skills/evolution-decision-structure-write/SKILL.md (601aa38) 0.571

5 枠中 3 枠が同一ファイル。スコアの分離も 0.585〜0.571(幅 0.014)で当時のまま。事実1 の修正は事実2 に影響していない。

既定経路(rrf + rerank)での重複率

top_k: 10 で 2 クエリ。

subagent への委譲はいつ必須になるのか:

  • skills/task-subagent-delegation/SKILL.md が doc と diff(45deef8) で 2 枠
  • 同一 commit 45deef8 の別ファイルがさらに 1 枠
  • 同内容の .claude/skills/task-subagent-delegation/SKILL.md(github-webhook-mcp 側のコピー)が 1 枠

wiki sync sidebar integrity check:

  • 同一 commit afae460 が 2 枠
  • issue #1317 と PR #1318(同一の作業単位)が 2 枠
  • docs/Decision-Structure.md が別 commit の diff で 2 枠

実質的に独立した情報は 10 枠中 6 前後。

重複の 3 軸

内容 備考
(a) 版の重複 同一ファイルが doc 1 行 + 複数 commit の diff 行として並ぶ #180 が記録した軸
(b) 作業単位の重複 同一の作業が issue / PR / review / comment に分かれて並ぶ #180 未記載
(c) 配布の重複 Li+ source が各 user repo の .claude/ に複製されており、同内容が repo 数だけ存在する #180 未記載。repo 横断検索で顕在化する

(a) は commit 差分をバックアップ方式で保持する設計(容量節約)の裏側なので、索引をやめる方向では解けない。表示段での集約が筋。

同一実体の定義(決定)

同一実体 = その行が指している対象(referent)であって、その行を生んだ作業(event)ではない。

集約が情報を隠すのは、独立した対象を 1 枠に畳んだときだけ。同じ対象の複数の版を畳んでも、対象の数は減らない。一方「作業」で畳むと、1 つの commit が触った別々のファイルが 1 枠になり、実際に独立した対象が隠れる。したがって畳む軸は referent 側に限る。

この定義から各軸の扱いが決まる:

対象 entity key 畳む 理由
doc 行 + 同一ファイルの diff 行(複数 commit) file:{repo}:{doc_path ?? file_path} する 対象は 1 つのファイル。版が複数あるだけ
issue 本体 + その issue_comment thread:{repo}:{number} する 対象は 1 つの issue
PR 本体 + その pr_review / pr_review_comment thread:{repo}:{number} する 対象は 1 つの PR
同一 commit が触った別々のファイル しない 対象は 2 つのファイル。commit は事象であって対象ではない
issue #1317 と PR #1318 しない 対象は 2 つ。かつ両者を結ぶ情報(Closes #N)は索引に無く、入れるには索引側の変更が要る(制約違反)
repo 横断の複製(軸 (c)) 本 issue では扱わない 下記

軸 (c) を本 issue から外す理由

skills/X/SKILL.md.claude/skills/X/SKILL.md を同一と判定するには、内容 hash を索引に持たせるか、パスの正規化ヒューリスティクスを置くかのどちらかが要る。前者は「索引側は変更しない」制約に反し、後者は本当に別物のファイルを誤って畳む。加えて本文が既に書いているとおり、配布先が古い場合はその差異自体が情報になる。判定材料が揃っていない軸なので、別 issue に切り出す。

代表の選び方

融合・rerank 後の順位が最上位の行を代表にする。 最新版を代表に固定しない。

これが「いつ変わったか」を問うクエリへの答え。そのクエリでは該当する古い diff が最上位に来るので、代表がその diff になる。最新版固定だと答えそのものが消える。

返却形式

代表 item に畳んだ分の参照を付す。捨てない。

{
  ...(代表 item の既存フィールド),
  "same_entity": {
    "count": 3,
    "others": [
      { "type": "diff", "url": "...", "commit_sha": "601aa38", "updated_at": "...", "score": 0.571 },
      ...
    ]
  }
}

same_entity は畳んだ行が 1 件以上ある場合のみ付ける(畳まれていない item には出さない)。

top_k は代表の数で数える。呼び出し側が 10 を要求したら独立実体 10 件が返る。

実装位置

src/mcp.ts。集約は filtered.slice(0, requestedTopK)手前に置く。rerank 有効時は既に top_k × 5(最大 50)で overfetch しているので、集約してから trim すれば追加の過取得なしで top_k を満たせる。rerank: false の経路も同じ順序に揃える。

制約

受け入れ条件

  • 上記 2 クエリで、top_k: 10 に対し独立した実体が 9 以上になる
  • 「いつ変わったか」を問うクエリで、該当する diff が代表として残ることを確認する(集約が情報を隠していないことの負の対照)
  • 同一 commit が触った別々のファイルが畳まれないことがテストで固定されている(referent と event の線が実装で守られていることの固定)
  • 重複率を測る回帰テストがある

想定変更箇所

  • src/mcp.ts(融合後・trim 前の集約段)
  • 対応するテスト
  • docs/same_entity フィールドと集約の定義)

参考

Metadata

Metadata

Assignees

No one assigned

    Labels

    done役目完了、orchestration (review / merge / close) フェーズ待ちready本文が実装開始できる形まで収束している状態。ただし更新は継続可能specLi+の挙動に影響する仕様・ポリシー・定義

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions