Agentic OS 技術スタックを下から読む 第24回:記憶を、どこに持つか ―― 文脈・外部検索・小さな状態
第12回では、記憶とは、ただ保存することではなく、「なぜそうしたか」という因果を残す土台だ、と見た。あれは、何を残すかの話だった。
記憶は、置き方で性質が変わる
第12回では、記憶とは、ただ保存することではなく、「なぜそうしたか」という因果を残す土台だ、と見た。あれは、何を残すかの話だった。
今回は、別の軸を見る。残すべきものが決まったとして、それをどこに、どう持つのか。ここで設計は大きく三つに分かれる。
一つ目は、過去をそのまま文脈に積む道である。二つ目は、過去を外に置き、必要なときに検索して戻す道である。三つ目は、過去を小さな固定サイズの状態に畳み、その状態を更新し続ける道である。
この三つは、単なる実装の違いではない。コスト、思い出せる確かさ、細部の残り方、失敗の仕方が、それぞれ違う。
道その一:全部、文脈に積む
いちばん単純なのは、過去のやり取り、ユーザーの好み、途中の判断、決定済みの方針を、そのまま入力の文脈に入れておくことだ。
この方法は強い。特別な検索器も、記憶用の別モデルも、更新規則も要らない。入力に入っていれば、本体はそれを読める。直近の会話が十数ターン程度で、数千トークンから数万トークンに収まるなら、これが最も素直である。
しかし弱みは二つある。
一つ目は、文脈が長くなるほど高いことだ。第1回から第7回で見たように、長い文脈は、文字列を足すだけでは済まない。生成中、本体は過去の各位置に対応するキーと値を保持する。いわゆる注意のための作業領域である。層が三十二、注意ヘッドが三十二、各ヘッドの次元が百二十八だとすると、ひとつのトークンごとにかなりの中間表現を持つ。これが一万トークンなら一万個、十万トークンなら十万個ぶん積み上がる。
長文脈の値段は、入力文字数そのものより、この保持される過去によって決まる。過去を長く持てば、メモリを食う。さらに、新しいトークンを一つ出すたびに、その長い過去を参照する。実装上の工夫でかなり軽くできても、長さが伸びれば、読む対象は増える。
二つ目は、入れたからといって使われるとは限らないことだ。
たとえば、五万トークンの文脈の中に、「このユーザーは最終出力に固有名を出したくない」という一行があるとする。その周囲には、別件の調査、古い議論、途中で捨てた案、生成済みの草稿がある。本体から見ると、必要な一行は、広い紙面の中の一点である。注意は全位置を見られるが、全位置を同じ強さで理解するわけではない。関連の薄い過去が大量にあると、肝心の一行は薄まる。
つまり、文脈に積む記憶は、保存の忠実度は高いが、利用の忠実度は保証しない。入っていることと、使われることは違う。
短い記憶ならよい。直近の依頼、作業中の制約、数ターン前の決定には向いている。しかし、長期記憶をすべてこの方法で扱うと、費用が増え、遅くなり、しかも大事な情報が埋もれる。
道その二:外に置いて、必要なときに検索する
二つ目の道は、過去を文脈の外に置くことだ。会話の履歴、読んだ記事、ユーザーの好み、過去の決定、失敗した実験を、外部の置き場に保存する。そして、いまの入力に関係しそうなものだけを取り出し、文脈に戻す。
この方法の利点は明快である。文脈を短く保てる。過去が百万トークンあっても、その全部を毎回読む必要はない。いま必要な三件、五件、十件だけを戻せばよい。
ただし、ここでは検索が記憶の入口になる。入口で外すと、その先はない。
検索は、たいてい二段に分かれる。まず、いまの入力を手がかりにして候補を広く拾う。次に、その候補を並べ替え、上位だけを文脈に入れる。たとえば、外の置き場に十万件の記憶があり、最初に百件を拾い、最後に八件を戻す。この八件に入らなかった記憶は、本体からは存在しないのと同じである。
ここに賭けがある。
ユーザーが「前に決めた出力形式で」と言ったとき、その「前」がどの会話かを検索が当てなければならない。語彙がずれていると外れる。「出力形式」という語で保存していたのに、今回の入力が「いつもの形」なら、表面的な一致は弱い。逆に、似た語を含む無関係な記憶が上に来ることもある。
外部検索の記憶は、保存量には強い。正確な事実にも強い。日付、URL、数値、過去の決定、引用可能な文章のように、細部を失いたくないものは、外に置く方がよい。
しかし、毎回の検索には計算がかかる。候補を探す計算、並べ替える計算、戻した記憶を文脈に入れるトークン費用がある。八件戻して、それぞれ三百トークンなら、それだけで二千四百トークン増える。毎ターン行えば、そのぶんが毎回の固定費になる。
外部の記憶は、引き出しとしては賢い。だが、いつも引き当てに賭けている。検索が当たる設計なら強い。検索が外れる課題では、見ていないものについて答えることになる。
道その三:小さな状態に、畳んで持つ
三つ目の道は、過去を固定サイズの小さな状態に畳むことである。
ここでは、過去の文章をそのまま保存しない。検索して戻すこともしない。代わりに、対話や観測から得た情報を、小さな数値の状態として持つ。その状態を、入力が来るたびに少しずつ更新する。そして、生成時には、その状態を本体の注意の計算に足し込む。
仕組みを細かく見る。
まず、すでに訓練し終えた本体を凍結する。本体の重みは変えない。言語能力、推論能力、文体、知識の大部分は、そのまま使う。長期記憶のために本体を再訓練しない。
その外側に、小さな記憶状態を一つ置く。たとえば、八かける八の行列を一つ持つ。要素数は六十四である。十六かける十六なら二百五十六である。三十二かける三十二でも千二十四である。これは、本体の何十億、何百億という重みと比べると、ほとんど点のように小さい。
新しい情報が入るたびに、この状態を更新する。ここで大事なのは、全部を作り直さないことだ。過去全文を読み直して新しい記憶を再構成するのではない。現在の状態に対して、今回の入力から作った差分を加える。
たとえば、いまの状態を小さな行列 S とする。新しい入力から、書き込み用の小さな行列 ΔS を作る。そして、次の状態を S + ΔS にする。ただし、単純に足すだけだと、古い情報がいつまでも残って飽和する。そこで、忘却の係数を入れる。たとえば 0.98 × S + ΔS のようにする。これなら、古い状態は一回更新するごとに二パーセントずつ薄くなる。五十回後には、およそ三十六パーセントまで下がる。百回後には、およそ十三パーセントまで下がる。
別の更新規則もある。入力から「消す量」と「足す量」を別々に作る。消す量が大きい成分は古い記憶を弱め、足す量が大きい成分は新しい記憶を書き込む。これにより、「ユーザーは短い返答を好む」という古い傾向を残しつつ、「この連載では細部を厚く書く」という今回の方針を強く上書きできる。
生成時には、この状態を本体にどう読ませるかが問題になる。ここでも、本体の重みは変えない。
本体の注意では、各トークンから問い合わせを作り、過去のキーとの相性を計算し、その相性で値を混ぜる。小さな状態を使う方法では、この注意の計算に、低ランクの補正を足す。
低ランクとは、自由度が小さいという意味である。大きな行列を丸ごと足すのではない。たとえば、本来なら四千九十六次元から四千九十六次元への補正が必要に見えるところを、八次元の細い通路を通して作る。四千九十六かける八の細い変換で圧縮し、八かける八の記憶状態を通し、八かける四千九十六の細い変換で戻す。この場合、記憶そのものは八かける八で、六十四個の数にすぎない。
この補正は、本体の出力を無理に書き換えるものではない。注意の向きに、少し偏りを入れる。いま読むべき特徴、残すべき流れ、直前までの作業状態を、注意に近い場所で反映する。
ここが、外部検索と違う。外部検索では、記憶を文章として戻し、本体に読ませる。小さな状態では、記憶を文章に戻さず、注意の計算に直接混ぜる。したがって、トークンを増やさない。毎回、過去の記憶文を文脈に貼る必要がない。
また、文脈に全部積む方法とも違う。文脈方式では、過去はそのまま残るが、長くなる。小さな状態方式では、過去は圧縮される。細部は失われるが、流れは残る。これは、作業中の姿勢、ユーザーの好み、最近の話題の方向、未完了の意図のようなものに向いている。
たとえば、ある対話で「説明は日本語で、固有名は避け、抽象論ではなく数字で書く」という制約が続いているとする。これを毎回二百トークンの注意書きとして貼ることもできる。しかし小さな状態なら、その傾向を数十から数百個の数に畳み、生成時の注意に反映できる。正確な文言は残らないが、「この出力ではそう振る舞うべきだ」という方向は残る。
どれくらい小さく、どれくらい効くか
驚くのは、この状態がかなり小さくても効くことだ。
たとえば、八かける八の行列を記憶状態として使う。要素数は六十四である。これは、一段落ぶんの文章にも満たない量だ。にもかかわらず、記憶を持たない素の本体と比べると、平均でおよそ一・一倍の成績になる。つまり、百点満点に換算して五十点だった課題が、五十五点前後まで上がる程度の改善である。
さらに、記憶を持たない別の軽い補助方式と比べると、およそ一・一五倍になる。これは、小さな状態が単なる追加パラメータではなく、実際に過去の流れを保持していることを示している。
記憶が重く効く課題では、差はもっと開く。過去の指示、以前の観測、途中の判断を使えないと答えにくい課題では、およそ一・三一倍に届く。五十点が六十五点程度になる差である。これは小さくない。
しかも、この改善は、本体を再訓練せずに得られる。本体を別物に置き換えない。長い文脈を無理に引き伸ばさない。外部の巨大な検索庫を毎回引かない。小さな状態を更新し、その状態から注意へ低ランクの補正を入れるだけである。
もちろん、万能ではない。
八かける八の状態に、過去の長文を正確に入れることはできない。六十四個の数に、三日前の会話の具体的な一文、日付、固有の数値、例外条件を全部保持するのは無理である。小さな状態が得意なのは、正確な保存ではなく、圧縮された傾向である。
その代わり、安い。状態の大きさが固定なので、対話が長くなっても記憶部分の大きさは増えない。十ターンでも百ターンでも、八かける八なら六十四個の数である。文脈に積む方法のように、過去が増えるほど入力が伸びるわけではない。
また、一般的な能力もほぼ保たれる。本体を壊さないからである。記憶のために本体の重みを直接書き換えると、別の能力が劣化する危険がある。小さな状態方式では、本体は凍結され、記憶は外付けの状態と読み出しの補正に閉じ込められる。失敗しても、壊れる範囲が小さい。
これは、Agentic OS の設計では大きい。長く動くエージェントでは、記憶は常に更新される。更新のたびに本体を変えるのは危ない。小さな状態なら、更新対象が狭い。破棄もできる。複数の作業単位ごとに別の状態を持つこともできる。あるプロジェクトでは八かける八、別のプロジェクトでは十六かける十六、長い調査では三十二かける三十二というように、記憶の太さを変えられる。
三つの取捨
どれが正解か、という話ではない。三つの道は、それぞれ違うものを得意にしている。
文脈に積む方法は、単純で強い。短い記憶には向いている。直近の依頼、いま開いている作業、数ターン前の制約は、そのまま文脈に入れるのがよい。保存の忠実度が高く、実装も簡単である。
ただし、長くなると高い。過去を積むほど、保持するキーと値が増え、生成のたびに参照する範囲も広がる。そして、長い文脈に入れた情報は、必ず使われるわけではない。大事な一行が、関係ない過去の中に埋もれる。
外部に置いて検索する方法は、膨大な知識に向いている。正確な事実、古い決定、過去の文書、まれにしか使わない情報は、外に置く方がよい。必要なときだけ戻せば、文脈を短く保てる。
しかし、検索の精度に依存する。拾えなければ存在しないのと同じである。さらに、毎回の検索計算と、戻した記憶を文脈に入れるトークン費用がかかる。検索結果が八件、合計二千四百トークンなら、そのぶんは毎回の入力に乗る。
小さな状態に畳む方法は、連続した対話の流れに向いている。最近の作業姿勢、ユーザーの好み、出力の調子、未完了の意図、注意すべき方向を保つのに向く。状態が固定サイズなので、長く使っても記憶の費用が膨らみにくい。
ただし、畳めば細部は薄れる。八かける八の状態は、八かける八でしかない。そこに正確な引用、細かな日付、例外条件の一覧を期待してはいけない。第7回で見た固定サイズの状態の宿命と同じである。固定サイズに押し込めば、何を残し、何を捨てるかが必ず起きる。
だから実際には、混ぜて使う。
直近のやり取りは文脈に置く。まれに引く確かな事実は外部に置き、検索する。流れ、癖、作業中の姿勢は小さな状態に畳む。三つを役割で分けると、記憶は安定する。
Agentic OS への含意
エージェントに記憶を持たせる、と言うとき、実は二つの別々の設計をしている。
一つは、第12回で見た「何を残すか」である。結果だけを残すのか。途中の判断も残すのか。なぜその選択をしたのかという因果を残すのか。これは、記憶の内容の設計である。
もう一つが、今回見た「どこに、どう持つか」である。文脈に積むのか。外部に置いて検索するのか。小さな状態に畳んで更新するのか。これは、記憶の配置と更新の設計である。
この二つは別の軸であり、両方が要る。
何を残すべきかが決まっても、それを文脈に置くのか、外部に置くのか、小さな状態に畳むのかで、コストも、遅さも、思い出せる確かさも変わる。逆に、どんなに立派な記憶置き場を用意しても、残す内容がただのログなら、エージェントは因果を思い出せない。
記憶は、保存箱ではない。運行時の計算にどう接続するかまで含めた設計対象である。
文脈に置けば、そのまま読めるが高い。外に置けば、広く持てるが検索に賭ける。小さな状態に畳めば、安く流れを保てるが、細部は薄れる。
長く動くエージェントでは、この取捨がそのまま運用品質になる。どこまでを文脈に残すか。何を外部検索に回すか。何を八かける八、十六かける十六、三十二かける三十二の小さな状態に畳むか。
記憶を持つとは、過去を全部抱えることではない。どの過去を、どの形で、次の一手に効かせるかを決めることである。
← 一覧へ