この記事の確認範囲
- 対象の論文:He ほか「Memory Tax: Dissecting the Net Value of Agent Memory」(arXiv:2610.34073 v1、2026年10月25日公開、CC BY 4.0、査読前のプレプリント)
- 著者の所属:イリノイ大学アーバナ・シャンペーン校(UIUC)、清華大学、中国人民大学、Verdent.ai、ウォータールー大学。著者は Xuan He、Sining Zhoubian、Jiawei Han、ChengXiang Zhai ほか計11名
- あわせて読んだもの:公式のプロジェクトページ、GitHub の公式リポジトリ(MIT ライセンス)、Hugging Face で公開された実験結果データ(CC BY 4.0)。いずれも2026年10月29日に確認
- 手元で確かめたこと:公開された「問題ごとの正誤」データから、論文の主要な数字(表1・図2)を再計算しました。LLM を動かす新しい実験はしていません。結果は「手元で確かめる」の節にまとめています
はじめに:記憶を賢くするほど、答えが悪くなることがある
AI エージェントを長く使ってもらうには、過去の会話を覚えておく「長期メモリ」が欠かせません。そこで最近は、会話を要約したり、事実だけを抜き出したり、知識をグラフにつないだりする「高度なメモリ」の仕組みがたくさん提案されています。
直感的には、整理された記憶のほうが役に立ちそうです。ところが、この論文が11種類のメモリを同じ条件で比べたところ、多くの場合で、会話の原文をそのまま検索するだけの「素の RAG」に負けていました。手間とお金をかけて記憶を整理したのに、正答率は下がっていたのです。
研究チームはこの損失を「メモリ税(Memory Tax)」と名付けました。記憶を整理すると得をする問題(メモリ利益)がある一方で、整理の途中で失われたり、見つけにくくなったりして損をする問題もある。その差し引きが、メモリの本当の価値(純価値)だという考え方です。

録音(原文)を残す方法と、議事録(要約)だけ残す方法の違い。議事録は読みやすい反面、書かなかった数字は二度と戻りません(例は論文の事例をもとにした概念図)。
この記事では、論文の結果を次の順で整理します。
- 何と何を、どんな条件で比べたのか
- 「99通り中90通りで素の RAG 以下」という結果の中身と、費用の差
- メモリ税がどこで生まれるのか(作るとき・探すとき・モデルを替えたとき)
- 論文が示す、税を減らす設計の工夫
- 自分のエージェントでメモリ税を数える方法(コード付き)
想定読者は、チャットボットや AI エージェントに長期記憶を付けたいエンジニアの方、そして「RAG とメモリは何が違うの?」と気になっている方です。
前提:素のRAGとエージェントメモリは何が違うのか
まず、比べている2つの方式をはっきりさせておきます。
素の RAG(ナイーブ RAG)は、会話の原文を小さなかたまり(チャンク)に分けて保管し、質問が来たら関係しそうなかたまりを検索して LLM に渡す方式です。保管するときに LLM は使わず、原文に手を加えません。
エージェントメモリは、保管する前に LLM が会話を読み、要約する、事実を1行ずつ抜き出す、ユーザーの好みをまとめる、出来事どうしをつなぐ、といった加工をします。質問が来たら、加工後の記憶(メモリ単位)から検索します。

違いは「保管する前に LLM で加工するかどうか」。加工の段階と、加工後の記憶を探す段階に、それぞれ税の発生源があります。
論文はこの関係を、会話の履歴 H をメモリ M に変える処理(取り込み)、質問 q に合う記憶を探す処理(検索)、見つけた記憶から答える処理(生成)の3段に分けて定義しています。素の RAG は、取り込みで「ほぼ何もしない」特別な場合だと見なせるので、メモリの価値を測る物差しとして使えるわけです。
比べた11種類のメモリ
論文は、記憶のしまい方で4つのグループに分け、それぞれ公開されている代表的な実装を選んでいます。
| グループ | しまい方 | 方式 |
|---|---|---|
| フラット型 | 抜き出した事実やメモを1か所に並べる | A-Mem、SimpleMem、MemOS |
| 階層・寿命型 | 短期・長期などの層に分け、移動・統合・更新する | MemoryOS、LightMem、OpenViking |
| グラフ型 | 人物・出来事などで記憶どうしをつなぎ、たどって探す | HippoRAG2、MAGMA、PlugMem |
| 学習済み方針型 | 何を書き・更新し・使うかを強化学習で学ぶ | Mem-T、Memory-R2 |
「素の RAG」といっても手抜きではない
ここで大事なのは、比較に使った RAG が、検索の基本をきちんと押さえた作りになっている点です。
- 会話の発言の切れ目でおよそ1,024文字ずつにまとめ、セッションの日時を先頭に付ける
- キーワード検索(BM25)と意味の近さで探す検索(埋め込み、Qwen3-Embedding-0.6B)を両方使う
- 2つの結果を合わせ、並べ替え専用のモデル(リランカー、Qwen3-Reranker-0.6B)で上位を選ぶ
- 「2回検索」版では、LLM が1回目の結果で足りるかを判断し、足りなければ質問を言い換えてもう一度探す
論文は付録で、分割の仕方や埋め込みモデル、リランカーを入れ替えても、RAG の成績が多くは数ポイント以内の差に収まることを確かめています。また、評価した方式の元論文などを見直したところ、RAG と比べていたのは一部だけで、その RAG も設定がはっきり書かれていないことが多かった、と指摘しています。比べる相手の RAG が弱すぎたせいで、メモリの価値が大きく見えていた可能性がある、というのが論文の問題意識です。
実験の条件
比較は「個人向けエージェントの会話の記憶」という、メモリが最もよく使われる場面で行われました。
| 項目 | 内容 |
|---|---|
| ベンチマーク | LongMemEval-s(500問、1問ごとに約50セッション・約11.5万トークンの会話)、PersonaMem-v2(40人分・934問、4択、約12.3万トークン)、MINT-Eval(60本・1,557問、約13.9万トークン) |
| 答えるモデル | Qwen3.5-4B、Qwen3.6-27B、GPT-5.6 Luna(推論 low) |
| 採点 | Qwen3.6-27B による自動採点(人の判定との一致は 95.1〜100%) |
| 公平にする工夫 | 埋め込みモデル・リランカー・回答用プロンプトを全方式で共通にし、検索で LLM に見せる量(Retrieval Scope)が RAG を下回らないように各方式の top-k を調整(多くは既定値より増やした) |
PersonaMem-v2 と MINT-Eval は費用の都合で一部(20%と10%)を、問題の種類の比率を保って抜き出しています。GPT-5.6 Luna の LongMemEval-s は100問です。
結果1:99通り中90通りで、素のRAGを下回った
11方式 × 3ベンチマーク × 3モデルで、結果は99通りあります。2回検索する RAG と比べると、メモリ側が上回ったのは9通りだけで、残り90通りは下回りました。とくに LongMemEval-s では33通りすべてが RAG を下回っています。

マスの数字は2回検索の RAG との差(ポイント)。上回った9通りは PersonaMem-v2 と MINT-Eval に集まっています。公開データから再計算した92通りと、判定データが公開されていない7通り(論文の表1の値)を合わせたもの。
代表的な数字を抜き出すと、次のとおりです(Qwen3.6-27B で答えた場合)。
| 方式 | LongMemEval-s | PersonaMem-v2 | MINT-Eval |
|---|---|---|---|
| RAG(1回検索) | 77.6 | 46.8 | 28.6 |
| RAG(2回検索) | 81.4 | 48.7 | 33.1 |
| A-Mem | 79.4 | 48.4 | 24.4 |
| MemOS | 76.6 | 50.2 | 19.7 |
| PlugMem | 75.8 | 45.2 | 33.3 |
| OpenViking | 58.8 | 38.7 | 18.6 |
単位は正答率(%)。論文 表1 より抜粋。
ここで誤解しないでほしいのは、「メモリはどれも役に立たない」という結果ではないことです。PersonaMem-v2 のように、ユーザーの好みを推し量る問題では MemOS などが RAG を上回っていますし、MINT-Eval でも PlugMem や Mem-T が上回る場合があります。論文自身も、メモリが一様に劣るわけではないと強調しています。
費用はけた違い
正答率で負けている一方、費用は大きく上回ります。GPT-5.6 Luna で LongMemEval-s の100問を解いたときの記録を並べると、差がよく分かります。

記憶を作る段階のトークン数(構築+保守)と時間。RAG は埋め込みで索引を作るだけなので、この段階で LLM を呼びません(論文 付録 表19、GPT-5.6 Luna)。
| 方式 | 正答率 | 記憶づくりのトークン | 記憶づくりの時間 | LLM 呼び出し回数(回答も含む全体) |
|---|---|---|---|---|
| RAG(2回検索) | 90.0% | 0(LLM 不使用) | 0.11時間 | 200回 |
| A-Mem | 84.0% | 1億7,430万 | 117.0時間 | 9万8,400回 |
| SimpleMem | 84.0% | 3,850万 | 13.9時間 | 6,980回 |
| LightMem | 77.0% | 522万 | 15.2時間 | 2,670回 |
| Mem-T | 78.0% | 2億3,210万 | 76.0時間 | 11万4,200回 |
時間は並列処理をならした「1本で処理した場合の換算時間」。
結果2:メモリには「利益」と「税」の両方がある
では、メモリはなぜ負けるのでしょうか。論文は全体の正答率だけでなく、1問ずつ「RAG とメモリのどちらが正解したか」を突き合わせました。すると、問題は次の4つに分かれます。
- 両方とも正解
- メモリだけ正解 → これがメモリ利益(整理したおかげで解けた)
- RAG だけ正解 → これがメモリ税(整理したせいで解けなくなった)
- 両方とも不正解
メモリ利益からメモリ税を引いたものが、メモリを作った意味=純価値です。ここでの基準は1回検索の RAG です。

SimpleMem(Qwen3.6-27B、LongMemEval-s)の内訳。40問を新たに解けるようになった一方、RAG なら解けた60問を落としています。公開データからの再計算でも同じ数になりました。
この見方で全体を見ると、メモリの姿がはっきりします。公開データから再計算できた92通りでは、メモリ利益はどの方式にも必ずあり(4.0〜18.5%)、同時にメモリ税も必ずありました(5.8〜43.4%)。そして92通り中68通りで、税が利益を上回っていました。
同じ仕組みが、ある問題では得をし、別の問題では損をする
論文が示す具体例で特に分かりやすいのが、「同じ仕組みが両刃の剣になる」例です。

同じ「1行ずつの事実に分ける」処理が、左では利益、右では税になります(論文 付録 図25の事例を要約)。
左の例では、「何軒の家を見たか」という質問に対し、RAG は長い会話のかたまりに埋もれて1軒しか見つけられませんでした。Memory-R2 は内見の記録を1件ずつ短い事実にしていたので、4件すべてを答えの材料に入れられました。
ところが右の例では、同じ処理が「500ポイントで次の買い物が5ドル引きになる」という発言を「ポイント特典のために店をよく使う」と1行にまとめ、肝心の「5ドル」を落としてしまいました。RAG は原文をそのまま渡したので正解しています。
ほかにも、相対的な日付(「3週間前」)を絶対日付に直す処理は順番の質問で役立つ一方、別の質問では出来事と日付の結び付きを取り違える原因になっていました。記憶の加工は、元の情報からの「ずれ」を必ず生みます。ずれは役に立つように設計されていますが、すべての質問にとって良いずれになるとは限らない、というのが論文の見立てです。
メモリ税はどこで生まれるのか
論文は、利益は「設計どおりの効果」なので、見落とされがちな税のほうを詳しく調べました。税の発生源は、メモリの中で3つ、評価のしかたで1つ見つかっています。
1. 忠実性:作る段階で情報が消える
1つめは、記憶を作る段階で情報が欠けたり、ゆがんだりする問題です。ここで消えた情報は、あとでどれだけ上手に探しても戻りません。
論文は、会話から500発言を抜き出し、それぞれがメモリの中でどう残っているかを人が確かめました。
| 方式 | 欠けなし | 一部欠け | 大きく欠け | 対応する記憶なし |
|---|---|---|---|---|
| MemoryOS | 98.4% | 0.0% | 0.0% | 1.6% |
| MAGMA | 93.6% | 6.2% | 0.2% | 0.0% |
| MemOS | 85.8% | 9.0% | 2.4% | 2.8% |
| LightMem | 76.2% | 13.6% | 0.6% | 9.6% |
| SimpleMem | 65.6% | 27.6% | 2.2% | 4.6% |
| OpenViking | 39.4% | 9.6% | 13.0% | 38.0% |
論文 表2より。方式によって残り方が大きく違います。
具体例を1つ見てみましょう。

同じ1文でも、方式によって残り方がまったく違います(論文 付録 図20の事例を日本語で要約)。
OpenViking は、この発言を「費用対効果の高い移動手段を好む」というユーザー像に変えて保存していました。人物像としては正しくても、「いくらと言われたか」には答えられません。
ほかにも、気になる例がありました。ユーザーが「〇〇の件は忘れて」と頼んだ発言を、ある方式は「忘れてほしい」という依頼ごと落とし、忘れるべき内容だけを記憶に残していました。別の方式は「忘れて」を「〇〇ではない」という否定の事実に書き換えていました。
論文はさらに、「正解に必要な記憶だけを直接渡したら、税はどこまで減るか」も試しています。検索の失敗をなくしても残る税は、作る段階で失われた分だと考えられるからです。結果は方式によって分かれました。
| 方式(LongMemEval-s、Qwen3.6-27B) | 通常の税 | 正解の記憶を直接渡したときの税 |
|---|---|---|
| MAGMA | 15.0% | 6.4% |
| MemOS | 9.8% | 7.4% |
| SimpleMem | 12.0% | 8.8% |
| PlugMem | 14.4% | 14.0% |
| OpenViking | 28.0% | 24.2% |
論文 図3より。MAGMA は税が大きく減ったので、情報は残っていたのに探せていなかったと考えられます。
2. 取り出しやすさ:残っていても見つからない
2つめは、情報は記憶に残っているのに、質問のときに見つからない問題です。論文は、検索を何回も繰り返して「必要な記憶が全部そろうのは何回目か」を数えました。

1回目の検索で証拠がそろった割合(Qwen3.6-27B、論文 図4の数値)。メモリは証拠が奥に埋もれやすいことが分かります。
なぜ埋もれるのか。論文は事例から、3つのよくあるパターンを挙げています。

分割・埋没・言い換えの3パターン(論文 付録 図24の事例を要約)。どれも情報自体は残っています。
- 分割:「チェーン交換に25ドル、ライトに40ドル」という1つの発言が3つの記憶に分かれ、合計の計算に必要な「25ドル」が4回目の検索でやっと出てきた
- 埋没:「通勤は片道45分」が59行あるユーザープロフィールの1行になり、関係ない情報に薄められて3回目まで出てこなかった
- 言い換え:「ジムはいつも夕方6時」という発言が会議の予定の話として書き直され、「いつも」が消えた。代わりに古い「7時」の記憶が先に出てきた
検索は、質問と記憶の言葉の近さで探します。記憶を書き換えると、質問の言葉から遠ざかってしまうことがあるわけです。論文は、同じ証拠について「質問と原文の近さ」と「質問と記憶の近さ」も比べています(PersonaMem-v2、Qwen3.6-27B)。OpenViking では近さの中央値が 0.351 から 0.239 に下がり、MAGMA でも62%の証拠で近さが下がりました。一方、SimpleMem のようにほとんど変わらない方式もあり、どのメモリでも下がるわけではありません。
3. 頑健性:モデルを替えると崩れやすい
3つめは、モデルを替えたときの安定性です。長く使うエージェントでは、記憶を作ったときのモデルと、あとで記憶を読むモデルが違うことがよくあります。
論文は、3つのモデルそれぞれで記憶を作り、別のモデルで答えさせました。すると多くの方式で、答えるモデルを弱いものに替えたときの落ち幅が RAG より大きくなりました。
| 場面 | RAG の落ち幅 | メモリの落ち幅 |
|---|---|---|
| MINT-Eval:GPT-5.6 Luna → Qwen3.6-27B で答える | −1.3 | MemOS −5.5〜−8.8、PlugMem −3.3〜−4.3 |
| PersonaMem-v2:Qwen3.6-27B → Qwen3.5-4B で答える | −9.6 | SimpleMem −10.6〜−12.2、LightMem −10.6〜−12.6 |
論文 図6より。幅は記憶を作ったモデルによる違い。MAGMA(−7.7〜−9.1)のように RAG より小さい方式もあります。
記憶づくりは LLM の判断に大きく頼るので、モデルが変わると記憶の中身も読み方も変わります。原文を保管する RAG にはない「揺れ」が加わる、というのが論文の説明です。
4. 評価側:ベンチマークに伸びしろが少ない
最後は、メモリの中ではなく評価のしかたの問題です。論文は15のメモリ用ベンチマークで、ごく単純な検索を試しました。

質問の中で珍しい2語を含むかたまりを集めるだけの検索(IDF キーワード)でも、5割を超えるベンチマークがあります(論文 付録 表16、Qwen3.6-27B)。
たとえば EverMemBench-MCQ では、IDF キーワード検索で68.2%、ハイブリッド RAG で82.7%に達しました。珍しい単語を1〜2個拾えば答えられる問題が多いと、凝ったメモリが上乗せできる余地(伸びしろ)は小さくなり、少しの損失でも目立ちます。
ただし論文は、こうした「原文を正確に思い出す」問題が実際の利用でも多く、それ自体に意味があることも認めています。そのうえで、今後のメモリ研究は素の RAG の点数もいっしょに報告し、伸びしろを見えるようにすべきだ、と提案しています。
会話以外のメモリでも同じことが起きる
メモリ税は会話の記憶だけの話ではありません。論文は2つの別の場面でも同じ見方を試しています。
1つは、過去のタスクの経験を次のタスクに生かす「経験メモリ」です。ウェブ操作(WebShop、Mind2Web)では、長い操作の記録を手順や戦略にまとめるメモリが、過去の記録をそのまま渡す RAG を上回る場合がありました。一方、ツールを使う課題(AppWorld)では、RAG の22.6%に対し、ReasoningBank と Memp が14.3%、SkillPro が10.7%と下回っています。効くかどうかは課題の種類に強く左右されます。
もう1つは、動画を作り続ける「世界モデル」の記憶です。カメラが一周して元の場所に戻ったとき、同じ景色を描けるかを比べたところ、過去のフレームをそのまま探して戻す方式(I3DM)が、潜在表現や KV キャッシュに圧縮する方式をおおむね上回りました(4つの指標のうち3つで最高)。ここでも「原文(原画)をそのまま使う」単純な方法が強い、という同じ傾向が見えます。
論文が示す、税を減らす3つの工夫
論文は最後に、新しい仕組みを作らずに、既存のメモリにそのまま足せる工夫を挙げています。
工夫1:原文へ戻る道を残す
いちばん効果がはっきりしていたのが、記憶の検索と並行して会話の原文も検索し、両方の候補をまとめてリランカーで並べ直してから答える方法です(論文では source-augmented、src-aug と表記)。加工で失われた証拠や、埋もれて見つからなかった証拠を、原文から取り戻せます。

記憶と原文の両方から候補を集め、まとめて並べ直す。税がおよそ半分になりました(論文 表4、Qwen3.6-27B)。
| 方式 | LongMemEval-s 正答率 | 税 | PersonaMem-v2 正答率 | 税 |
|---|---|---|---|---|
| SimpleMem | 73.6% | 12.0% | 46.7% | 11.2% |
| +原文へ戻る道 | 82.8% | 5.2% | 49.6% | 7.2% |
| MemoryOS | 73.4% | 11.4% | 48.2% | 17.1% |
| +原文へ戻る道 | 76.4% | 7.8% | 53.0% | 8.5% |
| Mem-T | 75.0% | 10.8% | 42.1% | 15.2% |
| +原文へ戻る道 | 80.0% | 5.2% | 49.0% | 8.7% |
論文 表4より。SimpleMem+原文(82.8%)は、2回検索の RAG(81.4%)も上回りました。
これは、記事の冒頭のたとえでいえば「議事録を作っても、録音は捨てない」ということです。整理した記憶の良さ(利益)を保ったまま、整理で失ったもの(税)を原文で補えます。
工夫2:探した経験を記憶に戻す
2つめは、過去の質問と、そのとき見つけた証拠そのものを記憶に加える方法です。何回も検索してやっと見つけた証拠を、次に似た質問が来たときにすぐ取り出せるようにします。論文はこれを「記憶のしかたを記憶する」と表現しています。
効果は控えめでした。PersonaMem-v2 で Mem-T は42.1%→45.7%、MemoryOS は48.2%→49.1%と上がりましたが、SimpleMem は46.7%→46.8%とほぼ変わらず、税はむしろ11.2%→12.1%に増えています。なお、LongMemEval-s は1つの会話に質問が1つしかないので、この方法は試せません。
工夫3:将来の質問を予測して、加工の向きを決める
3つめは今後の方向性として挙げられた考え方です。多くのメモリは、どんな質問が来るか分からないまま、先回りして記憶を加工します(Ahead-of-Time、AoT)。逆に、質問が来てから必要な加工をする方法(Just-in-Time、JiT)なら的を絞れますが、そのぶん応答が遅くなります。
論文は、その中間として「過去の関心や習慣から、将来どんな質問が来そうかを推定し、加工の手間をそこに集中させる」方向を提案しています。メモリを「過去の情報のうち、将来役に立ちそうなものを推薦する仕組み」と見る発想です。こちらは実験ではなく、提案にとどまります。
実務でどう生かすか(筆者の整理)
ここからは論文の主張ではなく、論文の結果を自分のエージェント開発に生かすための筆者の整理です。

メモリを入れる前に、まず素の RAG を物差しにする。筆者の整理による手順です。
- まず素の RAG を作る:BM25+埋め込み+リランカーのハイブリッド検索まで作れば、それが比べる基準になります。論文の結果を見る限り、多くの用途ではこれで十分な可能性があります
- メモリを足すなら原文は消さない:要約や事実の抽出をしても、元の会話は保管しておき、検索の候補に混ぜられるようにします。保存容量より、失った情報を取り戻せないことのほうが痛手です
- 同じ問題で両方を採点する:自分のサービスでよく来る質問を数十〜数百問用意し、RAG とメモリの両方で答えさせて正誤を付けます
- 問題ごとに利益と税を数える:全体の正答率だけでなく、「メモリだけ正解」と「RAG だけ正解」の数を見ます。税の出た問題を読むと、どんな加工が情報を落としているかが分かります
- 費用と並べて判断する:記憶づくりの LLM 呼び出し回数・トークン・時間も記録し、利益が費用に見合うかを確かめます。モデルを替えたときは測り直します
とくに4の「問題ごとに数える」は、全体の正答率では見えない情報をくれます。正答率が同じでも、利益と税がどちらも大きい方式と、どちらも小さい方式では、改善の手の打ち方がまったく違うからです。
手元で確かめる:公式データから「メモリ税」を数える
論文の著者は、全方式の「問題ごとの正誤」を Hugging Face で公開しています(CC BY 4.0)。これを使って、上の4分類を数えるスクリプトを作り、論文の数字を再計算してみました。
確認した環境
| 項目 | 内容 |
|---|---|
| OS | macOS(Apple Silicon) |
| Python | 3.9(標準ライブラリだけで動く) |
| 使ったデータ | Hugging Face の MemoryTax-Res-Qwen3-6-27B ほか2つ(判定ファイル judge.jsonl) |
| 確認日 | 2026年10月29日 |
1. 判定データを取ってくる
例として、Qwen3.6-27B で LongMemEval-s を解いたときの「RAG(1回検索)」と「SimpleMem」の正誤ファイルを取得します。作業用のフォルダを作ってから実行してください。
mkdir -p memory-tax-check/data && cd memory-tax-check
# 公式の結果データ(Hugging Face)から、問題ごとの正誤ファイルを2つ取得する
BASE=https://huggingface.co/datasets/hexuan21/MemoryTax-Res-Qwen3-6-27B/resolve/main
curl -sSL -o data/rag.jsonl "$BASE/naive_baselines/res_rag_hybrid_1rd_longmemeval_s_27b/judge_res/judge.jsonl"
curl -sSL -o data/simplemem.jsonl "$BASE/res_simplemem_longmemeval_s_27b/judge_res/judge.jsonl"
# 1行1問、verdict が 1 なら正解
head -c 200 data/rag.jsonl
2. 利益と税を数えるスクリプト
次のスクリプトを memory_tax.py として保存します。基準と比較対象の採点結果を渡すと、4つに分けて数えます。自分のエージェントの採点結果でも、1行1問の JSONL にすれば同じように使えます。
"""メモリの「利益」と「税」を、問題ごとの正誤から数える。
基準(例:素の RAG)と比較対象(例:メモリ付きエージェント)の採点結果を
JSONL で 1 行 1 問ずつ渡すと、次の 4 つに分けて数える。
両方正解 / メモリだけ正解(利益) / 基準だけ正解(税) / 両方不正解
使い方:
python3 memory_tax.py --base rag.jsonl --mem memory.jsonl
python3 memory_tax.py --base rag.jsonl --mem memory.jsonl --id-key id --ok-key correct
各行は {"question_id": "q1", "verdict": 1} のような形(キー名は引数で変えられる)。
正誤は 1/0、true/false、"CORRECT"/"WRONG" のどれでもよい。
"""
import argparse
import json
import re
def norm_id(qid):
"""公式データは方式ごとに ID の書き方が少し違う("persona10#q0" と "persona10#0"、
"dialogue__o3__user_103#14" と "o3__user_103#q14")ので、同じ形にそろえる。"""
qid = re.sub(r"^(dialogue|state|github|wiki)__", "", str(qid))
qid = re.sub(r"#q(\d+)$", r"#\1", qid)
# MINT-Eval の GitHub・Wiki 系は "a/b" と "a-b"、"Windsor Castle" と "Windsor_Castle_1a2b3c4d" の揺れがある
sample, _, num = qid.rpartition("#")
sample = re.sub(r"_[0-9a-f]{8}$", "", sample)
return re.sub(r"[^0-9a-z]+", "_", sample.lower()).strip("_") + "#" + num
def load(path, id_key, ok_key):
result = {}
with open(path, encoding="utf-8") as f:
for line in f:
if not line.strip():
continue
row = json.loads(line)
v = row[ok_key]
if isinstance(v, str):
v = v.strip().upper() in ("1", "TRUE", "CORRECT", "YES")
result[norm_id(row[id_key])] = bool(v)
return result
def compare(base, mem, ids=None):
"""基準 base と比較対象 mem の正誤(dict: id -> bool)から 4 分類を数える。"""
common = sorted(set(base) & set(mem) if ids is None else set(ids))
n = len(common)
both = sum(base[i] and mem[i] for i in common)
benefit = sum(mem[i] and not base[i] for i in common)
tax = sum(base[i] and not mem[i] for i in common)
wrong = n - both - benefit - tax
return {
"n": n,
"both_correct": both,
"benefit": benefit,
"tax": tax,
"both_wrong": wrong,
"net": benefit - tax,
"base_acc": 100 * (both + tax) / n,
"mem_acc": 100 * (both + benefit) / n,
"benefit_pct": 100 * benefit / n,
"tax_pct": 100 * tax / n,
}
def main():
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument("--base", required=True, help="基準(例:素の RAG)の採点結果 JSONL")
p.add_argument("--mem", required=True, help="比較対象(例:メモリ付き)の採点結果 JSONL")
p.add_argument("--id-key", default="question_id")
p.add_argument("--ok-key", default="verdict")
a = p.parse_args()
base = load(a.base, a.id_key, a.ok_key)
mem = load(a.mem, a.id_key, a.ok_key)
missing = len(set(base) ^ set(mem))
r = compare(base, mem)
print(f"共通の問題数 {r['n']}" + (f"(片方にしかない問題 {missing} 件は除外)" if missing else ""))
print(f"正答率 基準 {r['base_acc']:.1f}% → メモリ {r['mem_acc']:.1f}%")
print(f"両方正解 {r['both_correct']}")
print(f"メモリだけ正解(利益){r['benefit']} ({r['benefit_pct']:.1f}%)")
print(f"基準だけ正解(税) {r['tax']} ({r['tax_pct']:.1f}%)")
print(f"両方不正解 {r['both_wrong']}")
print(f"差し引き(純価値) {r['net']:+d} 問")
if __name__ == "__main__":
main()
norm_id は、公式データの中で方式ごとに問題 ID の書き方が少しずつ違うのをそろえるための関数です。自分のデータで ID がそろっているなら、そのままでも問題ありません。
3. 実行する
python3 memory_tax.py --base data/rag.jsonl --mem data/simplemem.jsonl
実行結果は次のとおりです。
共通の問題数 500
正答率 基準 77.6% → メモリ 73.6%
両方正解 328
メモリだけ正解(利益)40 (8.0%)
基準だけ正解(税) 60 (12.0%)
両方不正解 72
差し引き(純価値) -20 問
論文の図2(328・40・60・72、差し引き −20)、図3と表4の税(12.0%)と一致しました。A-Mem(差し引き +9)、PlugMem(−9)、OpenViking(−94)、Mem-T(−13)も、ファイル名の simplemem を入れ替えて同じ数になることを確かめています。
4. 99通り全部を数え直した結果
同じ方法で、3モデル分の判定ファイル134個をすべて取得し、全組み合わせを再計算しました(取得と集計のスクリプトは記事末の「再現用ファイル」にまとめています)。
| 確認したこと | 結果 |
|---|---|
| 判定ファイルがあった組み合わせ | 99通り中92通り(OpenViking と PlugMem の PersonaMem-v2、LightMem の MINT-Eval(GPT-5.6 Luna)は判定ファイルが未公開) |
| 2回検索の RAG を下回った数 | 92通り中83通り。未公開の7通りは論文の値でいずれも下回っており、合わせて90通り(論文・公式ページと一致) |
| 1回検索の RAG を下回った(純価値がマイナス)数 | 92通り中68通り |
| 利益と税の幅 | 利益 4.0〜18.5%、税 5.8〜43.4% |
| 論文の表1との違い | 92通り中2か所だけ。GPT-5.6 Luna の MemOS(PersonaMem-v2)が公開データでは43.4%(論文は41.7%)、Qwen3.5-4B の2回検索 RAG(MINT-Eval)が25.9%(論文は26.1%)。どちらも「下回った数」は変わりません |
5. おまけ:「原文へ戻る道」の最小の模型
工夫1の考え方を、標準ライブラリだけで動く小さな模型にしました。メモリの中身は論文の事例をまねて手で書いたもので、実在のメモリ方式の出力ではありません。本番では検索に埋め込み+BM25、並べ直しにリランカーを使います。
"""「原文へ戻る道」(論文 6.1 の src-aug)の最小の模型。標準ライブラリだけで動く。"""
import math
from collections import Counter
# 会話の原文(日付つきで 1 発言ずつ)
RAW = [
"2026-05-26 ユーザー: 空港からホテルまでタクシーだと60ドルくらいと言われて、ちょっと高いなと思っています。",
"2026-05-26 ユーザー: 荷物はスーツケース1つとリュックです。",
"2026-06-02 ユーザー: このメールを英語にして「日曜の午後はパン生地をこねるのが楽しみなので、会議は月曜にしてください」",
"2026-06-10 ユーザー: 来月の出張は大阪で、3泊の予定です。",
]
# メモリ(要約・整理したもの)。1 つ目は金額が落ち、3 つ目は原文に埋もれた好みが表に出ている
MEMORY = [
"移動はコスパを重視し、荷物の量も考えて交通手段を選ぶ。",
"来月、大阪へ3泊の出張がある。",
"週末の楽しみはパン作り(日曜の午後に生地をこねる)。",
]
def grams(text):
"""日本語は単語の区切りがないので、2 文字ずつ区切って数える。"""
t = "".join(text.split())
return [t[i:i + 2] for i in range(len(t) - 1)]
def bm25(query, docs, k1=1.5, b=0.75):
toks = [grams(d) for d in docs]
avg = sum(map(len, toks)) / len(toks)
df = Counter(g for t in toks for g in set(t))
scores = []
for t in toks:
tf = Counter(t)
s = 0.0
for g in set(grams(query)):
if g in tf:
idf = math.log(1 + (len(docs) - df[g] + 0.5) / (df[g] + 0.5))
s += idf * tf[g] * (k1 + 1) / (tf[g] + k1 * (1 - b + b * len(t) / avg))
scores.append(s)
return scores
def search(query, docs, k):
s = bm25(query, docs)
return [docs[i] for i in sorted(range(len(docs)), key=lambda i: -s[i])[:k]]
def src_aug(query, k=2):
"""メモリと原文から k 件ずつ集め、まとめて並べ直して上位 k 件を返す。"""
pool = search(query, MEMORY, k) + search(query, RAW, k)
return search(query, pool, k) # 本番ではここをリランカーにする
if __name__ == "__main__":
for q in ["空港からホテルまでのタクシー代はいくらと言われた?",
"週末の楽しみは何?"]:
print("質問:", q)
for name, hits in [("メモリだけ", search(q, MEMORY, 2)),
("原文だけ", search(q, RAW, 2)),
("メモリ+原文", src_aug(q))]:
print(f" [{name}]")
for h in hits:
print(" -", h)
print()
src_aug_demo.py として保存し、python3 src_aug_demo.py で実行すると次のようになります。
質問: 空港からホテルまでのタクシー代はいくらと言われた?
[メモリだけ]
- 移動はコスパを重視し、荷物の量も考えて交通手段を選ぶ。
- 来月、大阪へ3泊の出張がある。
[原文だけ]
- 2026-05-26 ユーザー: 空港からホテルまでタクシーだと60ドルくらいと言われて、ちょっと高いなと思っています。
- 2026-05-26 ユーザー: 荷物はスーツケース1つとリュックです。
[メモリ+原文]
- 2026-05-26 ユーザー: 空港からホテルまでタクシーだと60ドルくらいと言われて、ちょっと高いなと思っています。
- 移動はコスパを重視し、荷物の量も考えて交通手段を選ぶ。
質問: 週末の楽しみは何?
[メモリだけ]
- 週末の楽しみはパン作り(日曜の午後に生地をこねる)。
- 移動はコスパを重視し、荷物の量も考えて交通手段を選ぶ。
[原文だけ]
- 2026-06-02 ユーザー: このメールを英語にして「日曜の午後はパン生地をこねるのが楽しみなので、会議は月曜にしてください」
- 2026-05-26 ユーザー: 空港からホテルまでタクシーだと60ドルくらいと言われて、ちょっと高いなと思っています。
[メモリ+原文]
- 週末の楽しみはパン作り(日曜の午後に生地をこねる)。
- 2026-06-02 ユーザー: このメールを英語にして「日曜の午後はパン生地をこねるのが楽しみなので、会議は月曜にしてください」
1つめの質問では、メモリだけだと「60ドル」がどこにもありませんが、原文を混ぜると最上位に戻ってきます。2つめの質問では、メールの翻訳依頼に埋もれていた好みを、メモリが表に出してくれています。両方を混ぜると、税を補いながら利益も残せることが、小さな例でも確かめられます。
読むときの注意点
この論文の結果を自分の判断に使うときは、次の点に気をつけてください。
- 査読前のプレプリントです。数字や結論は今後の改訂で変わる可能性があります
- ベンチマークは英語の、固定された会話データです。日本語の会話や、質問と会話が入り混じりながら長く続く実運用は調べられていません
- 答えるモデルは GPT-5.6 Luna(推論 low)までで、より強い最新モデルは費用の都合で含まれていません。一部のベンチマークは抜き出した部分集合です
- 各メモリは公式の設定に近づけて動かしていますが、方式ごとに細かく調整すれば成績が上がる可能性は論文も認めています
- 公式リポジトリは、各方式を再現するコードを「今後公開」としています(2026年10月29日時点)。この記事で再計算できたのは、公開済みの正誤データからの集計までです
- 「メモリは不要」という結論ではありません。好みの推測や複数の会話にまたがる計算、作業手順の蓄積など、メモリが RAG に勝つ場面もはっきりあります
まとめ
- 11種類のエージェントメモリを、手を抜かない素の RAG と同じ条件で比べると、99通り中90通りで RAG を下回り、記憶づくりの費用はけた違いに大きかった
- メモリには、整理で解けるようになる「利益」と、整理で解けなくなる「税」の両方がある。多くの組み合わせで税が利益を上回っていた
- 税は、作る段階での情報の欠落(忠実性)、残っていても見つからないこと(取り出しやすさ)、モデルを替えたときの揺れ(頑健性)から生まれ、伸びしろの少ないベンチマークではさらに目立つ
- いちばん効いた対策は、原文を捨てずに記憶と並べて検索し、まとめて並べ直すこと。SimpleMem では税が12.0%から5.2%に下がった
- メモリを入れる前に、まず素の RAG を物差しにし、問題ごとに利益と税を数える。この習慣だけでも、凝った仕組みにお金をかけすぎる失敗を防げる
次の一歩としては、自分のサービスでよく来る質問を集め、素の RAG とメモリの両方で採点して memory_tax.py で数えてみるのがおすすめです。税の出た問題を数件読むだけでも、自分のメモリがどんな情報を落としているかが見えてきます。
参考リソース
- 論文:Memory Tax: Dissecting the Net Value of Agent Memory(arXiv:2610.34073)
- 公式プロジェクトページ:hexuan21.github.io/MemoryTax
- 公式リポジトリ:github.com/hexuan21/MemoryTax
- 実験結果データ:huggingface.co/datasets/hexuan21/MemoryTax-Res
- ベンチマーク:LongMemEval(Wu ほか、2025)、PersonaMem-v2(Jiang ほか、2025)、MINT-Eval(Lee ほか、2026)
再現用ファイル
この記事の再計算に使ったスクリプトは次の4つです。
| ファイル | 役割 |
|---|---|
fetch_judge.py | 3モデル分の判定ファイル judge.jsonl を Hugging Face から取得する |
memory_tax.py | 基準とメモリの正誤から、利益・税・純価値を数える(本文に全文掲載) |
reproduce_paper.py | 全組み合わせを数え直し、表1の正答率と「RAG を下回った数」を出す |
src_aug_demo.py | 原文へ戻る道の最小の模型(本文に全文掲載) |
本文中の図はすべて筆者作成です。数値は論文(CC BY 4.0)と公式の結果データから引用し、事例の文章は日本語に要約しています。
