15. Recall Pre-Gate Node (R9 Recaller)
基本情報
- 英語名 : Recall Pre-Gate Node(R9 Recaller)
- 位置 : triage と abc_screen の間。関連 ADR を最大 5 件想起する非破壊 actor
- 状態 : Production
- 実装 :
drp/src/nodes/recall_pre_gate.ts/ pure helpersdrp/src/nodes/recall_pre_gate_pure.ts/ 型 SSoTdrp/src/contracts/recall_pre_gate.ts- プロンプト設計: (該当なし)
- プロンプト SSoT:
prompts/production/recall-pre-gate/prompt.md/ output schemaprompts/production/recall-pre-gate/output_schema.json- テスト : (該当なし)
- 基盤 ADR : RQ-107 Must-have #1 R9 Recaller (synthesis は docs/research/ 配下)
- 変更履歴 : このページ
エレベーターピッチ
- これは何? : 起案を読んで「過去に似た決定をした既存 ADR」を最大 5 件自動で想起し、本文や審査の参照リストに添える proactive な想起 actor。
- だれのため? : 起案者(自分が知らない過去 ADR を参照できる)と、後段の整合性チェック・並列レビュー(citation を読んで重複・前提・矛盾・補完を精査できる)。
- なにが起きる?: 関連 ADR の top-5 を citation として後段へ渡すだけ。判定や差戻しは絶対にしない。embedding 失敗時は空配列で safe degrade。
- 譲れない一線 : rejected は絶対に立てない。候補集合の所属検査と逐語照合で hallucination を必ず除去する。
- だから : 過去 ADR の重複・前提・矛盾・補完の見落としが、起案者の記憶と検索努力に依存せず入口で機械的に拾われる。
1. 役割と位置づけ
起案者は自分が知らない過去 ADR を参照できない。本ノードは triage 通過直後に既存 ADR から関連のあるものを top-5 まで自動で挙げ、下流の整合性チェック・並列レビューに citation として渡す。
- 解決する課題: 過去 ADR の重複・前提・矛盾・補完の見落とし。起案者の記憶と検索努力に依存していた想起を、入口で機械的に強制する。
- 設計思想: proactive な想起 actor。判定や棄却は行わず、後段の
consistency/parallel_reviewが citation を読んで精査する。rejected は絶対に立てない。 - 二段構成 (ADR-0142 / ADR-0157 と同型): embedding で top-N 候補を事前絞り込み → LLM が top-5 citation を抽出 → コードが逐語照合 + 候補所属検査で hallucination を除去。
- 由来: RQ-107 Must-have #1 R9 Recaller。
2. フロー図
flowchart LR
T[triage] -->|needsAdr=true| R[recall_pre_gate]
R -->|RECALL_ENABLED=false| ABC1[abc_screen
citations=空]
R -->|候補 0 件 / embedding 失敗| ABC2[abc_screen
citations=空]
R -->|LLM 抽出 + 逐語照合| ABC3[abc_screen
citations=top-5]
どの分岐でも次は abc_screen。差戻しはしない。
3. トリガー条件
| エントリポイント | 起動グラフ | 説明 |
|---|---|---|
POST /draft | buildGraphWithWebhook | triage 通過 (needsAdr=true) 直後に実行 |
POST /chat/run | buildGraph | webhook を除く full pipeline |
スキップ条件:
- env
RECALL_ENABLED='false'で素通し (recallCitations=[]を返す)。 - 起案テキストが空 (
applicantInput.trim() === '') ならスキップ。 - Vectorize / embedding 失敗時は safe degrade で空配列。
- 候補集合が 0 件のときは LLM を呼ばず空配列。
再実行ガードは入っていない (citation は安全に再計算可能・state は単純上書き)。
4. 入力 (State)
| State フィールド | 型 | 必須 | 用途 |
|---|---|---|---|
title | string | 任意 | 起案タイトル |
context | string | 任意 | 背景・目的 |
options | string | 任意 | 検討した代替案 |
入力は buildRecallQueryText({ title, context, options }) で 1 本のテキストに組み立てる。Phase a SSoT の applicant_input 形式に合わせる。
外部入力:
- Vectorize binding (embedding 検索) — 環境変数経由
- KV (プロンプト本文 + LLM パラメータ) —
loadPrompt/loadLlmParams
5. 処理ロジック
0. RECALL_ENABLED='false' なら recallCitations=[] で素通し
1. applicantInput = buildRecallQueryText(state)
- 空文字なら recall_pre_gate_skipped ログ + 空配列で終了
2. embedQueryText(env, applicantInput) で起案文を embedding 化
3. queryRecallCandidates(env, vec, RECALL_TOP_N=20) で Vectorize 上位 20 件を絞り込み
- embedding / Vectorize が throw した場合は safe degrade で空配列
- 候補 0 件なら recall_pre_gate_no_candidates ログ + 空配列で終了
4. プロンプト (recall-pre-gate) と LLM パラメータを KV から取得
5. claude-opus (MODELS.consistency) を temperature 0 / seed 42 で 1 call
- user content: 【applicant_input】 + 【candidate_adrs】 (候補 20 件の整形)
- 期待出力: { citations: [{ adr_id, relevance, relevance_reason }] } 0〜5 件
6. safeParseLlmJson でフォールバック付き parse
7. filterValidCitations() がコードで検証
① adr_id が候補集合に含まれているか (hallucinated id を除去)
② 重複除去
③ relevance_reason の逐語照合 (NFKC + 空白畳み)
- 起案本文 OR 候補集合の summary のいずれかに含まれていれば valid
④ relevance が enum (high / med / low) でなければ 'low' へ正規化
⑤ 先頭 5 件で打ち切り (schema maxItems=5)
8. state.recallCitations へ verified を上書き
正規化の方針: NFKC + 空白畳み (全角/半角・合成/分解を吸収する方向)。ADR-0142 / ADR-0157 と方針統一・
#safe寄り。詳しい正規化はnormalizeForCitationMatch()にある。二段照合 (本文 OR summary): relevance_reason は起案本文に書かれた語と、候補 ADR の summary に書かれた語のどちらでも valid とする。proactive 想起者の役割上、起案者が書いていない概念でも summary 側にあれば拾える。
6. LLM 設定
| 項目 | 値 | 根拠 |
|---|---|---|
| モデル | claude-opus (MODELS.consistency) | 候補集合からの意味的選定・cross_validation と同モデルプール |
| temperature / seed | 0 / 42 | 同一起案には同一 citation 集合 (KV recall-pre-gate params で上書き可能) |
| 期待出力 | JSON object { citations: [...] } 0〜5 件 | output_schema.json で形式強制 |
| Vectorize 絞り込み | top-N = 20 | LLM に渡す候補を 20 件に絞る |
| プロンプト | KV recall-pre-gate (ADR-0085 lifecycle) | フォールバックはノード内 FALLBACK_PROMPT |
7. 副作用
なし (state 更新のみ)。
- Vectorize binding への query 1 回・KV からのプロンプト / params 取得 (
loadPrompt/loadLlmParams) は副作用ではないが外部 IO。 - GitHub API / Webhook / D1 書込には触れない。
構造化ログ:
event: 'recall_pre_gate_skipped'— 入力が空event: 'recall_pre_gate_embedding_failed'— embedding / Vectorize 例外event: 'recall_pre_gate_no_candidates'— 候補 0 件event: 'prompt_loaded'— KV / FALLBACK の出所event: 'recall_pre_gate_citations_filtered'— LLM 出力件数 / 残った件数 / dropped 件数
8. 出力 (State)
{
recallCitations: RecallCitation[], // 0〜5 件
}
RecallCitation の中身:
interface RecallCitation {
adr_id: string; // 'ADR-XXXX' 形式 (pattern ^ADR-\d{4}$)
relevance: 'high' | 'med' | 'low';
relevance_reason: string; // 起案本文 or 候補 summary の逐語引用 1-2 行
}
rejectedは どの分岐でも絶対に立てない。- 後段 (
consistency/parallel_review) がrecallCitationsを読んで精査する。 - 0 件の場合 (env OFF / 入力空 / 候補 0 / embedding 失敗) は空配列を返して通過。
9. 分岐 (次ノード)
graph.ts の edge:
.addConditionalEdges('triage', (s) => s?.needsAdr ? 'recall_pre_gate' : END)
.addEdge('recall_pre_gate', 'abc_screen')
| 状態 | 次ノード |
|---|---|
| 常に | abc_screen |
無条件 edge。差戻しを起こさないため addConditionalEdges ではない。
10. エラー時の挙動
| 失敗パターン | 挙動 | 影響 |
|---|---|---|
| embedding / Vectorize 例外 | recallCitations=[] で safe degrade | 後段の citation 参照は 0 件として動作 |
| 候補 0 件 | LLM を呼ばず空配列で終了 | コスト節約 |
| LLM 例外 | safeParseLlmJson のフォールバック値 { citations: [] } を返す | 例外伝播せず 0 件で通過 |
| LLM が JSON 以外を返す | フォールバック値で 0 件 | structured error log |
| LLM が候補集合に無い adr_id を返す | filterValidCitations が除去 | hallucinated id を弾く |
| relevance_reason が逐語照合できない | filterValidCitations が捨てる | 幻覚を citation から隔離 |
設計方針は fail-safe-skip (どのエラーでも 0 件で通過させる)。proactive 想起の失敗は後段審査の精度を直接落とさない。
11. 既知の弱点・運用注意
- embedding index 更新の遅延: 新規 ADR が受理されてから Vectorize へ ingest されるまで、その ADR は候補に出てこない。ingest pipeline (PR-2 の Vectorize binding + ADR ingest 経路) の monitoring が要る。
- top-N=20 の代表性: Vectorize の top-20 に入らない関連 ADR は LLM が見ない。意味の遠い関連 (語彙が違うが論点が同じ) は取りこぼしうる。
- 逐語照合の faithful but not factual 問題: relevance_reason の文字列実在は保証するが、関連の論理的妥当性は担保しない。後段 (
consistency/parallel_review) が精査する前提の役割分担。 - kill switch の安全側影響:
RECALL_ENABLED='false'で素通してもrecallCitations=[]になるだけ。下流 gate は state を読まないと挙動が変わらないため、誤動作時の影響が小さい。 - fail-open のトレードオフ: LLM エラー時に 0 件で通過するため、
recall_pre_gate_embedding_failed等のログが頻発すると想起機能が実質無効化される。週次集計で監視する。 - コスト累積: claude-opus を毎 run で 1 call 呼ぶ。triage で
needsAdr=trueを通過した起案のみが対象なので極端には膨らまないが、月次で観測する。
12. テストケース
- pure helpers の unit test:
recall_pre_gate_pure.tsのnormalizeForCitationMatch/filterValidCitationsを fetch/env/LangChain 非依存で test (vitest pool-workers 安全)。 - 本体 (
recall_pre_gate.ts) の test: LangChain を import するため workerd プールでは起動できない。pure helpers の re-export 経由で間接 test。 - golden eval: prompt-cicd フローで PR ごと CI ゲート化。citation 件数 / relevance 分布 / 候補不在 adr_id 検出率を追跡。
実行: prompt-cicd フローの一部。詳しくは /Users/ts_kuma/projects/bizlp/doc/prompts/production/recall-pre-gate/ 配下の eval セット。
13. 過去の設計判断ログ
| 日時 | 変更 | 経緯 |
|---|---|---|
| 2026-06 | RQ-107 Must-have #1 R9 Recaller の synthesis 採択 | 過去 ADR の重複・前提・矛盾・補完の見落としへの対策 |
| 2026-06 (Phase b-1 PR-1) | state Annotation recallCitations のみ追加 | graph 未配線・常時 default [] で安全に投入 |
| 2026-06 (Phase b-1 PR-2) | Vectorize binding + ADR ingest 経路を整備 | 候補絞り込みの基盤を準備 |
| 2026-06-21 (Phase b-1 PR-3) | 本体 + graph 配線 + RECALL_ENABLED kill switch | recall_pre_gate ノードを triage → abc_screen 間に挿入。migrate-v17 で telemetry 列追加 |
14. 関連リンク
- 前ノード: 01_triage.md
- 次ノード: 13_abc_screen.md
- State 定義:
drp/src/state.ts§ Webhook 直前recallCitations - 型 SSoT:
drp/src/contracts/recall_pre_gate.ts - pure helpers:
drp/src/nodes/recall_pre_gate_pure.ts - embedding helpers:
drp/src/lib/recall_embedding.ts - migrate:
drp/migrate-v17-recall-citations.sql - 関連 ADR:
- 基盤研究: RQ-107 Must-have #1 R9 Recaller (synthesis は docs/research/ 配下)