DEV Community

Cover image for Graph Memory: When Vector Search Fails
Elizabeth Fuentes L for AWS

Posted on Originally published at builder.aws.com

Graph Memory: When Vector Search Fails

📦 Clone and ⭐ stop-ai-agents-losing-memory-sample-for-aws

AI agents accumulate facts across conversations. Key-value memory stores them as labeled blobs. Vector memory retrieves them by meaning. Neither can answer a question that spans multiple facts connected by relationships. Graph memory closes this gap by storing memories as nodes and typed edges.

Graph memory architecture: Strands agent takes two paths — recall_semantic returns pieces (1/4), recall_graph traverses Maya Torres → Iberia → Madrid → Spain (4/4)

This post uses a travel assistant as the demo, but the failure is structural, not travel-specific. It shows up in any agent that accumulates facts about people, places, products, or events over time. Eventually a user asks something that can only be answered by following the edges between facts. And there are no edges to follow.

The same structural gap causes agents to hallucinate answers to counting and aggregation questions. In RAG vs GraphRAG, I measured a hotel assistant that couldn't answer "how many hotels accept pets?" without inventing statistics, because it had no graph to compute over. Here the failure is multi-hop retrieval, but the root cause is the same: no edges to follow.

Here is what that failure looks like in a real run, with the travel assistant after it accumulated four facts about its user:

Facts in memory:
  Maya Torres works at Iberia.
  Iberia flies to Madrid.
  Madrid is in Spain.
  Iberia belongs to Oneworld.

Question: "Who do I know connected to flights to Spain?"

Top-3 vector similarity results:
  - Iberia. An airline.
  - Spain. A country.
  - Madrid. A city.

Recovers the person (Maya Torres)? False
Enter fullscreen mode Exit fullscreen mode

Similarity found every piece. It never found the person, because a vector index has no notion of a relationship between its entries. Graph memory fixes this by storing memories as nodes and typed edges, so the answer is reached by traversal instead of resemblance. This post builds it with Neo4j, measures the same four questions against both retrievers (1/4 vs 4/4), and shows the prompting technique that gets an AI assistant to build it right. Everything runs from the companion repo.

(Post 3 of a series; the intro maps all the memory types. This is the most advanced demo so far: it assumes the earlier posts and a Neo4j instance. The code uses Strands Agents; the pattern carries over to any agent framework.)


Why Strands Agents for this demo?

Strands makes it simple to add graph memory to an agent. Creating an agent is just a few lines of code, and tools are functions with a decorator:

from strands import Agent, tool

@tool
def recall_graph(query: str) -> str:
    """Search graph memory by traversing relationships."""
    return graph_retriever.search(query)

agent = Agent(
    model=model,
    tools=[recall_graph, recall_semantic, remember_fact],
)
Enter fullscreen mode Exit fullscreen mode

That's it. No custom integrations, no framework lock-in. The @tool decorator is all you need to plug Neo4j retrievers into the agent. When book_flight executes, it writes edges directly to the graph, and the knowledge graph grows with usage.

The pattern shown here (external graph + tool-based access) works in any agent framework. Strands just makes it straightforward.


What is a multi-hop question?

A question whose answer lives in no single memory, only in the chain between several. Stored as a graph, the assistant's four facts form one:

(Maya Torres) ──WORKS_AT──▶ (Iberia) ──MEMBER_OF──▶ (Oneworld)
                                 
                            FLIES_TO
                                 
                             (Madrid) ──IN_COUNTRY──▶ (Spain)
Enter fullscreen mode Exit fullscreen mode

"Who do I know connected to flights to Spain?" requires three hops: person → airline → city → country. Key-value memory can't express it (no key is "the chain"). Vector memory retrieves the three most similar fragments and stops. Only a store that keeps the edges can walk them.

Multi-hop question over agent memory: vector similarity surfaces Iberia, Madrid and Spain as disconnected pieces, graph traversal walks the edges back to Maya Torres


How does graph memory answer it?

In two moves: similarity finds the entry point, traversal finds the answer. Both retrievers in the demo are official neo4j-graphrag classes, sharing the same graph and the same vector index. The only variable is whether edges get walked:

from neo4j_graphrag.retrievers import VectorRetriever, VectorCypherRetriever

# Before: pure similarity — returns the nearest nodes, disconnected
before = VectorRetriever(driver, "memory_embeddings", embedder=embedder)

# After: similarity finds an entry node, then Cypher walks back to the person
RETRIEVAL_QUERY = """
WITH node AS entry, score
MATCH (person:Person) WHERE person <> entry
MATCH path = shortestPath((person)-[*1..5]-(entry))
RETURN person.name AS who, [n IN nodes(path) | n.name] AS chain, max(score) AS score
ORDER BY score DESC
"""
after = VectorCypherRetriever(driver, "memory_embeddings",
                              RETRIEVAL_QUERY, embedder=embedder)
Enter fullscreen mode Exit fullscreen mode

Same question, second retriever, same run:

- who='Maya Torres' chain=['Maya Torres', 'Iberia'] score=0.77
- who='Maya Torres' chain=['Maya Torres', 'Iberia', 'Madrid', 'Spain'] score=0.71

Recovers the person (Maya Torres)? True
Enter fullscreen mode Exit fullscreen mode

Note what the graph adds beyond the answer: the chain. Every result carries the path that produced it (Maya → Iberia → Madrid → Spain). That receipt is what makes graph memory traceable, and it becomes the star of a later post on auditing agent decisions.


What do the measured results show?

Four multi-hop questions, both retrievers, checked deterministically against the known graph (no LLM judge, so the numbers reproduce):

Question Vector similarity Graph traversal
Who do I know that's connected to flights to Spain?
Who do I know connected to an airline that flies to Madrid?
Who works at the Oneworld airline I know?
Which person is linked to airlines in Spain?
Total 1/4 4/4

The one similarity got right is worth pausing on: on question 3 the person node happened to rank high by resemblance alone. Similarity isn't always wrong on multi-hop questions; it's unreliable, while traversal is consistent. That's the actual finding, and it matches what the graph-memory research measures at scale (MAGMA, GAAMA, Zep).

The agent also writes back: told "remember that Maya works at Iberia", the Strands agent calls a remember_fact tool that MERGEs the edge into Neo4j and logs it to agent.state. The memory grows as a graph, one fact per conversation.


When is a graph the wrong choice?

When your memories are independent notes. A graph of disconnected nodes is a slow key-value store with extra steps, plus a database to run and a schema to think about. Skip a graph when nothing in your questions crosses more than one fact. The honest decision line, extending the series' table:

You need Pick Why
Facts under known keys Key-value (post 1) Exact, instant, zero infrastructure
Search by meaning over independent notes Vector (post 2) Similarity is enough when nothing connects
Questions that hop across relationships Graph (this post) Only edges answer chain questions, with receipts

Two more honest costs: you design the schema (every edge type must earn a real question: model "who do I know at X?", not everything), and connectivity cuts both ways, because one wrong fact contaminates every traversal that crosses it. That blast-radius problem gets its own post (memory hygiene).


How do you run the demo?

git clone https://github.com/elizabethfuentes12/stop-ai-agents-losing-memory-sample-for-aws
cd stop-ai-agents-losing-memory-sample-for-aws/03-graph-memory-demo
uv venv && uv pip install -r requirements.txt
cp .env.example .env   # OPENAI_API_KEY + your NEO4J_* values
uv run python test_graph_memory.py
Enter fullscreen mode Exit fullscreen mode

Needs a running Neo4j (Desktop, Docker, or the free Aura tier) and OPENAI_API_KEY for model + embeddings (or swap to Amazon Bedrock; the README shows how). The repo's README also documents a real version-churn gotcha (neo4j-graphrag 1.18 emits Cypher 25's SEARCH clause, which fails on servers still defaulting to Cypher 5) and how the demo handles it automatically.


FAQ

What is graph memory for AI agents?

Agent memory stored as a knowledge graph: entities as nodes, facts as typed edges, with a vector index for finding entry points. It answers relationship questions ("who do I know connected to X?") that key-value lookup and vector similarity structurally cannot, and every answer carries the chain of facts that produced it.

Knowledge graph vs vector memory: which does an agent need?

Vector memory when questions match individual memories by meaning; graph memory when answers span several memories connected by relationships. Measured here: vector similarity solved 1 of 4 multi-hop questions, graph traversal 4 of 4. Most production agents eventually want both, similarity to enter the graph and traversal to answer.

Is this the same as GraphRAG?

Same mechanism, different corpus. GraphRAG builds a graph over your documents; graph memory builds one over the user facts the agent accumulated across conversations. The retrieval pattern (vector entry point, then traversal) is identical, which is why the official graph-RAG retriever classes work unchanged here.

Do I need an LLM to build the graph?

Not for this pattern. I write facts as explicit MERGE statements from a tool the agent calls, which keeps results reproducible. LLM entity extraction, such as SimpleKGPipeline, automates graph construction from raw text at the cost of determinism: a production option, not a requirement here.

How do you benchmark agent knowledge-graph memory?

Deterministically: fix a known graph, write multi-hop questions whose answers you can verify by construction, run each retriever, and count. An LLM judging its own retrieval adds noise. The demo's 1/4 vs 4/4 scorecard reproduces run after run because the check is structural, not judged.


Resources


Which of the five prompting rules surprised you most? Share in the comments.


Gracias!

🇻🇪 Dev.to Linkedin GitHub Twitter Instagram Youtube


Top comments (0)