You ask the agent about the payments retry table, and it comes back with the retry table plus the queue migration you argued about last month, because both mention the same service. Useful. Next week you ask about the queue and the retry table comes back with it again, and the week after that too. At some point you wonder whether that pairing is now something your memory layer knows, or whether you are watching the same shared entity match fire every time and nothing has been learned at all. The vendor page says knowledge graph, which is true, and answers nothing.
That is the buyer's question about graph memory once you strip the feature list off it. Every product in this category builds a graph, so the feature list separates nobody. What separates them is what a node is, when an edge is made, and whether anything in that graph changes because of what a search returned. Not because you wrote something new. Because you read. I put that question to six systems, Mem0, Zep with Graphiti, Cognee, Letta, Supermemory and Mnemoverse, and read each vendor's own pages and published code. I work on the last one, and on this exact question it holds the weakest row, so it goes first.
One line to carry: in all six the edges are made when you write, two of them walk further when you read, one of them writes a reinforcement when you read, and the only place where a read demonstrably strengthens an edge is a research repository, not a product.
Our row first, because it is the exposed one
Mnemoverse describes the mechanism this article goes looking for, in public, in code you can open. The memory server we publish prints it in a line the user sees: "concept-to-concept links learned from concepts that occur together as memories are stored and used". The comment above that line names the operation without a metaphor, co_activate, linking query concepts "to result concepts on use". Our library page says the same in prose: "Its association layer links concepts through weighted edges strengthened by co-activation, with feedback tuning the weights over time". On use. That is the loop, written down by us.
And our engine is closed, so you can read those sentences and you cannot check one of them. Five of the six say nothing about this mechanism in public, and silence promises nothing. We have said more and shown less. If you are choosing on evidence rather than on ambition, that runs against us, and it should.
Six graphs, four kinds of node
Zep: "Zep's temporal Context Graph is the unit of agent memory. Nodes are entities, and edges are facts or relationships. The graph updates as new data arrives." Cognee extracts entities and relationships into a graph store, Mem0 ships a feature named Graph Memory, Supermemory sells a memory graph on its product page, Letta resolves links between memory files, and Mnemoverse ships a learned association graph. Six products, six graphs, and the word does different work in each sentence.
Entities are what Zep and Cognee mean, and Cognee's retrieval guide is exact about the order: nodes are selected first and "An edge is only carried over if both of the nodes it connects were selected".
Memories are what Mem0 means, and its graph is bipartite. Entities are stored once, and "Over time this forms a graph: a web of entities, each connecting all the memories that mention it." Two memories are connected when they share an entity, with no learned weight on that connection, and the vendor rules out typed relations in the same breath: "It does not assign typed, labeled relationships between entities". Connected through a shared thing, not to each other.
Documents are what Letta means. The nodes are Markdown files the model itself wrote, the edges are file links a resolver parses, nothing weights them, and Letta's own published memory showcase says what a link is for: "A double-bracket path is a discovery link, not an automatic include or semantic retrieval instruction."
Facts on facts are what Supermemory means: "When content is processed, new facts connect to existing ones through three relationship types." Its published package types those three as strings and carries no weight, strength or access count on the edge, though its generated SDK leaves the field an open enum.
Concept labels are what we mean. Our nodes are neither entities nor memories, and our edges carry no relation types, no direction and no entity resolution, so the association layer never asserts that two records denote the same thing. Four kinds of object under one word.
When the edges get made
In all six, the edges are created when something is written. Extraction, linking, and then the work is finished before you ask anything. Same in every row, ours included.
Two of the six do something extra at query time. Cognee ships a neighbourhood depth parameter that triggers real multi-hop expansion, which the shipped code declares None, so it is off unless you set it. Zep documents the same shape under a different name: "You can enable breadth-first search to expand results around specified graph nodes." Its parameter table gives the seed no default and marks it not required.
Both walk the graph and find nodes. Walking is not changing, and for Cognee that is the vendor's own statement about its retrieval object: the memory fragment built for a query "is never read directly from, or written back to, the full persisted graph". For Zep the conclusion is narrower, because the served engine is closed: every documentation page I read uses the vocabulary of returning results, and none of them documents a write on the search path. That walk was not completed, so this is a statement about the pages read, not about the engine.
The one place a read writes, and where the write lands
Mem0 is the real counter-example: a read does write. Its memory decay page says so twice, once as a principle, "every time a memory is returned in a search it gets a small reinforcement", and once as a numbered step in the search pipeline, "Records a fire-and-forget reinforcement against each returned memory".
On that entire page the words edge, graph, connection and link occur zero times, checked on 8 September 2026. The reinforcement lands on the memory's own access history. It is not a connection that got stronger because two things came back together. And the whole feature is "opt-in per project" and "off by default". A read writes, to the memory, not to the graph, and only if you asked for it.
Twenty searches, with controls
To reach a surface documentation never touches, I searched each vendor's GitHub organisation for the vocabulary this mechanism has: hebbian, co-activation, spreading activation, co-retrieval. Four terms, five organisations, twenty code searches, all printed on the full write-up so you can run them. Re-run on 8 September 2026, all twenty return zero files.
A zero from an instrument is a statement about the instrument first, so it needs controls, and it has two. The positive control is the same search with the term memory, which returns files in every one of the five organisations. The stronger control does not depend on the index: I downloaded the default branch of each vendor's main repositories, seven trees, and grepped them directly, hyphenated and unhyphenated spellings both. Zero for all four terms in every tree, and zero in the single-file documentation corpora of Cognee, Supermemory and Mem0. One trap: letta-ai/letta no longer holds the source, so a grep there gives a zero that looks like evidence and is not; the source is in letta-code.
A search of what is published is not a search of what runs, and four of these six do not publish an engine you can read, ours among them. Two vendors also run chat communities unreadable from outside, and this sweep does not cover them.
The vocabulary is already taken
What makes this hard to see from outside is that the words are in use, for other mechanisms. Supermemory's preferences memory type "Strengthens with repetition", in a table whose neighbouring rows persist until updated and decay unless significant, and nothing in that vendor's documentation corpus says what does the strengthening. Letta's shipped system prompt tells the model that its references should strengthen with use, an instruction to a model, not a description of an engine.
An earlier cut of this argument, in my own drafts and video, said those values were computed once and frozen. That was wrong and catchable in one click: Zep regenerates observations when new evidence lands, and Supermemory's row sits in a table about things that move. What none of them says is where the change comes from. New evidence, a rating, a repetition, a score. Every one is a real mechanism. Not one is a connection that got stronger because two things came back together.
The one public implementation where that happens is the code attached to an ACL 2026 paper, ReinerBRO/HeLa-Mem. The last thing its retrieve method does before returning results is call a routine whose docstring reads "Strengthen connections between simultaneously retrieved memories." It walks every pair of the retrieved identifiers and adds weight to the edge between them. That is the whole mechanism, and what none of the five products has published is those few statements between having the results and returning them. It is a research artifact with no releases, no tags and no licence, and the claim is exactly that narrow: one public implementation where retrieving changes the connection.
The table
Every quotation above is a contiguous substring of a page its author published, re-checked on 2026-09-13, and each source is named on the full write-up. The table is a reading of published pages and code, not a benchmark.
| system | a node is | edges made | extra at read time | does a read write anything |
|---|---|---|---|---|
| Mnemoverse | concept labels | on write, and on use per our public source | cannot be checked, engine closed | our source says yes, to the edge; unverifiable from outside |
| Mem0 | memories, linked through shared entities | on write | entity boost reorders an over-fetched pool | yes, a reinforcement on the memory's own access history; opt-in, off by default; nothing on an edge |
| Zep with Graphiti | entities; edges are facts | on write | breadth-first search from named nodes, optional | none documented on the pages read; hosted engine closed |
| Cognee | entities | on write | neighbourhood depth, off unless set | no; the query fragment is never written back; an optional last-accessed stamp moves no weight |
| Letta | Markdown files the model wrote | on write | none | no |
| Supermemory | facts on facts, three typed relations, enum left open | on write | none documented | no weight on the edge type; the engine is not in the published tree |
What to ask a vendor
Three questions separate these six, and none of them is on a comparison table. What are the nodes: entities, memories, documents or concepts. When does anything happen: only at write time, or at query time as well, and if so whether it is on by default, because for the two that have it, it is not. And when the graph changes, what moved it: new evidence, a rating, a repetition, a score, or a read. The last one decides whether a memory layer learns from being used or only from being written to, and the category is quietest about it.
From published material, five of the six never name the last case. One names it in its own public source and cannot show you the engine that would settle it, and that one is us. Ask us that question harder than you ask anybody else.
Which of the six do you run, and have you ever seen a pairing come back that nothing in your writes explains?
Disclosure: I work on Mnemoverse, one of the systems above, so weigh the argument accordingly. The full comparison with every source page is on our library, and the MCP server is open source (MIT): github.com/mnemoverse/mcp-memory-server.
Top comments (6)
Respect for putting your own row first and flagging that the one place a read demonstrably strengthens an edge is a research repo, not a product. That honesty is rarer than the feature.
The distinction you're drawing — edge created on write vs edge reinforced on read — is the one that actually bites in production, but I'd add a third axis that's easy to conflate with read-reinforcement: retrieval ranking that adapts without touching the graph at all. When your payments-retry / queue-migration pairing keeps co-surfacing, that can be a learned edge, or it can be a static entity match plus a recency/frequency prior in the retriever that just looks like learning. From the outside they're indistinguishable until you freeze the graph and watch whether the pairing decays.
The failure mode we care about most isn't "does it learn on read" but "can a read-triggered reinforcement create a feedback loop where a coincidental early co-occurrence keeps getting stronger because it keeps getting surfaced." Co-activation strengthening is powerful, but without a decay term or negative feedback it's just confirmation bias with weights. Does Mnemoverse's feedback tuning include downweighting when a co-surfaced result gets ignored?
The third axis is the one I would separate first, because it is the one our own answer depends on. Ranking that adapts without touching the graph is exactly what runs on our read path: feedback and recency adjustments used to be applied after ordering and truncation, which meant the advertised re-ranking never changed what came back, and since 23 August they are applied before the cut. So an item can move in the results while no edge anywhere has changed weight. Your freeze test is the right way to tell them apart from outside, and it needs one control to be conclusive: freeze the graph, then also hold the query fixed, because a recency prior will decay the pairing on its own timetable and look like an edge that was never reinforced.
On your direct question, the honest answer is no. Feedback is an explicit call, helpful or wrong, and nothing reads an ignored result as a signal. A co-surfaced item that you scroll past is treated as if it never came back, so the pairing neither strengthens nor weakens on that event.
That makes the loop you describe narrower than it would otherwise be, and it also removes the defence you were probably expecting. There is no decay term at all: our documentation says it plainly, an unhelpful memory is out-ranked, never erased, and nothing decays or is deleted by age. So the protection against a coincidental early co-occurrence hardening is not that the weight fades on its own. It is that the weight only moves when someone states an outcome, which means an unattended system keeps its first impressions rather than amplifying them. Whether that trade is right depends on whether anything in your setup is reliably present to send the verdict, which is the same question that decides every negative feedback design I have looked at.
The decay-vs-no-decay answer above is clean, but it skips a question that sits one layer below it: is the edge weight update itself atomic under concurrent retrieval? Co-activation as described is a read-modify-write on a stored weight (read current value, add delta, write it back), and if two sessions retrieve the same concept pair at close to the same time without a lock or an atomic increment at the storage layer, one update silently loses to the other. That wouldn't show up in any single-session test, only under real concurrent load, and from outside it's indistinguishable from an edge that just wasn't reinforced enough, so you'd end up tuning the wrong knob. Does the storage layer do this with an atomic increment (Redis INCRBYFLOAT, a SQL UPDATE ... SET weight = weight + delta, something equivalent) or is it a plain read-then-write that could drop updates under contention?
The distinction is right and it is the one that would not show up in any test you are likely to run. A read-modify-write that loses an update under contention and an edge that was simply not reinforced are indistinguishable from outside, and the second explanation is the one most people reach for, so the tuning goes into thresholds that were never the problem.
On our side I am not going to answer from the inside, because the storage-layer detail is not something we publish and I am not going to state what I have not verified myself. What is documented is the behaviour: two concepts recalled in the same context get an association, and the association strengthens with repetition.
What I can offer is the test, since it needs no access to the implementation. Pick a concept pair with no history. Fire N concurrent recalls that co-activate exactly that pair, from separate sessions, with no other traffic. Then compare the resulting behaviour against N sequential recalls of the same pair, same N. If the concurrent run lands consistently weaker, updates are being dropped; if it matches, the increment is atomic or serialized somewhere. Run it twice with N far apart, because a single N hides both a small constant loss and a rate-dependent one.
That is a question worth asking every vendor that reinforces on read, and almost none of them document it. I am raising it on our side as an issue rather than answering it with a guess, and if the answer is publishable I will come back to this thread with it.
It's easy to read repeated pairings across sessions as the memory layer learning something, when every query is just re-hitting the same entity match and nothing new gets written. Persisting an edge only when someone confirms that pairing actually unblocked the task would keep the graph from turning into a search log. After a useful answer, do you see a new node in the store, or another hit on old ones?
Neither a new node nor only another hit, going by what is published. On our row the published description is that concept-to-concept links are learned from concepts that occur together as memories are stored and used, and the explicit confirmation signal is separate: a feedback call the agent makes afterwards, saying whether a recalled memory helped or misled.
Your gate is the right one. An edge that only counts once someone confirms the pairing actually unblocked the task is the difference between a graph that learns and a graph that records traffic. The practical catch is that the confirmation has to be sent. An agent that never reports outcomes turns any design like this back into a search log, however the store is built.