📦 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.
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
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],
)
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)
"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.
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)
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
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
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
- Companion repo — demo 03 with the scorecard and notebook
- RAG vs GraphRAG: When Agents Hallucinate Answers — how graph structure prevents hallucinated connections in RAG retrieval
- neo4j-graphrag for Python — the two retriever classes used here
- MAGMA: A Multi-Graph based Agentic Memory Architecture for AI Agents — Jiang et al., 2026
- GAAMA: Graph Augmented Associative Memory for Agents — Paul et al., 2026
- GRAVITY: Structured Anchoring for Long-Horizon Conversational Memory — Sun et al., 2026
- Zep: A Temporal Knowledge Graph Architecture for Agent Memory — Rasmussen et al., 2025
Which of the five prompting rules surprised you most? Share in the comments.
Gracias!
🇻🇪 Dev.to Linkedin GitHub Twitter Instagram Youtube


Top comments (0)