In Silico

AIエージェント

AIエージェントの長期記憶の仕組み:mem0とZep(graphiti)比較

2026/9/12 (更新: 2026/10/27)

  • AIエージェントの長期記憶
  • 長期記憶
  • LLM
  • mem0
  • graphiti
  • Zep
  • 有効期間
  • 矛盾
  • valid_at
  • invalid_at
  • 時間軸
  • 取り込み
  • 抽出プロンプト
  • ベクトルストア
  • 埋め込み
  • エッジ
  • エンティティ
  • LOCOMO
  • LongMemEval
  • MemGPT
  • A-Mem
  • LangMem
  • ChatGPT
  • 時間推論
  • データモデル
  • 追加専用
  • 重複
  • 無効化
目次
背景・問い・要点
背景

AIエージェントは、利用者の依頼を大規模言語モデル(LLM、Large Language Model)に渡し、LLMの出力に従って返答や操作を行うプログラムである。LLMは呼び出しのたびに、そのとき渡された文章だけをもとに答える。そのため、前の会話で利用者が話した事実は、何らかの形で渡し直さなければ、次の会話では使えない。

長期記憶は、この渡し直しを受け持つ仕組みである。書き込みでは、会話の発言を受け取り、LLMにそこから事実を取り出させて、一件ずつ保存する。読み出しでは、後の会話で利用者が質問したときに、質問と意味の近い件を検索して取り出し、LLMに渡す文章に添える。こうしてエージェントは、何週間も前に聞いたことを使って答えられる。

長期記憶を持たせると、すぐに一つの困りごとに当たる。利用者が先週「担当はAです」と言い、今週「Bに交代しました」と言ったとする。両方が記憶に残っているなら、次の会話でどちらを信じるかを、エージェントが決めなければならない。古いほうを消していれば、「いつまでAだったか」には二度と答えられない。矛盾した事実をどう扱うかは、記憶を持たせた時点で必ず生じる問題で、避ける方法はない。

この仕事を、記憶に書き込むときに済ませるか、記憶を読み出すたびにやり直すか。どの実装もどちらかを選んでいるが、機能の一覧に「矛盾を解決する」と書いてあっても、いつ誰が解いているかまでは書いていない。

見るべき場所は一つで、記憶の一件がどんな欄を持つかという取り決め(データモデル)である。一件がただの文なのか、その事実が真だった期間(有効期間)を、始まりと終わりの日付として入れる欄を持つのか。その形が、矛盾を書き込むときに解けるか、解くなら何を失うかを分けている。

本稿は、オープンソースソフトウェア(OSS。ソースコードが公開され、誰でも使えるソフトウェア)の二つの実装を例に取る。mem0は記憶の一件を一つの文に取る。graphitiは一件をエッジに取る。エッジは、記憶に出てくる人や物(エンティティと呼ぶ)のうち二つをつなぐ関係の一件で、事実の文と有効期間の欄を持つ。どちらの作り手も設計の考え方を論文とREADMEに書いているので、読者は原典に当たって中身を検算できる。

問い

mem0とgraphitiの長期記憶は、矛盾した事実をどこで解いているのか。

要点

mem0のOSSは矛盾する文も消さずに積み、どちらが今かは読む側が読むたびに解く。graphitiは一件に有効期間の欄を持たせ、書き込むときに古い件を消さずに終わりの日付を埋めて解く。

文だけの記憶で書き込み時に解くには古い文を消すか書き換えるしかなく、古い時点が残るかは書き込むLLM次第になる。mem0のOSSは取り込みを一回のLLM呼び出しで済ませる代わりに、今の状態を問うたびに読む側が新旧を見分ける。graphitiは取り込みのたびに欄を埋めるLLM呼び出しが要り、新旧どちらかの日付が空だと解けないが、過去の時点で何が真だったかは欄を引くだけで答えが出る。

モデル・例示

担当の交代を、三つの記憶の持たせ方で書き込む

※ この節の例は、説明のために作った架空のものです。

担当の交代という一つの矛盾を、一件の形だけを変えて三通りに書き込むと、矛盾を解く仕事が書き込みに入るか読み出しに入るかが形ごとに変わる。どの形でも、発言が一件として書き込まれ、質問に近い件が読み出されるという動きは同じである。

利用者が9月1日に「担当はAです」と言い、9月15日に「Bに交代しました」と言ったとする。9月20日に、「今の担当は誰か」と「9月10日の担当は誰か」の二つを問う。

一つ目は、文だけを貯めて何も消さない持たせ方である。この記憶が二回書き込み、一回読み出す流れを、順にたどる。

  1. 利用者エージェント1. 9月1日に「担当はAです」と言う
  2. エージェント長期記憶2. 発言と日付を渡す
  3. 長期記憶3. LLMが事実を取り出し、「9月1日、担当はA」の一件を保存する
  4. 利用者エージェント4. 9月15日に「Bに交代しました」と言う
  5. エージェント長期記憶5. 同じく発言と日付を渡し、LLMが「9月15日、担当はBに交代」の一件を保存する
  6. 利用者エージェント6. 9月20日に「今の担当は誰か」と問う
  7. エージェント長期記憶7. 問いの文で検索する
  8. 長期記憶エージェント8. 問いに近い二件を返す

記憶が返したのは「9月1日、担当はA」と「9月15日、担当はBに交代」の二件である。エージェントはこの二件を問いに添えてLLMに渡し、LLMが文中の日付を比べて「今はB」と答える。「9月10日の担当」も、同じ二件の日付を比べれば「A」と出る。矛盾を解いたのは、読み出しの後で二件を比べたLLMである。検索が片方の件を返し損ねると、答えを誤る。

二つ目は、文だけを貯め、矛盾した古い文を書き込みのときに消す持たせ方である。9月15日の書き込みで、記憶には「9月15日、担当はBに交代」だけが残る。「今の担当」には日付を比べずに答えられるが、「9月10日の担当」にはもう答えられない。古い文を消さずに「9月1日から9月14日まで担当はA、9月15日からはB」と書き換え、二つの時点を一文に収める道もある。そうするかどうかは、書き込むLLMの判断に任される。

三つ目は、一件に有効期間の欄を持たせる持たせ方である。9月15日の書き込みのときに、Aの件には「始まり9月1日、終わり9月15日」、Bの件には「始まり9月15日、終わりは空」と欄を埋める。終わりの日はその日を含まないので、9月15日からはBの件だけが真になる。古い件は消えず、9月20日の読み出しでは二件が欄つきで返る。LLMは文中の日付を推し量らずに、欄を見て「今はB」「9月10日はA」と答えられる。その代わり、書き込むたびに新旧を見比べて欄を埋める仕事が要る。

三つを比べると、次の三つの性質が分かる。

  1. 文だけを貯めて何も消さないと、矛盾は解かれずに残り、読むたびに読む側が解き直す。
  2. 文だけの記憶で矛盾を書き込みのときに解くと、古い文を消せば過去の時点を問う材料を失い、書き換えれば古い時点が残るかどうかは書き込むLLM次第になる。
  3. 有効期間の欄を持たせると、古い件を残したまま書き込みのときに解けるが、欄を埋める仕事が書き込みに加わる。

本稿が読む実装は、三つの形に次のように当たる。今のmem0のOSSは一つ目に当たり、矛盾する文を消さずに両方残す。Mem0の論文の基本構成は二つ目に当たり、古い文を消す。graphitiは三つ目に当たり、「担当はA」の一件が、エンティティ「利用者」と「A」をつなぐエッジになり、そこに有効期間の欄が付く。

mem0のOSSが一つ目の形と重なるのは、この点までである。OSSでは、文に入る日付が例と違うことがある。状態を述べた文には、日付が付かないこともある。日付が付くときも、OSSは「先週」のような言い方を、取り込んだ当日を基準に日付へ直す。そのため、例の日付どおりに入るのは、発言をその日のうちに取り込んだときである。

二つの実装の立ち位置

mem0は、Mem0社が開発するオープンソースのエージェント記憶ライブラリで、GitHubのスターは6万件を超える1。graphitiは、商用のエージェント記憶サービスZepを提供する会社が公開するオープンソースのフレームワークで、スターは3万件台2。どちらも設計を説明する論文が2025年にarXivへ投稿されている。mem0の論文は査読会議ECAI 2025(European Conference on Artificial Intelligence)に採録され、Zepの論文は査読を経た掲載が確認できていないプレプリントである34。

記憶の持たせ方はこの二つの外にもあり、Mem0の論文は比較の相手に、会話の記録を全部そのまま渡す構成や、記録を切って検索で引く構成を含む六つの種類を並べる3。名前で挙がるのは、MemGPT、A-Mem、LangMem、ChatGPTのメモリ機能などである3。

成績で比べたくなるが、順位は出ない。Mem0の論文は同じLOCOMOでZepも測っているが、測ったのは片方の当事者で、対象も論文の旧構成とZepの商用版だった3。LongMemEvalの数字は両社とも公開しているが、どちらも自社のサービスを自社で測ったもので、設定も違う41。

なぜ今、記憶の単位が問われるのか

記憶を持たせるには、何を一件として貯めるかを先に決めることになる。文を一件に取ったMem0の作り手と、エッジを一件に取ったZepの作り手は、似た診断から違う前提を置いた。

貯める物の形は違うが、診断は重なる。Mem0の論文は、コンテキストウィンドウが伸びても根本の限界は「先送り」されるだけだとし、実際の会話は話題の連続性をめったに保たないと書く3。Zepの論文は、既存の検索補強が「ほぼ静的なコーパス」を相手にしてきたとし、エージェントには利用者との対話に加えて業務や世界のデータから「絶えず変わり続けるデータ」が要ると書く4。

前提はそこで分かれる。Mem0の論文は何を残すかを選ぶことを記憶の定義に置き、Zepの論文はいつまで真だったかを持つことを狙いに置く。

Mem0の論文は堅牢な記憶を「重要な情報を選んで貯め、関連する概念を統合し、必要なときに関連する細部を取り出す」ものと定める3。現在のREADMEは、更新も削除も無い一回のLLM呼び出しへ改めたとし、「記憶は積み上がり、何も上書きされない」と書く1。

Zepの論文はグラフを「損失なく」更新し「事実と関係の時系列を、有効期間も含めて保つ」ことを狙いに置く4。graphitiのREADMEも冒頭で「事実が時間とともにどう変わるかを追跡する」と書く2。

mem0のOSSの記憶は一文で、矛盾は読む側が解く

mem0の記憶一件は、「User went to Paris the week of May 15, 2023」のような、それだけで読める短い文である。埋め込みベクトルとともにベクトルストアへ一件ずつ入り、エンティティや関係といった構造は持たない1。

時点の情報は、文の中に書き込まれる。抽出プロンプトは基準日を渡し、「先週」のような相対表現をその日付に解決させる。OSSの実装では基準日は取り込み当日の日付で、会話の日付は渡せない。理由は、「先週」のままでは半年後に役に立たないから、と書いてある1。OSSの実装には、事実が真だった期間を持つ欄が文の外に無いので、それは文の中に置くしかない。

矛盾は、取り込みでは解かれない。現在の実装は追加専用で、矛盾する文も両方残す。抽出プロンプトは矛盾する既存記憶のIDを「リンク」として返させるが、main.pyの取り込みはそのIDを保存せず、文と埋め込みだけを入れる1。プロンプトの方針は「迷ったら抽出せよ。少し冗長な記憶は、欠けた記憶よりはるかに安い」であり、機械的に弾かれるのはハッシュが一致する完全な重複だけである1。言い回しの違う同じ事実を捨てるかどうかは、プロンプトの指示を受けたLLMが判断する1。取り出しの順位も新しさを見ない。点数は意味の類似・語の一致・エンティティの一致の合計で、時間に対応する項は無い1。

つまり、ある事実の古い状態を述べた文と新しい状態を述べた文は両方残り、同時に検索結果へ入りうる。抽出プロンプトは、変化を書くときに新しい記憶へ、何が何に変わったかを収めさせる1。それでも古い状態を述べた文は日付を持たないことがあり、古い文だけを引いた読む側は、今の状態を取り違えうる。矛盾を解く仕事は消えたのではなく、読む側へ移っている。

著者らの論文は、これとは違う設計を書いていた。基本構成では、取り込みのたびに類似の記憶を引き、追加・更新・削除・何もしないの四つからLLMに選ばせ、矛盾した古い文は削除する3。同じ論文がもう一つ書いたグラフ構成は、古い関係を「時間推論を可能にするため、物理的に取り除くのではなく無効と印を付ける」3。現在のオープンソース実装はどちらの更新段も持たない1。矛盾した古い文を正しく消せば矛盾は解けるが、判定を誤れば根拠ごと失われる。追加だけなら根拠は残るが、矛盾も残る。論文と同じ作り手が、今のOSSでは追加だけの側へ寄せたことになる。

作り手は、本稿が読んだOSSの実装には無い時間推論の機能もブログで説明している。文を単位にしたまま、書き込み時に別の処理が各記憶へ時間の欄を付け、同じ事実の新しい状態が来ると古い記憶の終了時点を自動で埋め、何も削除しないという5。単位は文のままでも、有効期間の欄を足せば、矛盾を解く仕事は書き込み側へ移る。

graphitiの記憶はエッジで、矛盾は書き込むときに欄へ解かれる

graphitiの記憶一件は二つのエンティティをつなぐエッジで、事実の文と、Zepの論文が数える四つの時刻を持つ。valid_at(事実が真になった時点)、invalid_at(真でなくなった時点)、created_at(システムが記録した時点)、expired_at(システムが無効にした時点)である2。Zepの論文はこれを二本の時間軸と呼ぶ。世界で事実が真だった期間と、システムがそれを真だと思っていた期間を、別々に持つ4。だから、過去のある時点でシステムが何を真と記録していたかも問える。

矛盾は取り込みで解かれる。新しい事実が来ると、似たエッジを検索で集め、LLMが一回の呼び出しで「重複」と「矛盾」に振り分ける。次に日付の比較が向きを決める。矛盾に挙がったエッジのうち、新しいエッジより前から真だったものだけを無効にし、そのinvalid_atへ新しいエッジのvalid_atを書き込む2。LLMが新旧を取り違えても、日付が正しければ向きは壊れない。

代償は二つある。一つは取り込みが重いことで、エンティティの同定も矛盾の判定も、名前や文の一致で決まらないものはLLM呼び出しへ回り、日付の解決は「軽い」LLM呼び出しで行う42。もう一つは日付への依存で、新旧どちらかのvalid_atが空なら比較が成り立たず、無効化は起きない。時間を欄に持つ設計は、欄を埋める仕事を必ず抱える。

ただし、解かれた矛盾は読む側からも見える。既定の検索は無効になったエッジも返し、各事実にvalid_atとinvalid_atを添えたうえで、「invalid_atがPresentなら有効」という規則をLLMへのコンテキスト文字列に書く2。読む側の判断が消えるのではなく、判断に使える欄が渡される。

「過去の時点で何が真だったか」を問う用途なら、時間を欄に持つ設計は欄を引くだけで答えを出す。文の集合でも文中の日付を読めば推定はできるが、mem0のOSSのように取り出しの順位が時間を見なければ、取りこぼしうる。その用途が無くても、消さずに積む文の集合では、今の状態を問う質問のたびに、読む側が新旧の文を引き当て、どちらが今かを見分ける。


出典5件
  1. mem0ai/mem0(GitHub、mainを2026年9月取得). https://github.com/mem0ai/mem0 — READMEは更新も削除も無い一回の抽出を掲げ成績は管理版のものと断り、ソースは抽出時の日付解決(OSSでは取り込み当日)、保存されない矛盾リンク、時間の項が無い順位式を示す。スターは6万件超。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11

  2. getzep/graphiti(GitHub、mainを2026年9月取得). https://github.com/getzep/graphiti — Zepの論文が数えるエッジの四つの時刻、LLMが重複と矛盾に振り分けたあと日付の比較で古いエッジのinvalid_atを埋める処理、無効になったエッジも有効期間つきで返す既定の検索を示す。スターは3万件台。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  3. 「Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory」ECAI 2025. https://arxiv.org/abs/2504.19413 https://doi.org/10.3233/faia251160 — 書き込み時に追加・更新・削除・何もしないを選ぶ基本構成と、古い関係を無効と印すグラフ構成、六種類の比較相手を述べる。成績は自社測定。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8

  4. 「Zep: A Temporal Knowledge Graph Architecture for Agent Memory」arXiv:2501.13956(Zep所属の著者のプレプリント). https://arxiv.org/abs/2501.13956 — 有効期間つきで事実を損失なく更新するグラフと二本の時間軸を述べる。成績はZepのサービスの自社測定。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  5. Taranjeet Singh「Introducing Temporal Reasoning in Mem0」Mem0のブログ, 2026年5月12日. https://mem0.ai/blog/introducing-temporal-reasoning-in-mem0 — 抽出後の別処理が各記憶に時間の欄を付け、新しい状態が来ると古い記憶の終了時点を埋めて何も消さないと作り手が説明する。本稿はこの機能のコードを読んでいない。 ↩

この記事はAIを使用して執筆されました。内容には誤りが含まれる可能性があります。事実や数字の出典は脚注から確かめられます。