コンテンツにスキップ

想起のプロセス v0 — 設計

翻訳について: 正本は英語版です。

Status: 想起の記録 (§1) とループの基本形 (§2) は 2.6.0a7 でリリースされています。 想起のプロセス、 Cued Recall、 想起の記録 の最初の段です。 v0 は 2 つの部分からなり、この順で作ります。まず記録を作り、何かがそれに頼る前に計器として検定します。 次にループの基本形を作ります。

0. v0 が加えるもの

  • 想起の記録。 求められた時に、recall と reconstruct は、各段がどの行を残し、落とし、並べ替えたか、 そしてその理由の記録を返します。入るのは参照とスコアだけで、本文は入りません。求められなければ何も変わりません。
  • 時期の手がかり。 何かがいつ起きたかをおぼろげに覚えている呼び出し側は、それを伝えられます (time_cue)。 確かさは sure・likely・vague のいずれかです。サーバーはその期間を、他のすべてに加えて探し、 そこで見つかった行を、上限の決まった段数だけ上げます。誤った手がかりが招く損は、その上限付きの移動と L 行の別枠までです。 手がかりが無ければ何も変わりません。
  • 1 回の見直し。 期間の中に何も無ければ、サーバーは同じ呼び出しの中で期間を一度だけ広げて探し直します。

ループを使った recall は、どの方針で動いたかを記録するので、後から再生して原因を特定できます。

1. 想起の記録

1.1 求め方

recall に trace: true が加わります。reconstruct には同じ名前の引数が既にあります。reconstruct が記録を 求められた時は、内部で行った recall の記録も trace.recall として返します。記録は応答の中に入れて返します。 v0 はサーバーに保存しません。保存したい呼び出し側 (ベンチマークのハーネスなど) が保存します。

記録には保存された本文は入りません。参照・順位・スコア・理由だけです。記録が手元の外へ出る時に何を含めてよいかは、 別に決めます。

1.2 形式 (trace_version 1)

欄 中身
trace_version 1。既存の欄の意味が変わる時だけ上げる。欄を足すだけなら上げない
policy {scoring, process}: その呼び出しが動いたスコアリングの版と想起のプロセスの方針 (手がかり無しは single-pass-v0、有りは cued-v0.3。§2.11 より前は cued-v0.2、§2.10 より前は cued-v0.1、§2.8 より前は cued-v0)
server_version 答えたサーバーの版
scope 解決後の agent_id・project_id・channel・source_id
request limit、想起の深さ、deep、融合方式、confidence の並べ方、事前分布の設定、エピソードペナルティが有効か、渡された time_cue
config 埋め込みの方式とモデル、走査窓・reach・far リストの上限、融合ゲートと autocut が有効か
arms 検索器ごと (近いベクトル・遠いベクトル・エピソードの全文検索・記憶のキーワード・ブロック・手がかりの検索器) の {ref, rank, raw}、深さまで
fusion 候補ごとの融合スコアと、各検索器の寄与
scoring 効いた場合の、行ごとのエピソードペナルティの係数と事前分布の重み
gate 信号、較正済みの閾値か heuristic の下限とどちらが効いたか (origin)、プールの大きさ、候補ごとの判定 (通ったか、理由付きで落ちたか: below_gate・profile_small_pool・unscored_volume)、gate_fallback が起きたか
autocut 起きたか、どこで切ったか、何を落としたか
order 件数で切る前の最終の並びと、件数で落ちた ref
reservation 確保された別枠 (ブロック到達・時期の手がかり) で入った行
stages ループの段ごとに 1 件: その段が何を探し、なぜ次の段を始めたか
suspected 実行中にループが疑った失敗と、その段、取った手
coverage 問いのどの部分を返した記録が含むか: 部分 (位置と種類)、各部分を含む ref、どの記録も含まない部分 (§1.5)
timing_ms 段ごとの時間

1.3 疑いの失敗コードと確定の失敗コード

失敗の分類 は 2 通りに使い、その 2 つを分けておきます。

  • 疑い: recall の中。ループは自分が失敗したかを知りえません。比べる正解を持たないからです。見えるのは兆候だけで、 何を疑ったか (例: 手がかりの期間に候補が無い時の CANDIDATE_MISS) と、それにどう対処したかを記録します。
  • 確定: 後から、サーバーの外の道具が記録と既知の正解を突き合わせて付けます。答えの記録がどの検索器にも無ければ CANDIDATE_MISS、ゲートか autocut が除いたなら FILTER_DROP、通ったが件数の切れ目より下なら (または block の検索器が届いたが、 その検索器のために取っておいた別枠より下なら) RANKING_MISS、返ったが使われなかったなら証拠や読み手のコード。 道具は benchmarks/recall_trace_confirm.py です。

2 つを分けておくと、ループ自身の判断を測れます。疑ったことが、確定したこととどれだけ一致したか。確定した失敗の 履歴が、サーバーの挙動を自動で変えることはありません。方針の変更は、新しい方針の版を伴う、見直された変更です。

1.4 記録が使える状態になる条件

記録は計器です。その主張は「失敗した recall を、記録だけから段に帰属できる」ことで、何かがそれに頼る前に検定します。

  1. 故意の欠陥を正しく名指す。 検索器を 1 つ止めれば CANDIDATE_MISS、ゲートを極端に上げれば FILTER_DROP に ならなければなりません。セッションごとにエピソードを持つストアでエピソードペナルティを有効にすれば、 RANKING_MISS が増えなければなりません。手作業で見つけたペナルティの効き方そのものです。
  2. 手作業の帰属を再現する。 エピソードペナルティの効き方を突き止めた分析は、その場限りのスクリプトで行いました。 記録と確定の道具は、記録から同じ帰属に至らなければなりません。
  3. 求められなければ何も変えない。 挙動の golden がこれを固定します。記録を求めた時の費用は実測し、recall 自体の 時間の 5 分の 1 以内を目標にします。

1.5 被覆台帳

記録を求めた recall と reconstruct は、問いのどの部分を返した記録が含むかも記録します。記録するだけです。 答えが確定した後に計算し、想起の中でこれを読むものはありません。答えがまだ押さえていないものを取りに行く、 ループの後の段のための計器です。実際の利用で数えると、どの返した記録も含まない部分を持つ問いがどれくらいあるかが分かります。

  • 部分は問いの語で、依存なしに文字の種類で切ります: 2 文字以上の漢字の連続、2 文字以上のカタカナの連続、 2 文字以上の英数字の識別子 (名前、2.5.5a1 のような版番号、bug-218、#354、パス)。ひらがなの連続は助詞や 語尾なので部分にしません。問いは先に正規化し (NFKC、次に小文字)、同じ部分は 1 回だけ数えます。部分は最大 32 個です。
  • 被覆は、各記録の保存された全文に部分が文字列として含まれるかで判定します。応答に載るプレビューではありません。 recall では返した行、reconstruct では返した項目が引く記録が対象です。
  • 本文は載せません。 部分は正規化した問いの中の位置と種類で、記録は ref で記録します。問いを持つ呼び出し側は、 部分を normalize(question)[start:end] で取り出せます。
欄 内容
normalization nfkc-lower
parts [{span: [start, end], kind}]。kind は kanji・katakana・identifier のいずれか。初めて現れた順
covered_by 部分ごとに、その部分を本文に含む ref。応答が記録を並べた順
uncovered どの記録も含まない部分の番号
records 読んだ記録の数
parts_omitted 問いの部分が 32 個を超えた時だけ: 外した数

台帳を作れなかった時、coverage は {"error": <例外の型>} になり、呼び出しは台帳が無い時と同じに答えます。 費用は timing_ms.coverage です。

言わないこと。押さえられていない部分は、答えが無いことの証拠ではありません。記録が同じ事実を別の言葉で書くことが あり、英語では 2 文字以上の単語がすべて部分になり、よく使う語も含まれます。非公開の問いの組での実測: 答えの根拠が 返った問いでは、この取り方の語は、平均して部分の 82% が返した記録の中に見つかりました。最初の設計だった珍しい 3 文字組は 8% で、日本語ではほとんどが語の境目をまたぐ断片だったためです。同じ実測で、押さえられていない部分は、 答えが記憶に無い問いと有る問いを見分けませんでした。台帳は「答えが無い」ことの信号ではありません。

2. ループの基本形

2.1 時期の手がかり

time_cue = {
  "after": "2026-08-01", "before": "2026-08-31"      # 絶対。どちらか片方を省いてもよい
    または
  "ago": {"unit": "days" | "weeks" | "months", "value": 3} | "long_ago",   # 今からの相対
  "confidence": "sure" | "likely" | "vague"                             # 必須
}

引数の名前が time_cue なのは、reconstruct が連想記憶の層の実体を指して「cues」を既に使っているからです。 確かさは数値ではなく 3 値の語です。0 から 1 の数値は別名の重みになり、2 つのエージェントが同じように較正することは ありません。

手がかりは期間になります。sure はそのまま使い、likely は両側に期間の長さの半分だけ、vague は長さの分だけ 広げます。この余白は方針の版の一部です。

この期間が、この版の動的検索窓 (Dynamic Retrieval Envelope) です。手がかりの検索器が探すストアの部分であり、§2.5 の 1 回の修正が広げるものです。

2.2 手がかりの検索器

サーバーは、期間に限った検索器を 1 本余分に走らせます。タイムスタンプが期間の中にある記録を対象にしたベクトル検索と キーワード検索で、想起の深さまで取ります。期間の外の行は、除かれも、重みを下げられも、スコアを付け直されもしません。 手がかりは事前分布であって、フィルタではありません。

2.3 手がかりが行をどう動かすか

手がかりは、融合スコアにも、品質ゲートにも、autocut にも触れません。それらと件数がどの行を返すかを決めた後で、 その中で手がかりの検索器でも見つかった行を上げます。件数で切った後に上げるので、答えから行を押し出すことはありません (§2.9)。 上げる量は、スコアでなく位置で上限を決めます。

key(row) = p − L × 61 / (61 + c)        昇順に並べる
  • p は、返す並びの中でのその行の位置です。
  • c は、手がかりの検索器でのその行の順位です。
  • L は確かさで決まります: sure 3、likely 2、vague 1。

加点は最大 L なので、L より多く前にいた行は、どれも前にいたままです。手がかりが同時に何行を上げても、 どの行も L 段より上には上がりません。

この形は、融合のコードに照らして確かめた 2 つの導出から来ています。

  • 手がかりの検索器をもう 1 票として足すと、期間が第一の並べ替えの鍵になります。 rrf (k = 60) では、 順位 r の票 1 つと順位 c の手がかりの票を持つ行は、r × c < 61² = 3,721 のとき、1 位の票を含め、 票が 1 つしかないどの行にも勝ちます。深さが 50 なら、どの組でも成り立ちます。最悪の場合、行は最下位から 1 位へ動きます。
  • 手がかりを rsf のチャネルとして足すと、他の全行のスコアが変わります。 期間の外の行はすべて n/(n+1) 倍になり、 較正済みの閾値の近くにある行がゲートで落ち始めます。

スコアの差を保つほど小さな重みでも、位置の上限は保証できません。スコアの同点は実際に起こり、どんな正の加点も同点を崩します。

融合スコアに掛ける年齢の重みを、実際の長期記憶ストアで測ったところ、試したどの率でも重み無しに負けました。 理由は同じ平らさで、rrf では 1 位と 30 位の差が 1.475 倍しかなく、重みの幅より小さいのです。この結果が、 v0 が行を順位の空間で動かす理由です。

2.4 別枠

手がかりの検索器が見つけた記録のうち答えに入っていないものは、上限付きの移動では届きません。そのために別枠を確保し、 手がかりの検索器の順に埋めます。ブロックの別枠 が、ブロックの検索器だけが届いた記録のために 別枠を確保するのと同じです。別枠は何も押し出さず、その行は手がかりから来たことを示します (match_reason.signal = cue、admission = reservation)。cued-v0.3 (§2.11) からは、探した確かさの L と同じ数の行の別枠があり、 件数で切られた記録も別枠に入れます。それまでは 1 行の別枠で、他の検索器が届かなかった記録だけが対象でした。

2.5 1 回の見直し

手がかりの検索器が期間の中に何も見つけなければ、ループは CANDIDATE_MISS を疑います。手がかりが違う期間を指している、 ということです。期間を確かさ 1 段分広げ (sure は likely の余白へ、likely は vague の余白へ、vague は期間なしへ)、 手がかりの検索器だけをもう一度走らせます。他の検索器の結果は、想起のプロセスが求めるとおり使い回します。 段は最大 2 つで、時間の上限があります。上限で止まった時は記録に書きます。

2.6 不変条件

  • time_cue が無ければ、recall は今日のものと同一です (golden で固定)。
  • time_cue があっても、品質ゲートを通る行の集合は、無い場合と同一で、件数で返る行も同一です。手がかりはそれらの行を並べ替え、予約された行を最大 L 行足すだけです (cued-v0.3 より前は 1 行)。
  • どの行も L 段より上には上がりません。
  • 分離 (agent_id・project_id・channel) は決して広げません。それは探す空間であって、手がかりではありません。

2.7 実装で決めたこと

上の節が開けたままにしていた点は、次のように決めました。いずれも方針の版 cued-v0 の一部です。

  • 相対の期間。 {"unit": u, "value": n} は、今から n 単位前を中心とする 1 単位ぶんの期間で、今より後には伸びません。1 か月は 30 日です。long_ago は、その範囲が持つ時間の幅のうち最も古い 3 分の 1 です。絶対の手がかりの開いた端は、範囲の最も古い記録か今で閉じます。日付はその日全体を指すので、before: "2026-08-31" は 31 日を含みます。
  • 手がかりの検索器は記憶を探し、cued-v0.2 (§2.10) からはエピソードも探します。エピソードの時刻は、他の箇所と同じく開始時刻、無ければ記録された時刻です。ベクトルの半分は、通常のベクトル検索器が既に埋め込んだクエリのベクトルを使い回すので、手がかりのために 2 回目の埋め込みは起きません。手元にベクトルが無い場合はキーワードだけになります。すべてのリストは逆順位で 1 つにまとめます。クエリが空なら、期間内の新しい記録を返します。出どころで絞る時は、エピソード (利用者ごとの出どころを持たない) は channel でも範囲が決まっている場合だけ探します (通常の検索器と同じ)。
  • 同点。 手がかりが見つけた行と見つけなかった行の同点は、見つけた行を前にします。それ以外の同点は元の順を保ちます。したがって手がかりの検索器が 1 位にした行は、そこまで下にいれば、ちょうど L 段上がります。順位が下の行ほど上がり幅は小さくなります (順位 60 で L の半分)。
  • 広げ直した後の L は、実際に探した確かさの段のものです。vague の期間に何も無ければ、それより広い期間が無いので止まります。
  • 広げ直しの時間の上限は CPERSONA_RECALL_CUE_TIME_LIMIT_MS (既定 1000) で、recall の開始から数えます。
  • 応答は time_cue を持ちます: 方針、最後に探した期間、使った確かさの段、広げ直したか、動いた行の数、使った別枠の数。手がかりの検索器が順位を付けた行は match_reason.cue_rank を持ちます。読めない手がかりは ok: false と、どの部分かを示す error で拒否し、無視はしません。
  • reconstruct も同じ time_cue を受け付け、候補を読む recall に適用します。

2.8 今日を指す手がかりは使わない (cued-v0.1)

手がかり自身の期間 (確かさの余白を足す前) の始まりが、今から 24 時間前より後なら、その手がかりは今日か未来だけを 指しているので、recall は使いません。返る行は手がかり無しの recall と完全に同じです。応答の time_cue は ignored: "recent_only" と期間を持ち、記録には cue_ignored が残ります。方針の版は cued-v0.1 で、cued-v0 は この規則が無いだけの同じ方針です。

理由: ループの最初の計測では、別のモデルが質問文と質問日だけから取り出した手がかりを各問に渡しました。 60 個の手がかりのうち 36 個は、問が時期に触れていないのに質問日そのものを名指ししていて、手がかりの期間が 根拠を含んだのは 57 問中 18 問だけでした。呼び出し側が今日の日付で手がかりを埋めることが、手がかりが外れる 実際の形です。この規則で失う正しい手がかりは、答えが直近 1 日に保存された場合だけで、その記録はもともと ストアで最も新しいものです。ツールの説明は、依頼の文そのものが時期に触れている時だけ手がかりを渡すよう求めます。

2.9 上げるのは件数で切った後 (cued-v0.1)

cued-v0 では、件数 (limit) で並びを切る前に行を上げていました。そのため切る位置のすぐ下の行が答えに上がり、 最後の行を押し出すことがありました。ツールの説明は「手がかりが行を取り除くことはない」と書いていたのに、です。 実際の長期記憶のストアで件数 10 で測ると、抽出した手がかりで 60 問中 8 問、わざと誤らせた手がかりで 60 問中 2 問で これが起きていました。cued-v0.1 は先に切り、返す行の中でだけ上げるので、返る行は手がかり無しの recall と完全に同じ行の 並べ替えに、最大 1 行の別枠を足したものになります。その代わり、切る位置のすぐ下の行を上げて答えに入れることはできなくなり、 行を足せるのは別枠だけです。

2.10 手がかりの検索器はエピソードも探す (cued-v0.2)

cued-v0.1 までは、手がかりの検索器は記憶だけを探していました。実際の長期記憶のストアで、手がかりの期間が 93% の問で根拠を 含んでいた 181 問を測ると、効果は根拠の種類で分かれました。根拠が記憶なら 35 問で上がり 3 問で下がり、エピソードなら 1 問も 上がらず 12 問で下がりました。手がかりの検索器がエピソードを見つけられないので、期間内の別の記憶を上げてエピソードを追い越して いたのです。cued-v0.2 は、同じベクトルとキーワードの半分で期間内のエピソードも探します。

その後、新しい問 (LongMemEval のうち本文が時期を指す問、事前登録した判定則、69 問) で測ると、cued-v0.2 は判定則を 満たしませんでした (上がった 8 問、下がった 5 問、片側 p = 0.26。benchmarks/measurements/results-longmemeval-time-cue.md)。 手がかりは正しく、69 問中 64 問で期間が根拠のセッションを含んでいました。

2.11 件数で切られた行の別枠と、件数から独立した手がかりの検索器 (cued-v0.3)

判定の後に想起の記録から読んだ、cued-v0.2 がそれらの問でほとんど効かなかった理由: 根拠のセッション 186 のうち 47 を 失っており (19 はゲートを通って件数で切られ、28 はどの検索器にも届かなかった)、そのうち 34 は手がかりの期間の中にありました。 手がかりの検索器が届いたのは 3 つだけで、理由は方針そのものにありました。

  • 手がかりの検索器は件数と同じ深さまでしか探していませんでした。期間がスコープの大半を覆うと、通常の検索器が既に 返したものを並べるだけになります。
  • 件数で切られた行は通常の検索器が届いた行なので別枠の対象外で、切った後に上げる移動 (§2.9) でも戻せません。

cued-v0.3 はこの 2 点だけを変えます。

  • 手がかりの検索器は件数と無関係な深さまで探します (各半分 50 件)。件数だけを変えても候補は変わりません。
  • 別枠: 探した確かさの L と同じ数 (sure 3、likely 2、vague 1)。別枠には、答えに入っておらず、品質ゲートと autocut が拒否しなかった、手がかりの検索器の最上位の記録が入ります。どの通常の検索器も届かなかった記録か、通ったが 件数で切られた記録です。ゲートや autocut が拒否した行は、この経路でも戻りません。

§2.6 の上限は構造のままです。ゲートを通る行と件数で返る行は手がかり無しの recall と同じで、手がかりはそれを並べ替え、 最大 L 行を足します。したがって手がかり付きの recall は limit より最大 3 行多く返しえます。同じ問で再生すると、この 変更で根拠が全部返る問は 39 から 43 になりましたが、手がかり無しで同じ数の行を足しても 40 になりました。伸びの一部は 別枠を増やしたこと自体によります。時期の別枠がそれ以上のものを運ぶかを、時期に依存する問で、手がかり無しと、同じ数の 手がかり無しの行の両方に勝つことを求める判定則で測りました。LMEB の TMD (本文が時期を述べる 1,167 問) では勝ちました。 返る行の NDCG の平均は、手がかりありで 0.189 から 0.281 (+49%)、同じ数の手がかり無しの行では 0.204 (+8%) になり、 根拠は 1,029 問で上がり、下がった問はありませんでした (benchmarks/measurements/results-tmd-time-cue.md)。

3. v0 が主張すること

v0 は能力です。呼び出し側が「いつ」を伝えられ、答えは決まった範囲の中でそれを反映します。答えの正確さを 上げるとは、まだ主張しません。

精度の主張には、手がかりを持つ問が十分に要ります。手がかりを持てるのは、本文が時期を指している問だけです。 手がかり付きの問が数十問では、中程度の効果を検出する力が弱すぎます。したがって主張は、手がかり付きの問を 少なくとも 60 問持つ質問集を待ちます。最初のそうした計測では、cued-v0.2 に主張はつきませんでした (§2.10)。 2 回目の計測で、cued-v0.3 は検索の段で、時期に依存する問について主張を得ました (§2.11)。正しく述べられた時期は、 同じ数の行を足すだけで得られる以上に根拠を上げます。答えの正確さについての主張ではありません。

誤った手がかりが招きうる害は、統計的な検定ではなく、構造 (L 段と L 行の別枠) で上限を決めています。誤った手がかりの 損が 3 ポイント未満だと検定で示すには、1,000 問を超える問が要ります。

精度の計測を行う時は、手がかりを、問の本文とその日付だけから、固定したプロンプトで、問を書いたのとは別の系列の モデルで抽出します。手がかりを答えから作ってはいけません。

4. v0 の後

  • 手がかりの伝播: 強く当たった行の期間・エピソード・プロジェクト・宣言された関係を、次の段の手がかりにする。
  • ゲートの緩和: ゲートのすぐ下に候補が多い時 (FILTER_DROP を疑う時)、ゲートを上限付きで 1 段下げる。
  • 古い情報との矛盾: 新しい記録が同じことについて古い記録と矛盾する時、両方を役割付きで返す。
  • 2 段を超える、より豊かな停止条件。
  • 実際の recall を後から調べるための記録の保存。