{
  "source_file": "0930",
  "source_platform": "Dev.to",
  "author": "Edward Izgorodin",
  "title_listed": "Is Agent Memory Just RAG With Extra Steps? We Opened the Source Code to Find Out",
  "keywords": "",
  "domain": "大模型算法",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://dev.to/izgorodin/is-agent-memory-just-rag-with-extra-steps-we-opened-the-source-code-to-find-out-349i",
  "publish_date": "2026-08-27",
  "corpus_stage": "",
  "notes": "",
  "id": "d659235c1e09",
  "collected_at": "2026-10-02T14:03:25",
  "title": "Is Agent Memory Just RAG With Extra Steps? We Opened the Source Code to Find Out - DEV Community",
  "body_markdown": "Under every agent memory launch, the same comment appears: \"so it's RAG with extra steps.\" Instead of arguing, we opened the shipping source of mem0, LangGraph, Graphiti and Generative Agents at pinned commits and read the actual read and write paths. The answer is more interesting than either side of the flame war.\nDisclosure up front: I work on Mnemoverse, a memory layer for AI agents, so this is a vendor reading competitors' code, and you should weigh it accordingly. Two rules held throughout: in the full version every code claim cites file, lines and a pinned commit, and this short tour names file and commit wherever it quotes code; and there is not a single performance number, ours or anyone's. The full version with all 40 numbered claims and sources lives in our library; this is the short tour.\nAt read time, the skeptic is right\nWhen a memory layer answers a query, here is mem0's shipping search path (\nmem0/memory/main.py\n, commit\n001c235\n): embed the query, over-fetch from a vector store, run a keyword search alongside, fuse the scores, optionally rerank.\n# Step 2: Embed query\nembeddings\n=\nself\n.\nembedding_model\n.\nembed\n(\nquery\n,\n\"\nsearch\n\"\n)\n# Step 3: Semantic search (over-fetch for scoring pool)\ninternal_limit\n=\nmax\n(\nlimit\n*\n4\n,\n60\n)\nsemantic_results\n=\nself\n.\nvector_store\n.\nsearch\n(\nquery\n=\nquery\n,\nvectors\n=\nembeddings\n,\ntop_k\n=\ninternal_limit\n,\nfilters\n=\nfilters\n)\n# Step 4: Keyword search (if store supports it)\nkeyword_results\n=\nself\n.\nvector_store\n.\nkeyword_search\n(\nquery\n=\nquery_lemmatized\n,\ntop_k\n=\ninternal_limit\n,\nfilters\n=\nfilters\n)\nEnter fullscreen mode\nExit fullscreen mode\nThat is a textbook hybrid retrieval pipeline. If you have built retrieval, you have built this. LangGraph is even more explicit: long-term memory there is a JSON document under a namespace and a key, and semantic search is opt-in. And Anthropic ships a first-party memory tool that is six file commands with no embeddings and no scoring at all: a filesystem, from the company that makes the model.\nSo the concession, said plainly because our category usually does not say it: for mem0, LangGraph and Anthropic's tool, the ranking at read time is retrieval and nothing else. Generative Agents, the fourth repository we opened, is the partial exception: its read path sums cosine relevance with a recency term and an importance score the model assigned at write time, weighted 3, 0.5 and 2 in the shipping code, and that is still a scoring function over a stored index rather than a second kind of read. If somebody drew you two different diagrams for how candidates get ranked, one of them was fiction. What differs is what reading does to the record, and that comes below.\nThe difference lives on the write path\nNot every memory layer does more than store. LangGraph's\nput()\nwrites your dict as it arrived, with no model in the path, and Anthropic's tool writes a file. In the systems that do more, mem0 with inference on, Graphiti and Generative Agents, up to three things happen on write that do not happen when you index a document. A model judges whether the thing deserves to be stored at all. The new item is reconciled against what is already there, as mem0 and Graphiti do. And the stored unit can carry a validity interval separate from when the system learned it.\nThe third one is worth seeing in code. In Graphiti (Zep's engine, a competitor of ours), every entity edge carries five datetime fields; three of them are enough to show the idea (\ngraphiti_core/edges.py\n, commit\n96ef997\n):\nexpired_at\n:\ndatetime\n|\nNone\n=\nField\n(\ndefault\n=\nNone\n,\ndescription\n=\n'\ndatetime of when the node was invalidated\n'\n)\nvalid_at\n:\ndatetime\n|\nNone\n=\nField\n(\ndefault\n=\nNone\n,\ndescription\n=\n'\ndatetime of when the fact became true\n'\n)\ninvalid_at\n:\ndatetime\n|\nNone\n=\nField\n(\ndefault\n=\nNone\n,\ndescription\n=\n'\ndatetime of when the fact stopped being true\n'\n)\nEnter fullscreen mode\nExit fullscreen mode\nTwo axes: when the system learned something, and when it was true in the world. State \"we deploy on Fridays\" in March, contradict it in June, and the March record is still there, now saying \"this used to be true, and here is when it stopped.\" Your agent can explain March's decisions after June's migration. On contradiction, nothing is deleted, so nothing has to be re-derived from a stale summary.\nCan you build the same thing with timestamps in your RAG metadata? Yes, and it works. The honest difference is where the reconciliation code lives: with metadata it is query-time logic in your application, maintained by you, growing a branch per conflict type; in a memory layer it happens once at write time in code you do not own. What is amortised, not what is achievable. Anyone selling you a capability gap here is selling something that is not there.\nWhy agents remember the wrong things\nHere is the part that made us uncomfortable. A memory layer reads from a corpus it wrote itself. Extraction writes memories, retrieval reads them, and the output feeds the next extraction. A RAG pipeline can retrieve badly, but it cannot corrupt its source: your documents were written by people, elsewhere. A memory store's extraction errors become its own retrieval corpus.\nNow add the reinforcement schemes. In every reinforcement scheme we opened, retrieval is what keeps a memory alive. Generative Agents' scored retrieval rewrites\nlast_accessed\non every node it returns (\nretrieve.py\n, commit\nfe05a71\n). MemoryBank, by its own paper, increments a memory's strength and resets its clock on recall. LangGraph ships with expiry switched off, but once an item has a time-to-live, refreshing it on every read is the default (\nlibs/checkpoint/langgraph/store/base/__init__.py\n, commit\n644815f\n):\nclass\nTTLConfig\n(\nTypedDict\n,\ntotal\n=\nFalse\n):\n\"\"\"\nConfiguration for TTL (time-to-live) behavior in the store.\n\"\"\"\nrefresh_on_read\n:\nbool\n\"\"\"\nDefault behavior for refreshing TTLs on read operations (`GET` and `SEARCH`).\n\n    If `True`, TTLs will be refreshed on read operations (get/search) by default.\nEnter fullscreen mode\nExit fullscreen mode\nPut together: a wrong fact that keeps getting retrieved is protected by the exact signal that was supposed to prune it. For a corpus the system did not write, \"frequently used means valuable\" is reasonable. For a corpus the system wrote itself, it is a feedback loop with the sign pointing the wrong way. That is the mechanism behind the complaint under every memory launch: it remembers the wrong details and applies them in the wrong places.\nWhere our own system fails this test\nOur shipped write gate scores novelty against the nearest existing memory in the same domain. It does not judge importance or factuality, which makes the name we gave it, an importance gate, wrong: the API field is called importance, the only refusal the core emits reads \"Below importance threshold\", and the number behind both is novelty. The consequence, in a personal domain, which is where a write lands by default: a correction is by nature phrased almost exactly like the thing it corrects, so it scores as a near-duplicate, so it is the one input most likely to be rejected. The stale fact survives as the sole record and, having no competitor in the store, looks more authoritative than it should. That is in our 0.8.1 changelog as a user-facing known defect. We have not solved it. Since 22 August the REST write accepts a field that names the atom it corrects, and a write declared that way skips the refusal; but the old atom is only marked, not hidden, and still competes in reads, a correction phrased like the original and not declared as one is still rejected on every surface, and the MCP tools carry no such field yet. In a shared room the gate refuses only an exact duplicate, so the same correction stores there; that is a different rule, not a fix.\nThree checks you can run this afternoon\nOn any vendor, including us. None of them is a sales-call question.\nOpen the read path.\nFind the search function and read it: an embed call, an over-fetch, a keyword branch, a fusion step means retrieval. That is fine. It is only a problem if the pricing claims it is something else.\nStore a fact, contradict it, list everything.\nOriginal still there with a closed validity interval: Graphiti-style honesty. Present but hidden: demotion, not forgetting. Gone or silently rewritten: you lost the audit trail.\nSubmit a correction phrased like the original, in a personal domain.\nIf it is not in the store afterwards, the write gate ate it and the wrong fact is now the only record. This is where we fail today in a personal domain, in public, in our changelog. Run it on us.\nThe whole argument, animated mechanism by mechanism:\nDisclosure: I work on\nMnemoverse\n, a memory engine for AI agents. This article is the short version of the original in our library, which carries all numbered claims, the paper-vs-code table, and full sources at pinned commits:\nRAG vs agent memory: what the source code actually shows\n. Code excerpts reproduced for commentary under the upstream licenses (mem0, Graphiti, Generative Agents: Apache-2.0; LangGraph: MIT).",
  "metadata": {
    "title": "Is Agent Memory Just RAG With Extra Steps? We Opened the Source Code to Find Out - DEV Community",
    "description": "Under every agent memory launch, the same comment appears: \"so it's RAG with extra steps.\" Instead of... Tagged with ai, agents, rag, llm.",
    "keywords": "ai, agents, rag, llm, software, coding, development, engineering, inclusive, community",
    "author": null,
    "og:type": "article",
    "og:url": "https://dev.to/izgorodin/is-agent-memory-just-rag-with-extra-steps-we-opened-the-source-code-to-find-out-349i",
    "og:title": "Is Agent Memory Just RAG With Extra Steps? We Opened the Source Code to Find Out",
    "og:description": "Under every agent memory launch, the same comment appears: \"so it's RAG with extra steps.\" Instead of...",
    "og:site_name": "DEV Community",
    "og:image": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz89i22xrviy6tj36inm2.png",
    "twitter:site": "@thepracticaldev",
    "twitter:creator": "@",
    "twitter:title": "Is Agent Memory Just RAG With Extra Steps? We Opened the Source Code to Find Out",
    "twitter:description": "Under every agent memory launch, the same comment appears: \"so it's RAG with extra steps.\" Instead of...",
    "twitter:card": "summary_large_image",
    "twitter:widgets:new-embed-design": "on",
    "twitter:image:src": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz89i22xrviy6tj36inm2.png"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/d659235c1e09/code/body-000.txt",
      "chars": 463,
      "language": "text"
    },
    {
      "file": "attachments/d659235c1e09/code/body-001.txt",
      "chars": 336,
      "language": "text"
    },
    {
      "file": "attachments/d659235c1e09/code/body-002.txt",
      "chars": 306,
      "language": "text"
    }
  ],
  "images": [],
  "audio": [],
  "videos": [
    {
      "url": "https://www.youtube.com/watch?v=tSV0epuR7SI",
      "src": "https://www.youtube.com/embed/tSV0epuR7SI",
      "status": "ok",
      "kind": "single_video",
      "local_path": "attachments/d659235c1e09/videos/repair-tSV0epuR7SI.mp4",
      "bytes": 27092161,
      "valid": true,
      "height": 1080,
      "width": 1920,
      "duration": "759.341000",
      "reused": false
    }
  ],
  "links": [
    {
      "href": "https://www.youtube.com/embed/tSV0epuR7SI",
      "label": "",
      "type": "iframe"
    },
    {
      "href": "https://mnemoverse.com/",
      "label": "Mnemoverse",
      "type": "a"
    },
    {
      "href": "https://mnemoverse.com/docs/library/rag-vs-agent-memory",
      "label": "RAG vs agent memory: what the source code actually shows",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:49:32Z",
  "source_url": "https://dev.to/izgorodin/is-agent-memory-just-rag-with-extra-steps-we-opened-the-source-code-to-find-out-349i",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 9082,
  "expected_body_images": 0,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/d659235c1e09/pages/source.html",
  "local_html": "attachments/d659235c1e09/pages/article.html",
  "body_base_path": "attachments/d659235c1e09/pages/",
  "body_markdown_path": "attachments/d659235c1e09/pages/article.md"
}