GrooveSeek

Semantic search over a Markdown knowledge base, served over MCP.

View the Project on GitHub alphabet-h/grooveseek

24. 強制再構築が索引を空にする前に embedding endpoint を試す

背景と課題

強制再構築 — groove index --force と、MCP ツール rebuild_index の force: true — は、まず索引を空にする (grooveseek/src/db/meta.rs の reset_for_model。grooveseek/src/indexer.rs の reset_and_resolve_context_mode から呼ばれる)。reset は自前の transaction で走って commit する。文書の embedding は その後、1 ファイルずつ行う。

FastEmbed ならこの順序で困らない。モデルは reset の前に読み込まれ、プロセス内で動く。 provider = "openai-compatible" (ADR-0022) では、provider は endpoint に触れずに作られ、最初のリクエストは reset が commit した 後に出る。api_key の誤りや失効 (401)、endpoint の停止、rate limit (429)、別の長さの ベクトルを返すようになったサーバは、どれもその最初のリクエストで表に出る — 索引が 既に空になった後に。コマンドは失敗し、後に残るのは文書が 1 件も無い索引になる。

MCP 経路ではさらに悪い。rebuild_index は HTTP の port に届く相手なら誰でも呼べ、 GrooveSeek は設計上認証を持たない。endpoint が断り続ける間、呼び出し側は配信中の 索引を何度でも空にできる。

ADR-0022 は GrooveSeek は endpoint を probe しない と決めた。理由は索引を開く ことについてだった: 起動と Config::validate が決定的でなくなる、停止中の service が 設定エラーに見える、索引を開く前にリクエストがマシンの外へ出る。ここでの問いはもっと 狭い: 知識ベースを endpoint へ送ることが目的のコマンドは、置き換えようとしている 索引を壊す前に、endpoint が応えるかを確かめてよいか。

判断の軸

検討した選択肢

  1. reset の前に、固定の文字列 1 件を document 側で embedding する。強制再構築の ときだけ。 採用。

  2. 新しい索引を古い索引の横に作り、成功したら差し替える。 今は見送る。再構築は ファイルごとに commit するので、途中で止まった実行も終えた分は残る。実行全体を 1 つの transaction にすると再構築の間ずっと書き込み lock を握って watcher を止め、影の database は新しい次元の vec_chunks を作る間ディスクを 2 倍使う。保証としてはこちらが 強い — 途中で落ちる endpoint も覆う — が、この問題に要る変更よりずっと大きい。

    再考する条件: 別の理由で再構築が build-then-swap に移ること。そのとき probe は守る ものが無くなり、外してよい。

  3. 最初の本物のバッチが embedding できるまで reset を遅らせる。 退けた。reset の 後には空の索引を前提にした書き込みが続き (context mode、コードチャンクの budget と policy、宣言フィールドの集合)、最初の embedding はファイルを読んでチャンクにした後、 ファイルごとのループの中にある。破壊的な段階をそのループへ移すと、reset が単純に 保とうとしているコードに散らばる。節約できるのは小さなリクエスト 1 回だけ。

  4. provider を作るすべての場所で probe する — serve の起動、validate、 すべての index。ADR-0022 の理由で退けた。ここでその理由は何も変わらない。強制で ない index は何も壊さないので、そこでの失敗の代償はその実行 1 回分で済む。

  5. そのままにし、--force には動く endpoint が要ると書く。 退けた。失われたことは 次の検索が何も返さないまで気付かれず、MCP 経由ならどの呼び出し側でも繰り返せる。

決定

強制再構築は、索引を reset する前に embedding provider へ probe を 1 回送る。 それ以外は probe しない。

結果と代償

参考