In Silico

AIエージェント

エージェント記憶の単位は、矛盾を誰が解くかを決める

2026/9/12

目次
※ 概念図(背景→問い)・作図:AI 【背景】 エージェントの記憶では、古い事実と新しい事実が必ずぶつかる矛盾を解く仕事は、消せない 【問い】 記憶の単位を文にするか辺にするかで、その仕事はどこへ行くか書き込むときに解くか、読むたびに解くか
※ 概念図(背景→問い)・作図:AI。実データではない値を一つも含まない。エージェントの記憶では古い事実と新しい事実が必ずぶつかり、矛盾を解く仕事は消せないという背景から、記憶の単位を文にするか辺にするかでその仕事がどこへ行くのかという問いへ進む構図を描いた図であり、本文で引く各一次資料そのものの主張ではない。
背景

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

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

見るべき場所は一つで、記憶の一件が何であるかというデータモデルである。一件が自然言語の文なのか、二つの実体をつなぐ辺なのか。その形が決まると、矛盾をどこで解くかは、ほぼ自動的に決まってしまう。

問い

記憶の単位を文にするか辺にするかで、矛盾を解く仕事はどこへ行くのか。

要点

文を単位にすると矛盾は読む側に残り、有効期間つきの辺を単位にすると矛盾は書き込む側で解かれる。 文を貯めるmem0は、新旧の文を並べたまま残し、どちらが今かの判断を記憶を読むLLMに毎回させる1。辺を貯めるgraphitiは、取り込みの時点で古い辺に「いつまで真だったか」を書き込み、辺は消さずに残す2。どちらが良いかは、「過去の時点で何が真だったか」を問う用途があるかで決まる。

二つの実装の立ち位置

mem0は、Mem0社が開発するオープンソースのエージェント記憶ライブラリで、GitHubのスターは6万件を超える(2026年9月9日時点で64,995件)1。graphitiは、商用のエージェント記憶サービスZepを提供する会社が公開するオープンソースのフレームワークで、スターは3万件台(同日30,726件)2。どちらも設計を説明する論文が2025年にarXivへ投稿されており、mem0の論文は査読会議ECAI 2025に採録され、Zepの論文は査読を経た掲載が確認できていないプレプリントである34。エージェントの長期記憶を担う道具として同じ棚に並ぶが、貯める物の形が違う。

mem0の記憶は一文で、矛盾は読む側に残る

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

時点の情報は、文の中に書き込まれる。抽出プロンプトは会話の日付を渡し、「先週」のような相対表現をその日付に解決させる。理由も書いてある。「“User went to Paris last week” is useless 6 months later. “User went to Paris the week of May 15, 2023” is meaningful forever.」1。文の外に時間の置き場が無いので、文の中に置くしかない。

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

つまり「担当はA」と「担当はBに交代した」は両方残り、両方が同時に検索結果へ入りうる。どちらが今かは、記憶を読んで答えを作るLLMが、文の中の日付を手がかりに毎回判断する。矛盾を解く仕事は消えたのではなく、読む側へ移っている。

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

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

graphitiの記憶一件は、二つの実体をつなぐ辺であり、辺が事実の文と四つの時刻を持つ。valid_at(事実が真になった時点)、invalid_at(真でなくなった時点)、created_at(システムが記録した時点)、expired_at(システムが引退させた時点)である2。Zepの論文はこれを二本の時間軸と呼ぶ。世界で事実が真だった期間と、システムがそれを真だと思っていた期間を、別々に持つ4。だから「今年6月の時点で、担当は誰だと記録されていたか」という問いが立つ。

矛盾は取り込みで解かれ、分業がはっきりしている。新しい事実が来ると、似た辺を検索で集め、LLMが一回の呼び出しで「重複」と「矛盾」に振り分ける。次に日付の比較が向きを決める。矛盾に挙がった辺のうち、新しい辺より前から真だったものだけを引退させ、そのinvalid_atへ新しい辺のvalid_atを書き込む2。矛盾があるかどうかはLLMが挙げ、どちらが後かは日付が決める。LLMが新旧を取り違えても、日付が正しければ向きは壊れない。古い辺は消えず、いつまで真だったかを持って残る。

代償は二つある。一つは取り込みが重いことで、実体の同定にも、矛盾の判定にも、日付の解決にもLLM呼び出しが要る42。もう一つは日付への依存で、両方のvalid_atが空なら比較が成り立たず、無効化は起きない。時間を欄に持つ設計は、欄を埋める仕事を必ず抱える。

ただし、解かれた矛盾は読む側からも見える。既定の検索は引退した辺も返し、各事実にvalid_atinvalid_atを添えたうえで、「invalid_atがPresentなら有効」という規則をLLMへの文脈文字列に書く2。読む側の判断が消えるのではなく、判断に使える欄が渡される。

単位を選ぶことは、矛盾を解く場所を選ぶことである

文を単位にすれば、矛盾は文の中の日付と読む側のLLMに残る。辺を単位にすれば、矛盾は取り込みの時点で欄に書かれ、辺は履歴として残る。 どちらを選んでも、矛盾を解く仕事そのものは消えない。書き込みで一度払うか、読み出しのたびに払うかが変わる。

二つの設計を成績で比べたくなるが、公開されている数字はどちらも作り手が自分の実装を測ったもので、ベンチマークも違い、順位は出ない34。選ぶときに見るのは、その用途で「過去の時点で何が真だったか」に値段が付くかである。付くなら、時間を欄に持つ設計しか答えを出せない。付かないなら、文の集合と、それを読むLLMで足りる。取り込みは軽く、判定の誤りで根拠を失う事故も起きない。


出典4件
  1. mem0ai/mem0 のソース(mainブランチを2026年9月9日にraw.githubusercontent.comから取得。同日のGitHub APIでstar 64,995件、説明文は「The Memory Layer for AI Agents - Drop-in memory infrastructure for AI agents and apps. Context that persists. Built for production.」)。mem0/memory/main.py_add_to_vector_store()# === V3 PHASED BATCH PIPELINE ===のコメントに続けてsystem_prompt = ADDITIVE_EXTRACTION_PROMPTを置き、get_update_memory_messagesDEFAULT_UPDATE_MEMORY_PROMPTもこのファイルに一度も現れない(grepで0件)。mem0/configs/prompts.pyADDITIVE_EXTRACTION_PROMPTは「You are a Memory Extractor」「Your sole operation is ADD」と役割を限定し、時間について「Resolve ALL relative references against Observation Date」「CRITICAL: “User went to Paris last week” is useless 6 months later. “User went to Paris the week of May 15, 2023” is meaningful forever.」、リンクについて「Contradiction: New information that conflicts with an existing memory」(ただし_add_to_vector_store()が応答から使うのはmem.get("text")mem.get("attributed_to")だけで、linked_memory_idsは記憶のpayloadに入らない——同ファイルのlinked_memory_idsはすべて実体行のもの)、方針について「When in doubt, extract. A slightly redundant memory is far less costly than a missing one.」と書く。重複弾きのうち機械的なものはmem_hash = hashlib.md5(text.encode()).hexdigest()の完全一致だけである。意味レベルの重複排除は同じプロンプトがLLMへ指示しており、「This is your primary deduplication reference — do not re-extract information already captured here.」「Use these ONLY for deduplication and linking — do NOT extract new memories from Existing Memories.」「If new information in New Messages is semantically equivalent to an Existing Memory with no meaningful new context, skip it.」「Two memories about the same fact phrased differently are redundant — keep the richer one and drop the other.」と書く。取り出しはmem0/utils/scoring.pyscore_and_rank()で、docstringが「combined = (semantic + bm25 + entity_boost) / max_possible」と書き、ENTITY_BOOST_WEIGHT = 0.5、除数は意味のみ1.0/BM25込み2.0/実体込み2.5/BM25なし実体あり1.5。時間に対応する項は同ファイルに無い。明示的な期限だけはmain.pydef _payload_is_expiredがあり、payloadのexpiration_dateが今日より前なら検索から外す。実体の同定もadd()の中で走る。main.pyfrom mem0.utils.entity_extraction import extract_entities, extract_entities_batchを読み込み(entity_extraction.pyの冒頭は「Entity extraction from text using spaCy NLP.」)、_upsert_entity()_normalize_entity_text" ".join(value.strip().lower().split()))の完全一致を先に引き、外れたときだけself.entity_store.search(query=entity_text, vectors=entity_embedding, top_k=1, ...)を走らせてsemantic_match = existing[0] if existing and existing[0].score >= 0.95 else Noneで同一実体と判定し、linked_memory_idsへ記憶IDを足す。LLM呼び出しはこの経路に無い。比較のため変更前のコミット57f944e18ad8mem0/memory/main.pyも取得して読んだ。そこではfunction_calling_prompt = get_update_memory_messages(でプロンプトを組み、event_type = resp.get("event")のあとADDUPDATEself._update_memory()・DELETEself._delete_memory(memory_id=memory_id))・NONEへ分岐している。https://github.com/mem0ai/mem0 2 3 4 5 6 7 8

  2. getzep/graphiti のREADMEとソース(mainブランチを2026年9月9日にraw.githubusercontent.comから取得。同日のGitHub APIでstar 30,726件、説明文は「Build Real-Time Knowledge Graphs for AI Agents」)。READMEは「Temporal Fact Management: Facts have validity windows. When information changes, old facts are invalidated — not deleted. Query what’s true now, or what was true at any point in time.」と書き、GraphRAGとの比較表では時間の扱いを「Explicit bi-temporal tracking with automatic fact invalidation」、矛盾の扱いを「Automatic fact invalidation with temporal history preserved」と記す。欄はgraphiti_core/edges.pyEntityEdgeにあり、valid_atは「datetime of when the fact became true」、invalid_atは「datetime of when the fact stopped being true」、expired_atは「datetime of when the node was invalidated」(説明文の語はnodeだが、フィールドはEntityEdgeのもの)。同クラスにはもう一つreference_timeがあり、説明文は「reference timestamp from the episode that produced this edge」。矛盾処理はgraphiti_core/utils/maintenance/edge_operations.pyにあり、resolve_extracted_edges()が重複候補(両端が同じ辺に絞った検索)と無効化候補(SearchFilters()を空で渡したEDGE_HYBRID_SEARCH_RRF検索)を集め、prompt_library.dedupe_edges.resolve_edge(context)の1回の呼び出しがduplicate_factscontradicted_factsを返す。graphiti_core/prompts/dedupe_edges.pyEdgeDuplicateは後者を「contradicted_facts: idx values from EITHER list (EXISTING FACTS or FACT INVALIDATION CANDIDATES)」と定義する。向きの決定はresolve_edge_contradictions()で、edge.invalid_at = resolved_edge.valid_atedge.expired_at = edge.expired_at if edge.expired_at is not None else utc_now()を実行する。逆向きの分岐はresolve_extracted_edge()にあり、コメントは「# Expire new edge since we have information about more recent events」。日付の付与は_extract_edge_timestamps()が担い、if edge.valid_at is not None or edge.invalid_at is not None: returnで既に日付がある辺は飛ばす。検索の既定はgraphiti_core/search/search_filters.pySearchFiltersで、valid_at: list[list[DateFilter]] | None = Field(default=None)のように4つの日付欄がすべてdefault=Nonegraphiti_core/search/search.pyexpired_atという文字列は現れない(grepで0件)。取り出した結果をLLMへ渡すgraphiti_core/search/search_helpers.pysearch_results_to_context_string()は、各事実を{'fact': edge.fact, 'valid_at': str(edge.valid_at), 'invalid_at': str(edge.invalid_at or 'Present')}の形で並べ、文脈文字列に「Facts are considered valid between their valid_at and invalid_at dates.」(ソースではvalidのあとで改行)と「Facts with an invalid_at date of “Present” are considered valid.」という規則を添える。⚠ READMEはZep(商用サービス)とgraphiti(OSSフレームワーク)を別物として並べており、本稿が読んだのは後者のコードである。https://github.com/getzep/graphiti 2 3 4 5 6

  3. P. Chhikara, D. Khant, S. Aryan, T. Singh & D. Yadav, “Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory”, arXiv:2504.19413(2025年4月28日投稿。著者はMem0所属。査読会議ECAI 2025の論文集に掲載されている。IOS Press『Frontiers in Artificial Intelligence and Applications』所収、DOI 10.3233/faia251160、2025年10月21日発行。本稿はarXiv版v1から引く)。抽出段は「The extraction phase initiates upon ingestion of a new message pair」で始まり、更新段は「the system first retrieves the top s semantically similar memories using vector embeddings from the database」に続き、操作の定義が「four distinct operations to execute: ADD for creation of new memories when no semantically equivalent memory exists; UPDATE for augmentation of existing memories with complementary information; DELETE for removal of memories contradicted by new information; and NOOP when the candidate fact requires no modification to the knowledge base」。Appendix BのAlgorithm 1はDELETEの分岐をm_i ← FindContradictedMemory(f, M)M ← M \ {m_i}と書き、「Remove contradicted information」と注記する。抄録は「We introduce two memory architectures for AI agents.」と構成が二つあることを宣言し、グラフ構成の矛盾処理は§2.2末尾にある。「An LLM-based update resolver determines if certain relationships should be obsolete, marking them as invalid rather than physically removing them to enable temporal reasoning.」成績はLOCOMOベンチマークで自社の二構成(文のみの基本構成と、グラフを足した構成)と他手法を比べたもので、Zepについては「Zep’s memory graph consumes in excess of 600k tokens」等を報告するが、競合を当事者が測ったものなのでZepの成績の証拠としては使っていない。⚠ 数値は著者ら自身の測定であり、第三者の再現ではない。留保として引く理由: この論文が測ったADD/UPDATE/DELETE/NOOPの構成は、現在のOSSパッケージには存在しない(1で確認)。∴ 論文の成績は、いまのmem0aiを選ぶ根拠としては使えない。https://arxiv.org/abs/2504.19413 2 3 4

  4. P. Rasmussen, P. Paliychuk, T. Beauvais, J. Ryan & D. Chalef, “Zep: A Temporal Knowledge Graph Architecture for Agent Memory”, arXiv:2501.13956(2025年1月20日投稿。著者はZep所属。査読誌・会議への掲載は確認できていない=プレプリントとして引く)。グラフの定義は「a temporally-aware knowledge graph」で、「This graph comprises three hierarchical tiers of subgraphs: an episode subgraph, a semantic entity subgraph, and a community subgraph.」。実体の同定にLLMを使うことは「the system embeds each entity name into a 1024-dimensional vector space」「The system also performs a separate full-text search on existing entity names and summaries to identify additional candidate nodes.」に続けて候補と文脈をLLMへ渡して解決させる記述から。二本の時間軸は「bi-temporal model, where timeline T represents the chronological ordering of events, and timeline T′ represents the transactional order of Zep’s data ingestion」と定義し、「the system tracks four timestamps: t′created and t′expired ∈ T′ monitor when facts are created or invalidated in the system, while t_valid and t_invalid ∈ T track the temporal range during which facts held true」。成績は「In the DMR benchmark, which the MemGPT team established as their primary evaluation metric, Zep demonstrates superior performance (94.8% vs 93.4%)」と「Zep achieves substantial results with accuracy improvements of up to 18.5%」を報告するが、同じ論文の§4.2が自分でこう評価している。「these results must be contextualized: each conversation contains only 60 messages, easily fitting within current LLM context windows.」「The high performance achieved by simple full-context approaches using modern LLMs further highlights the benchmark’s inadequacy for evaluating memory systems.」。18.5%の比較相手はTable 2の「Full-context / gpt-4o / 60.2%」対「Zep / gpt-4o / 71.2%」=Zep自身の全文投入ベースラインで、競合との比較になっていないことは§4.3.1が自ら書いている(「we were unable to achieve successful question responses using this approach」)。∴ 本稿は成績を設計の優劣の根拠に使っていない。⚠ 数値はZepの著者ら自身の測定であり、第三者の再現ではない。留保として引く理由: 本稿がgraphitiのコードから読んだ機構の利点を、この論文は自社の実装を自社が測った成績として報告しており、しかも測定対象はOSSのgraphiti単体ではなくZepである。https://arxiv.org/abs/2501.13956 2 3 4

この記事はAIが執筆しています。内容には誤りが含まれる可能性があります。ご注意ください。