Knowledge Graph vs Vector Search for Agent Memory: When to Add a Graph, and Why Time Matters

AI Agent MemoryRAG & RetrievalGaurav Dadhich27 September 202641 min read
Knowledge Graph vs Vector Search for Agent Memory: When to Add a Graph, and Why Time Matters
On this page
  1. What is knowledge graph agent memory?
  2. What each approach actually answers
  3. Where vector search breaks for agent memory
  4. What a knowledge graph costs you
  5. Knowledge graph vs vector search at a glance
  6. The hybrid that holds up in production
  7. When to add a graph
  8. Is grep enough? What the RAGFlow results mean for agent memory
  9. The choice underneath: chunks or statements
  10. What is a temporal knowledge graph, and does agent memory need one?
  11. Two clocks: valid time and transaction time
  12. How vector search, a graph and a temporal graph handle a fact that changes
  13. Point-in-time questions, and how to run them
  14. Recency weighting and decay are not a substitute
  15. Business facts change too
  16. What goes wrong in practice
  17. You may not need a graph to get time
  18. Keeping tenants apart when retrieval walks relationships
  19. Do you need a graph database for this?
  20. How to test the choice on your own agent
  21. Where Maximem Synap fits
  22. Frequently asked questions

Published 27 September 2026 · Updated 3 November 2026. Model and embedding prices and PostgreSQL 19 status re-checked on 3 November 2026, unchanged since the 30 October update. This update adds what InfiniFlow's 30 October RAGFlow paper, in which an agent searching with only BM25 and grep matched compiled structure on document-search accuracy, does and does not mean for agent memory, alongside LangChain's 28 October evidence-graph result.

For agent memory, neither wins on its own. Vector search is the right tool for finding past statements that mean something close to the current question; a knowledge graph is the right tool once the agent has to connect facts through the same person or company, or tell which of two conflicting facts is current. Start with vector and keyword search over self-contained statements, and add graph expansion when you can name a class of questions that needs it. Maximem Synap is built on that split: it stores each memory as a discrete statement rather than a transcript chunk, resolves every mention of a person to one entity, records a changed fact as a change instead of overwriting it, and expands through the entity graph in its Accurate retrieval mode, when a question spans several entities.

Time is the second decision, and it is separate from the first. A temporal knowledge graph records when each fact was true; a bi-temporal one also records when the memory learned it. Those two clocks let an agent answer three different questions correctly, "where does Priya work now?", "where did Priya work in May?" and "what did we think on 15 July, when we sent her that email?", and a memory that keeps only one clock, or none, will answer at least one of them with whichever fact happens to rank higher.

What is knowledge graph agent memory?

Knowledge graph agent memory is long-term memory for an AI agent stored as entities (people, companies, projects) and typed relationships between them, with each relationship carrying when it was true and which message it came from, so the agent retrieves by following connections from the entities in a question and not only by how close the wording is. It is built continuously from the agent's own conversations and documents, which is what separates it from a knowledge graph built once over a fixed corpus, and every write passes through the same five steps:

  1. Extract. A language model reads the new message and pulls out self-contained statements and the entities they mention, so "I joined Initech in June" yields Priya, Initech and a WORKS_AT relationship that starts in June.
  2. Resolve. Each mention is matched to an existing entity or creates a new one, so "Priya", "Priya S." and "my account manager" land on one node when they are one person.
  3. Write with time and source. The relationship is stored with its validity period, the time the memory recorded it and a pointer to the message it came from.
  4. Reconcile. The new relationship is compared with existing ones on the same entity; if it contradicts a single-valued one, such as a current employer, the old one is closed and kept for history, not deleted.
  5. Retrieve. At question time, similarity and keyword search find entry-point statements and entities, the graph expands a bounded number of hops from them, and the merged results are filtered by validity, ranked and trimmed for the prompt.

Extraction, resolution and reconciliation are where the cost and the errors live, and each gets its own treatment below; retrieval is the hybrid pattern most production systems settle on. A February 2026 survey of graph-based agent memory organises the research along similar lines, as extraction, storage, retrieval and evolution, its word for updating what memory holds.

What each approach actually answers

Vector search turns text into embeddings (arrays of numbers that place similar meanings near each other) and returns the stored items nearest to the query. Most stores do this approximately for speed: pgvector, for instance, runs exact search by default and adds HNSW or IVFFlat indexes for approximate search, "which trades some recall for speed." The question vector search answers is "what have I stored that is similar to this?"

A knowledge graph stores entities as nodes and the relationships between them as typed, directed edges: Priya -WORKS_AT-> Acme, Acme -ON_PLAN-> Enterprise, Enterprise -RENEWS_ON-> 2027-03-01. Retrieval starts at an entity and follows edges. The question a graph answers is "what is connected to this, how, and what is true about it?"

Agent memory needs both kinds of question. A support agent asked "have we seen this error before?" wants similarity. The same agent asked "is this customer's renewal affected by the outage on their plan?" wants three facts joined through two entities, and no single stored sentence is similar to that question.

Knowledge graph, temporal knowledge graph, context graph

Vendors use these three terms loosely, and they overlap. A knowledge graph is entities plus typed relationships. A temporal knowledge graph puts a validity period on each relationship, and a bi-temporal one adds the time each fact was recorded and retired. Context graph is the newer, product-facing name: Zep's October 2026 post defines it as a knowledge graph built to give agents context, storing entities, relationships, when each fact was true and where it came from. Read that way, a context graph is a temporal knowledge graph with provenance, aimed at the prompt; the engineering questions below apply to all three names.

Organising retrieval around entities is an older idea than agents. In a 2010 study of a commercial web search log, Pound, Mika and Zaragoza found that "more than half of the queries focus on an entity or entity type" (40.60% entity queries and 12.13% type queries in their sample), and the questions people put to an assistant about a customer or a colleague skew the same way. Graph retrieval also mirrors an old model of human recall, spreading activation (Collins and Loftus, 1975), where a search starts at a few labelled nodes and propagates outwards along links, which is roughly what an entity-first expansion does.

Where vector search breaks for agent memory

Vector search fails in four predictable ways once memory spans many conversations, and none of them is fixed by a better embedding model.

Similar is not current. "I live in Chicago" and "I moved to Berlin in June" embed close together, so both come back for "where does the user live?", ranked by similarity rather than by which one is still true. A retrieval layer over transcripts holds the fact as stated in March and the correction in June, and has no view about which is current; the model is left to sort it out at read time, on every call. This is the most common source of stale agent memory, and the section on changing facts below walks through it case by case.

Chunks have no identity. "My manager", "Sarah" and "Sarah Chen" are three strings in three chunks. Nothing in a vector index knows they are one person, so a question about Sarah retrieves a third of what the agent knows about her.

Multi-hop answers are not similar to the question. "Which of this user's projects depend on the service we deprecated last month?" needs the user's projects, each project's dependencies and the deprecation date. Each fact is stored somewhere; none of them is individually close to the question, so top-k similarity misses the chain.

Exact terms match poorly by meaning. Order numbers, error codes, API names and SKUs are where embeddings blur. In our retrieval study (50,000 documents across five datasets, 5,000 queries, scored on MRR@10 by exact document match with no model judge), vector search led the five-dataset average on MRR@10 (0.6320 against 0.5325), but with the code dataset removed, plain keyword search led the average of the other four (0.5931 against 0.5614), and it indexed in 2.11 seconds against 161.6 seconds for embeddings. This one is fixed by adding keyword search alongside vectors, not by adding a graph.

What a knowledge graph costs you

A graph fixes the first three failures, and it charges for it on every write.

An extraction call per write. Someone has to turn "Sarah said Acme is moving to the annual plan next quarter" into entities and edges, and in agent memory that someone is a language model. The price gap to embedding has narrowed sharply since September, because cheap extraction models got much cheaper. Assume a conversation turn adds 400 tokens, and an extraction call sends 2,000 input tokens (the turn, the extraction instructions and the candidate entities it might match) and returns 250 tokens of entities and relationships. At list prices re-checked on 3 November 2026:

Per conversation turnPrice basisCost per turnPer million turns
Embed the turnOpenAI text-embedding-3-small, $0.02 per million tokens$0.000008$8
Extract a graph with Claude Haiku 5.5$0.10 input, $0.50 output per million tokens, for prompts up to 100,000 tokens$0.000325$325
Extract a graph with Claude Sonnet 5.5$2 input, $10 output per million tokens$0.0065$6,500

On the cheaper model that is roughly 40 times the embedding cost, down from roughly 400 times at the September prices for Claude Haiku 4.5, and still before storage and before any follow-up call to settle an ambiguous entity match. For a customer-facing agent where a wrong fact costs a customer, it is a small price. For an internal tool processing millions of low-stakes turns, it may still not pay back.

Build once, read many times. The extraction cost is paid once per write, and every later read gets compact facts instead of raw transcript, so fewer tokens reach the expensive answering model. Zep's context graph post makes the same argument: extraction can run on a small model while only the answering agent needs a frontier one. Whether it pays back depends on your read-to-write ratio; an agent that writes a lot and rarely asks gets less of the benefit.

Errors become durable. A vector index that retrieves a poor chunk forgets about it by the next query. An extraction that writes Priya -WORKS_AT-> Initech from a hypothetical ("if I worked at Initech...") has created a fact that every later traversal will follow.

Entity resolution is the hard part. A graph with three nodes for Sarah is worse than no graph, because traversals from each node see a third of her relationships and report them confidently. "Acme", "Acme Corp" and "ACME Corporation" have to merge; "Sarah (my manager)" and "Sarah (my sister)" must not. Doing this well means matching on names, aliases and context, and sending the genuinely ambiguous cases to a person to decide.

Empty on day one, and slower on the hot path. A new user has no graph, so early recall leans on similarity anyway. And each hop is another lookup, while expanding a neighbourhood multiplies candidates to rank; if the traversal sits inside every conversational turn, measure its latency before you ship it.

Knowledge graph vs vector search at a glance

Vector searchKnowledge graphTemporal (bi-temporal) knowledge graph
StoresEmbeddings of text (chunks or statements)Entities, typed relationships, propertiesThe same, plus a validity period and a recorded period on each fact
AnswersWhat is similar to this?What is connected to this, how?What is connected, and what was true (and believed) at a given time?
Strong atParaphrase, fuzzy recall, "have we discussed this before?"Multi-hop questions, one identity across many namesChanging facts, "as of" questions, audit of past answers
Weak atCurrent vs outdated facts, identity, multi-hop, exact identifiersCold start, extraction errors, history lost on overwriteEverything a graph is weak at, plus date extraction errors
Cost per writeOne embedding callOne LLM extraction call, plus entity resolutionThe same, plus a contradiction check against existing facts
Useful fromThe first stored itemOnce enough entities and relationships existOnce facts start changing

Exact identifiers belong to none of the columns; they are keyword search's job, which is why production memory usually runs hybrid search.

The hybrid that holds up in production

A pattern that holds up uses each index for what it is good at, in sequence. Embed the query and run similarity and keyword search to find entry points: the statements and entities most relevant to the question. From those entities, expand along relationships to pick up connected facts that were never similar to the question. Merge the candidates, rank them on relevance, recency and confidence, drop anything that has been superseded (or, for an "as of" question, anything that was not valid at that time), and trim to a token budget before it reaches the prompt.

Microsoft Research's GraphRAG uses the same local-search move over documents, starting from entities and traversing their neighbourhood, and adds community summaries for corpus-wide questions, because "RAG fails on global questions directed at an entire text corpus, such as 'What are the main themes in the dataset?'" Agent memory rarely asks corpus-wide questions. It asks about specific people and their changing circumstances, which is why the expansion step matters more than the summaries. The closest agent-memory analogue to a community summary is a background job that condenses repeated facts into one statement about a pattern (Zep calls these Observations). Because repeated facts attach to the same entities, a graph makes the repetition visible, and the summary then works like a materialised view: one statement replaces weeks of near-identical facts in the prompt, and it helps with questions such as "how does this user usually work?"

When to add a graph

Start with vector plus keyword search over clean, self-contained statements. Add relationship expansion when at least one of these is true, and you can point at failing conversations that show it:

  • The agent regularly needs two or more facts joined through an entity ("the plan of the company this user works for").
  • The same person, company or project shows up under several names across conversations or channels.
  • Facts change, and wrong answers come from the agent picking the outdated one.
  • Someone needs to ask why the agent believes something, and "it was similar" is not an acceptable answer.

If none of these shows up in your failure logs, a graph adds cost and moving parts without changing answers. A single-session support bot, or an agent whose stored facts are independent and stable ("prefers email", "account type: enterprise"), is well served by similarity search.

The middle option: entity linking without a graph

Identity and relationships are separable problems. A store can extract the entities in each memory and link every memory to the entities it mentions, which fixes "chunks have no identity" (everything about Sarah is reachable from Sarah) without typed edges, traversal or a graph database. Mem0's open-source library moved to this design in April 2026: pull request #4805, merged on 14 April, removed its graph memory modules, including the Memgraph and Neptune backends, and added spaCy-based entity extraction and linking next to combined semantic and BM25 keyword search. Entity linking answers "what do we know about this person?" well. It cannot answer a two-hop question whose middle entity never appears in the question, and it does not say which of two conflicting facts is current, so your failure logs decide whether it is enough.

Is grep enough? What the RAGFlow results mean for agent memory

For searching documents, a lexical search loop inside an agent is now a very strong baseline. In Context as Infrastructure, posted to arXiv on 30 October 2026 by InfiniFlow (the company behind the open-source RAGFlow) with East China Normal University, a search harness whose only retrieval tools were BM25 and grep, with MiniMax M3 as the answering model, answered 85% of BrowseComp-Plus and reached 91% on the multi-hop FRAMES benchmark, with no vector index and no compiled index. The authors report that adding a dense retrieval leg lowered accuracy in their runs, because the agent already does the semantic work by rewriting its own queries. Read these as a vendor's results from one setup: FRAMES answers were scored by an LLM judge the paper does not name, the token and accuracy figures rest on 200 FRAMES questions, the paper gives no question count for its BrowseComp-Plus run (which used roughly three million tokens per question and which the authors call a scale check, not comparable with FRAMES), each structural variant ran once, and the authors report no confidence intervals or repeated seeds.

Adding structure on top did not raise accuracy. Under the same harness, a RAPTOR-style summary tree, an LLM-written wiki and an entity-relation graph were each neutral on FRAMES accuracy, and a PageIndex-style outline was neutral at small scale and worse at full FRAMES scale, where a confidently wrong branch cost more than open search. The graph was neutral, in the paper's words, because "FRAMES supplies entity-linked evidence; few questions required a join". What moved was cost. A navigation tree whose leaves are claims paired with verbatim evidence cut total query tokens by 91% when that evidence sufficed and added 1.9% when it did not, a 38% average across FRAMES. The saving was paid for in turns: on the seven questions both arms of one ablation answered, query tokens roughly halved while end-to-end latency roughly tripled, because a smaller page of evidence needs more retrieval rounds. In a separate measurement, a gate that decides when a deep read is worth it cut mean latency from 140 seconds to 30.

Two conclusions hold for document search. Keyword search run by an agent is the baseline any index has to beat on your own questions, and once accuracy saturates, structure should be judged on tokens and latency per correct answer, measured together, because a token saving can hide a latency bill.

Neither conclusion settles the agent-memory question, and the paper does not claim it does: it treats memory as a separate artifact, durable preferences, decisions and commitments extracted from the session log, and says its evaluation "is continuity rather than multi-hop QA". FRAMES asks what a set of Wikipedia articles says, where each fact holds still and the right passage exists somewhere to be found. Agent memory asks what was true, for whom, and when: where Priya works now, where she worked on 15 May, what the agent believed when it drafted the July email, and whether a fact belongs to this user or to another tenant. Grep over raw transcripts finds "I live in Chicago" and "I moved to Berlin in June" equally well and has no view on which is current; it cannot tell a correction from a change, close a fact's validity period, or answer "what did we know then?" unless something recorded when each statement was learned. Those are write-time decisions, and no retrieval loop, lexical or dense, makes them at read time. The same split runs through agent memory vs RAG: shared documents that change by new versions on one side, per-person facts that get corrected on the other.

Corpora where facts supersede one another point the other way. LangChain's 28 October OpenWiki experiment used a synthetic library of 111 insurance documents in which policies and endorsements "reference, amend, and override one another", with outdated versions and contradictions mixed in, and asked each question three times. On 50 connected questions, the agent with a compiled, source-linked wiki scored 0.52 accuracy against 0.10 for the same agent with keyword search alone; on 80 simple lookups the two were level (0.84 against 0.83) and search was faster. Adding an evidence graph, typed edges such as supersedes and refers_to built from the wiki's citations without extra model calls, raised accuracy a further 27% over the wiki alone (not over search) and cut cost per correct answer by 58%. That is also a vendor's single setup, on a synthetic corpus, with the answering and grading models not named and compilation costs excluded from the answer costs. Taken together, the two results draw a usable boundary: where the corpus states each fact plainly and the facts do not change, an agent with grep holds up; where facts supersede one another and the answer depends on which version applies, structure that records the supersession earns its cost.

For agent memory, the result sharpens the advice above instead of overturning it. Run keyword search for exact identifiers and plain lookups, as our own retrieval study also suggests, and judge any graph you add on tokens and latency per correct answer as well as on accuracy; the work on time and identity belongs at write time, where grep cannot reach.

The choice underneath: chunks or statements

Indexes get the attention, but the unit you store decides more. A chunk of transcript contains the fact you want and four you do not, it contains the March version and the June correction side by side, and it cannot be compared with another chunk in any meaningful way. A self-contained statement ("The user moved to Berlin in June 2026") can be compared with an existing one ("The user lives in Chicago") and a system can decide whether the new one replaces it, adds to it or duplicates it. Put chunks in a graph and you get a graph of chunks; put statements in a vector index and a lot of the "similar is not current" problem goes away, because superseded statements can be marked and filtered before ranking.

Replacement has its own trap, and it is the one systems rarely advertise. If "the user takes 20mg of atorvastatin daily for cholesterol" is replaced by a later, vaguer "the user takes a cholesterol medication", the memory has lost the dose while appearing to update. Maximem Synap will not make that trade: when a newer statement is poorer than the one it resembles, both survive. That rule sits in the update step, and no choice of index provides it.

What is a temporal knowledge graph, and does agent memory need one?

A temporal knowledge graph is a knowledge graph in which every fact carries time: when it became true, when it stopped being true, and when the system learned it. Where an ordinary knowledge graph stores Priya -WORKS_AT-> Acme as if it were permanent, a temporal one stores that edge with a validity period, so it can answer "where does Priya work now?" and "where did Priya work when she signed the contract?" and get different, correct answers. The research literature defines it the same way; a 2024 survey of temporal knowledge graph question answering writes a temporal knowledge graph as entities, relations, timestamps and facts, with facts often stored as quadruples (subject, relation, object, time). Much of that research is about forecasting which links will appear next; agent memory needs something plainer, reliable recall of what holds now and what held then.

Time-dependent questions are a large share of what makes memory hard. LongMemEval, a 500-question benchmark for long-term chat memory, tests five abilities: "information extraction, multi-session reasoning, temporal reasoning, knowledge updates, and abstention". Two of the five abilities, temporal reasoning and knowledge updates, are exactly the questions a memory without time gets wrong.

Two clocks: valid time and transaction time

Every fact an agent stores has two dates, and bi-temporal memory is simply memory that keeps both. Database research named them decades before AI agents. Temporal databases define valid time as "the time period during or event time at which a fact is true in the real world" and transaction time as "the time at which a fact was recorded in the database"; "a bitemporal model contains both valid and transaction time." Martin Fowler's Bitemporal History calls the same pair actual history and record history, and his example is the one every agent builder eventually meets: on 15 March, HR reports that Sally got a raise on 15 February. Asked what Sally earned on 25 February, the honest answer depends on the perspective: on 25 February the company believed the salary was $6000, and that was the figure it ran payroll on.

The two clocks disagree constantly in agent memory. A user mentions in September that they changed jobs in June. A backfill imports last year's support tickets today. A customer corrects a delivery address that was mistyped a week ago. A memory that keeps only when it stored something will treat the imported ticket as the newest truth; a memory that keeps only when things were true cannot explain why the agent said something different last week.

Stored, the two clocks become four timestamps on each fact:

FieldClockMeaningName in Zep's paper
valid_fromValid timeWhen the fact became true in the worldt_valid
valid_toValid timeWhen it stopped being true (open while still true)t_invalid
recorded_atTransaction timeWhen the memory learned itt′created
retired_atTransaction timeWhen the memory stopped believing this version (open while believed)t′expired

Zep's 2025 paper, the source of the last column, keeps "timeline T", "the chronological ordering of events", separately from "timeline T′", "the transactional order of Zep's data ingestion", and stores all four timestamps on each edge of its graph. Some databases add a third clock, decision time ("the time at which the decision was made about the fact"), which matters in regulated audit trails and rarely in agent memory.

With both clocks in place, an agent can answer four kinds of time question, and each needs a different filter:

  1. What is true now? Valid now, and currently believed. This is the default for a prompt.
  2. What was true at time T? Valid at T, according to what the memory believes today. "Where did Priya work in May?"
  3. What did the memory believe at time T′? Recorded on or before T′ and not yet retired at T′. "Why did the agent send Priya's renewal to Acme on 15 July?"
  4. What changed, and why? The sequence of retired versions of one fact, with the source of each change.

Question 3 is the one teams forget until a customer asks it, and it is the only one a valid-time-only model cannot answer.

How vector search, a graph and a temporal graph handle a fact that changes

Take one fact through each design. Priya worked at Acme. She joined Initech on 1 June 2026, and mentioned it to the agent on 10 September ("I joined Initech in June"). On 15 July, before it knew, the agent drafted a renewal email to her Acme address. On 20 October someone asks four questions.

DesignWhere does Priya work now?Where did she work on 15 May?What did the agent believe on 15 July?Why did the answer change?
Vector search over transcript chunksBoth chunks return, ranked by similarity; the model guessesCannot tell; the June date sits in the text, not in the indexAnswerable only if chunks carry their ingestion time and you filter on itNo link between the two chunks
Vector search over statements, superseded ones flaggedInitech, if you filter to current statementsInitech, wrongly, under a current-only filter; both, unranked, without itAnswerable from ingestion time, as abovePartial, if the flag points to the replacement
Knowledge graph, edge overwrittenInitechInitech, wrongly; the Acme edge is goneLostLost
Knowledge graph, second edge addedBoth edges look currentBothLostNo link
Temporal graph, valid time onlyInitechAcmeInitech, wrongly: it now "knows" she left in June, so it cannot explain the July emailPartial
Bi-temporal graphInitechAcmeAcmeThe 10 September message, linked to the closed Acme edge

A store that keeps only write time (which is what most vector stores record) can answer "what did we know then?" but not "what was true then?", and a temporal graph that keeps only valid time has the opposite gap. And the ingestion timestamp on a chunk is transaction time, not valid time, so a date filter over it answers a different question from the one most people think they are asking.

Three judgements sit underneath every row, whatever the index:

A correction and a change look identical in text. "The meeting was actually on Tuesday" fixes what was recorded, so the old version is retired in transaction time and the valid period moves. "From next week, meetings move to Tuesday" is a real change, so the old fact's valid period ends and a new one starts. Getting this wrong either rewrites history or invents a change that never happened.

Only single-valued relationships supersede. A person usually has one current employer and one home address, so a new value should close the old one. A person can speak several languages, have several children and use several tools, so a new value sits alongside the old ones. A memory that treats every relationship as single-valued will quietly retire true facts; Cognee's October 2026 guide handles this by configuring which relationships are functional, and some such rule is needed in any system.

Invalidate, do not delete. When a new fact contradicts an old one, close the old fact's validity period and keep it. Zep's paper describes the mechanism concretely: an LLM compares a new edge with "semantically related existing edges", and on a temporally overlapping contradiction the old edge's t_invalid is set to the new edge's t_valid. Graphiti's README states the same rule for its open-source engine: when information changes, old facts are invalidated and kept with their history. Deletion still has a place, for erasure requests, but it is a different operation from an update.

Facts with a known end date are the easy case and are still often missed: "I am on parental leave until 3 February" should be written with valid_to already set, so the agent stops mentioning the leave on 4 February without anyone having to tell it.

Point-in-time questions, and how to run them

A point-in-time query is a filter on the two clocks, and you can run it in the database you already have. A minimal bi-temporal fact table in PostgreSQL uses one range per clock:

CREATE TABLE facts (
  subject   text      NOT NULL,
  relation  text      NOT NULL,
  object    text      NOT NULL,
  valid     tstzrange NOT NULL,  -- valid time: when it was true in the world
  recorded  tstzrange NOT NULL,  -- transaction time: when the memory held this belief
  source_id text                 -- the message or document it came from
);

-- What was true on 15 May, according to what the memory believes today?
SELECT object FROM facts
WHERE subject = 'priya' AND relation = 'works_at'
  AND valid @> timestamptz '2026-05-15'
  AND upper_inf(recorded);

-- What did the memory believe on 15 July about where Priya worked that day?
SELECT object FROM facts
WHERE subject = 'priya' AND relation = 'works_at'
  AND valid    @> timestamptz '2026-07-15'
  AND recorded @> timestamptz '2026-07-15';

The first query returns Acme; the second returns Acme too, which is the point, because on 15 July the row saying Initech had not been recorded yet. A "now" query is the first one with now() in place of the date. When the 10 September message arrives, the application does not update the Acme row: it closes that row's recorded range, inserts a corrected Acme row whose valid range ends on 1 June, and inserts an Initech row valid from 1 June, all three stamped with the new source_id.

PostgreSQL's temporal tables documentation for version 19 covers the valid-time half natively, with WITHOUT OVERLAPS primary keys and PERIOD foreign keys, and states plainly that "PostgreSQL does not currently support system time", so the recorded range above is maintained by the application or a trigger. In a graph, the same two filters sit on each edge a traversal crosses, which is the expensive part: a two-hop "as of" question has to apply them at every hop, or it will join a fact that was true then to one that is only true now.

Time filters can also make retrieval worse if they are bolted on after similarity search. A July 2026 paper on a bitemporal Neo4j memory store measured this on a 60-question LongMemEval sample (10 per question type), scoring recall@10 as whether a retrieved result shares at least half its tokens with the reference answer, with no model judge described: on the scored temporal-reasoning questions, recall fell from 50% with current-state search to 37.5% with its time-travel path. The authors trace it to over-fetching nearest neighbours, dropping older versions that fail the time filter, and re-ranking the survivors by similarity alone, so the right answer slips below position 10. The sample is small, and the mechanism is the useful part: apply the time condition inside candidate generation, not only after it.

Recency weighting and decay are not a substitute

Boosting newer memories helps ranking, but it cannot tell a new fact from an old event reported late, or a one-off exception ("for this trip, book aisle seats") from a lasting change of preference. A filter and a boost also do different jobs: a filter removes what was not valid at the time asked about, while a boost or a decay score only reorders, so a stale fact still reaches the prompt, one place lower. Deciding what is current is a write-time judgement about what a new statement does to the old ones; recency is a read-time tiebreak.

There is also a choice about what the model sees. Current-only retrieval is right for most turns. Showing history ("Priya moved from Acme to Initech in June") is right when the change itself is relevant, and wrong when it invites the model to reason from the old value. Make it a per-call decision, not a store-wide one.

Business facts change too

The two clocks matter as much for what an agent knows about the business it works in as for what it knows about a user: pricing rules, contract terms, which column joins two tables, who owns an account. Those facts change in meetings, contracts and chat threads, and the change rarely lands anywhere the agent reads with a date attached.

Zep's 27 October 2026 review of 15 public engineering write-ups on internal data agents (from LinkedIn, DoorDash, Spotify and OpenAI among others) puts numbers on it. In its reading, "13 of the 15 teams depend on people to write or approve this context manually", and "10 of the 15 teams check for stale data or context, but none of the write-ups describes a record of when a business fact applied." The post is a vendor's reading of other teams' blog posts and says so ("A design element that a write-up does not mention can still exist in the system"), so the counts describe what teams wrote about, not necessarily what they run. The gap it names holds up anyway: a table description has an owner and a review when it changes, while, in the post's words, "a business fact that changes in a meeting or a contract has no pull request."

A recency check fails here for the same reason it fails on Priya's employer. A rule for last year's data can arrive in a message sent this morning, and a contract term signed two years ago can still be in force, so checking only how new the context is would apply the new rule to every period and mark the old, still-valid term as stale. Zep's 29 October follow-up gives a clean example: a migration in September 2025 changes the key that joins orders to accounts, a data engineer announces it in chat a week later, and an agent that trusts the newest fact applies the new key to first-quarter orders and returns numbers that look plausible and are wrong.

Business factValid time (when it applied)Transaction time (when the memory learned it)
Orders join on account_idUntil the September 2025 migrationJanuary 2025, from a runbook
Orders join on account_uuidFrom the September 2025 migrationA week after the migration, from chat

With both columns, "which key joins first-quarter 2025 orders?" and "which key joins orders today?" get different, correct answers, and "what did the agent believe when it built last month's report?" can be answered too. Two further things change when the memory is about the business. A business change often makes other facts wrong with no explicit link between them (a renamed pricing tier, for example, quietly invalidates every note that quotes the old tier's limits), so reconciliation has to look past the one relationship that changed. And business context is shared by every user of the agent, so a wrong business fact is wrong for everyone at once, which is the strongest case for keeping a person in the loop on business facts while user-level facts update automatically.

Maximem Synap holds an organisation's own context (uploaded as files, sent over the API, or pulled continuously through connectors for Google Drive, Notion, SharePoint, Slack, Linear, Mintlify and Zendesk) at a different level from what it knows about each user, and keeps the two apart; every statement it keeps records when it was stored and when the thing it describes happened.

What goes wrong in practice

Relative dates get resolved against the wrong anchor ("next Friday" read on the day of ingestion rather than the day it was said), which is why Zep's extraction prompt resolves relative expressions against a reference timestamp carried with each message. Imported history lands as if it were new unless the import passes the original date through. Unknown dates are silently filled with "now" instead of being left empty, and a valid-from date that is really an ingestion date poisons every "as of" answer that touches it. Two intervals that meet at midnight both look current under an inclusive bound. Date precision gets inflated, so "sometime in 2024" becomes 1 January 2024. A temporal model makes these errors visible; it does not prevent them.

You may not need a graph to get time

Time and graphs are separate decisions. If your "as of" questions concern single facts ("what plan was this customer on in January?"), a table with validity columns answers them, as the query above shows. SQL:2011 standardised application-time tables (valid time), system-versioned tables (transaction time) and bitemporal tables that combine both; MariaDB and IBM Db2 implement the standard's temporal features, and SQL Server has had its own temporal tables since 2016. A graph becomes necessary when the time-bound question crosses relationships: who managed this account when the incident happened, which team that person was on then, and which policy applied to that team at the time.

Keeping tenants apart when retrieval walks relationships

A similarity search can be filtered by tenant in one place. A traversal follows edges wherever they lead, so if any relationship connects two customers' data (a shared vendor, or a person who changed employers), a two-hop query can cross the boundary unless every hop is scoped. In a multi-tenant product the agent will then confidently use another tenant's fact about a shared entity.

Maximem Synap enforces its hierarchy at write time: every memory belongs to a scope (your account, your customer's organisation, one person, one conversation), and a read prefers the narrowest scope that applies and sees everything above it, never below and never sideways. One person's memory does not reach another person's session, and one tenant's memory does not reach another tenant's, whichever retrieval technique produced the candidate.

Do you need a graph database for this?

Often not. At the scale of one agent's memory, relationships can live in the database you already run. PostgreSQL's recursive queries handle bounded traversals, Apache AGE adds openCypher queries to PostgreSQL 11 through 18, and pgvector keeps embeddings in the same place. PostgreSQL 19 reached its first release candidate on 15 October 2026 and had no general-availability release as of 3 November; its release notes list no native graph query (SQL/PGQ) support, so plan on recursive queries or AGE for now. If you already run Neo4j, its vector index puts similarity search and traversal in one engine. The companion guide, what is a graph database and when should you use one, covers that decision in general.

Agent memory is also a different job from GraphRAG. GraphRAG builds a graph once over a document collection to answer questions about the documents. Agent memory builds its graph continuously from conversations about the same recurring people, where the difficult parts are updates, time and identity rather than summarising a corpus.

How to test the choice on your own agent

Benchmarks tell you whether a system handles the categories; your own conversations tell you whether it handles your agent. Build a small fixture before choosing, write the expected answer down before you run it, and ask each time question in all the forms that apply ("now", "as of" a date, and "what did we know on" a date):

CaseWhat you feed inQuestionExpected answer
Changed factLives in Chicago (March); moved to Berlin (June)Where does the user live? / Where did they live in April?Berlin / Chicago
Late reportOn 10 September: "I joined Initech in June"Where did they work on 15 July? / What did we believe on 15 July?Initech / Acme
BackfillLast year's tickets imported today, with their original datesWhat is the user's most recent issue?The newest by event date, not an imported one
Relative date"The call is next Friday", said on Monday 5 October, ingested on Monday 12 OctoberWhen is the call?Friday 9 October, anchored to when it was said, not 16 October
Correction vs change"Actually it was Tuesday" vs "from next week it moves to Tuesday"When was last week's meeting?Tuesday in the first case, the old day in the second
Multi-valued relationSpeaks Spanish; later, "I have started speaking German at work"Which languages does the user speak?Both
Known end date"On leave until 3 February"Is the user on leave on 4 February?No
Poorer restatement"20mg atorvastatin daily"; later, "a cholesterol medication"What does the user take?The dose survives
Business rule changeRunbook (January 2025): orders join on account_id; chat a week after a September 2025 migration: they now join on account_uuidWhich key joins first-quarter 2025 orders? / Which key joins orders today?account_id / account_uuid
Identity, multi-hop, identifierOne person under three names; a two-hop question; an order numberAs writtenOne person; the joined answer; the exact match
AbstentionNothing stored on the topicAs written"I do not know"

Check the evidence the retrieval returned as well as the final answer, because a model can pick the wrong fact even when the right one was retrieved, and a passing answer from the wrong evidence will fail next week. Run the fixture again after any change to extraction prompts or ranking, since that is where time handling usually breaks.

For published numbers, check which LongMemEval abilities a result covers (temporal reasoning and knowledge updates are the two that exercise time), how many questions were run, which models answered and judged, and who ran it; our own runs use an open harness so the method can be re-run.

Where Maximem Synap fits

Maximem Synap is a hosted memory service that sits beside your agent: you send conversations as they happen, and before each reply you ask what is known and get back a short, ranked set of statements ready for the prompt. The work described above (deciding what to keep, resolving entities across channels, handling changed facts without losing detail, keeping scopes apart) happens inside it, so the choice between similarity and relationships becomes a per-call parameter rather than an architecture you maintain.

context = await sdk.conversation.context.fetch(
    conversation_id="3f6b1a2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
    search_query=["What did Alice say about the project Bob is leading?"],
    max_results=10,
    mode="accurate",   # "fast" for the live turn; "accurate" follows entity relationships
)

Fast mode is the default and is built for agents that are talking to someone right now; Synap also works out what the agent is likely to need next and puts it in reach before it is asked for, which serves in-conversation reads at an asserted P75 under 15 ms. Accurate mode takes longer and does more: it breaks a compound question into parts, expands through the relationships between entities, and merges what it finds. How Synap works walks through the pipeline, and the real cost of DIY agent memory sets out what building the same pieces yourself involves.

How Maximem Synap handles a fact that changes

Each memory Synap keeps is a self-contained statement with a timestamp, a confidence score, a scope, and when the thing it describes actually happened, which is not the same date and is often the one that matters. When a new statement arrives that resembles an existing one, Synap decides whether it replaces it, adds to it or duplicates it: "I moved to Berlin" retires "I live in Chicago", so the agent stops repeating what stopped being true. Nothing is destroyed by that update. The old statement is marked historical and linked to what replaced it, along with the reason, so the history of a fact (where it came from, what it replaced, why it changed, what it used to say) can be read in the dashboard. And a newer statement that is poorer than the one it resembles never replaces it. Background consolidation runs every few hours, once a day and in your product's quiet hours, so the agent is not working from a stale picture between conversations. If your agent must answer "what did we believe on this date?" about its own past behaviour, put that case in the fixture above and test it on any system you evaluate, ours included.

Frequently asked questions

Is a knowledge graph better than vector search for AI agent memory?

Neither is better on its own. Vector search finds past statements similar in meaning to the question; a knowledge graph connects facts through the same entities and, with time on each fact, can tell which of two conflicting facts is current. Most agents should start with vector and keyword search over self-contained statements and add graph expansion when specific questions need facts joined through a person, company or project.

What is knowledge graph agent memory?

Knowledge graph agent memory stores what an AI agent learns as entities and typed relationships, each with when it was true and where it came from, and retrieves by following connections from the entities in a question. It is built continuously from conversations: extract statements and entities, resolve each mention to one entity, write the relationship with its dates and source, close any fact it supersedes, and at question time combine similarity and keyword search with a bounded graph expansion.

What is a temporal knowledge graph?

A temporal knowledge graph is a knowledge graph in which every fact carries time: when it became true, when it stopped being true, and usually when the system recorded it. That lets an agent answer both "what is true now?" and "what was true on this date?", and lets it retire an outdated fact without deleting its history.

What is bi-temporal memory?

Bi-temporal memory keeps two clocks on every fact: valid time, when the fact was true in the world, and transaction time, when the memory recorded or stopped believing it. Valid time answers "what was true on 15 May?"; transaction time answers "what did the agent believe on 15 July?", which is the question behind most audits of a past answer.

What is the difference between valid time and transaction time?

Valid time is when a fact held in the real world; transaction time is when the system learned it. They diverge whenever information arrives late, is backfilled or is corrected: a job change in June that the user mentions in September has a June valid time and a September transaction time.

What is a context graph?

A context graph is a knowledge graph built to supply an agent's context: entities, the relationships between them, when each fact was true and where it came from. In practice it is a temporal knowledge graph with provenance, and the same design questions apply: extraction cost, entity resolution and how changed facts are handled.

How do I stop an agent using stale memory?

Decide what is current when a new statement is written, not only at read time: close the old fact's validity period when a new one supersedes it, keep it for history, and filter retrieval to currently valid facts by default. Recency boosts and decay help ranking but still let stale facts into the prompt, and only single-valued relationships, such as a current employer, should supersede.

Do business facts need valid time too?

Business facts need both clocks as much as user facts do. Pricing rules, contract terms and data definitions change in meetings and contracts, often with no dated record, and a rule for last year's data can arrive in a message sent this morning. An agent that checks only how recent its context is will apply a new rule to old periods. Recording when each business fact applied and when the agent learned it lets the agent answer for the period in the question.

Why use a temporal knowledge graph for agent memory?

Facts about users change, and agents are asked both current and historical questions. Without time on each fact, a memory returns whichever of two conflicting facts ranks higher. LongMemEval, a 500-question benchmark for long-term chat memory, treats temporal reasoning and knowledge updates as two of its five core abilities for this reason.

Is BM25 or grep enough for agent memory?

For finding passages in documents, often yes: in InfiniFlow's 30 October 2026 RAGFlow paper, an agent using only BM25 and grep with MiniMax M3 answered 85% of BrowseComp-Plus and 91% of FRAMES in the authors' own runs, and adding summary trees, a wiki or a graph did not improve FRAMES accuracy. Agent memory asks a different question, what was true, for whom and when, and grep over raw transcripts returns an old fact and its correction side by side with no view on which is current. Keep keyword search for exact identifiers and plain lookups, and handle time, identity and supersession when memories are written.

Can I use a knowledge graph and vector search together?

Using both is the usual production design: similarity and keyword search find the most relevant statements and entities, the graph expands from those entities to connected facts, and the merged results are filtered by validity, ranked and trimmed to a token budget before they reach the prompt.

Do I need a dedicated graph database for agent memory?

Usually not at the scale of one agent's memory. Relationships can live in PostgreSQL using recursive queries or the Apache AGE extension, next to pgvector for embeddings, and validity periods fit in range columns. A dedicated graph database makes sense when traversals are deep, variable in depth and central to the product, or when you already run one.

Most of the extra cost is an LLM extraction call on every write. With a turn of 400 tokens and an extraction call of 2,000 input and 250 output tokens, embedding costs about $8 per million turns with text-embedding-3-small, while extraction costs about $325 per million turns on Claude Haiku 5.5 and $6,500 on Claude Sonnet 5.5, at list prices checked on 3 November 2026.

How does Maximem Synap handle knowledge graphs and changing facts?

Maximem Synap stores discrete statements rather than transcript chunks, resolves "my manager", "Sarah" and "Sarah Chen" into one entity across conversations, and expands through the entity graph in its Accurate retrieval mode. Each statement records when the thing it describes actually happened. When a fact changes, the old statement is marked historical and linked to its replacement with the reason, and a newer statement that is less detailed never replaces a more detailed one.

Sources, retrieved 27 October 2026, with the Zep and Mem0 material and the graph memory survey retrieved 30 October 2026, and prices, PostgreSQL status, the RAGFlow paper and the LangChain post retrieved 3 November 2026: Yu et al., Context as Infrastructure: Knowledge Compilation for Retrieval in Agentic Systems with Harnesses, arXiv 2610.41172; LangChain, Building Evidence-Grounded Agent Memory with OpenWiki and Graphs; pgvector on GitHub; Neo4j Cypher manual, vector indexes; OpenAI, text-embedding-3-small model page; Anthropic, Claude pricing; Edge et al., From Local to Global: A Graph RAG Approach, arXiv 2404.16130; Su et al., Temporal Knowledge Graph Question Answering: A Survey, arXiv 2406.14191; Wu et al., LongMemEval, arXiv 2410.10813; Rasmussen et al., Zep: A Temporal Knowledge Graph Architecture for Agent Memory, arXiv 2501.13956; Graphiti on GitHub; Niksarli and Baheti, A Graph-Native Bitemporal Memory Store for Conversational AI Agents, arXiv 2607.26520; Wikipedia, Temporal database; Martin Fowler, Bitemporal History; Zep, 7 reasons context graphs are unusually effective; Cognee, Best Time-Aware AI Memory Platforms for Conflicting Data; Pound, Mika and Zaragoza, Ad-hoc Object Retrieval in the Web of Data, WWW 2010; Wikipedia, Spreading activation; PostgreSQL documentation, WITH queries; PostgreSQL 19 temporal tables; PostgreSQL 19 release notes (RC 1); Apache AGE on GitHub; Maximem Synap documentation, context fetch; Maximem, File Search vs Vector Search for RAG; Zep, Metadata is the easy part of a data context layer; Zep, How Zep Context Graphs keep two timelines for every fact; mem0ai/mem0 pull request #4805; Yang et al., Graph-based Agent Memory: Taxonomy, Techniques, and Applications, arXiv 2602.05665.

From the team at Maximem

Stop rebuilding agent memory from scratch

Maximem Synap is the context management layer we built after hitting every problem in this post ourselves. Persistent recall across sessions, entity resolution and conscious forgetting, in Python, TypeScript and REST.

Related posts

Looking for more to read? See our directory of the best engineering blogs to follow.