Agentic memory graph for an AI assistant
Objective
LLM agents that work over weeks need memory that survives context-window resets. A pure vector store can recall semantically similar past chats but loses sequence and entity links — the agent forgets that "the project I helped with last Tuesday" was the same one as "the deck the CFO asked about." A graph captures both: episodes and the entities they touched. The wow moment: one Cypher query retrieves "all past sessions about projects similar to today's question that involved the CFO" — multi-hop structural recall plus semantic search in one shot.
Step 1: Set up the agent's memory graph
// Episodes — one node per past chat session, with an embedding of its summary.
MERGE (e1:Episode {id: "EP-001", date: "2026-04-08",
summary: "User asked for help drafting Q2 OKRs for the Logistics team",
embedding: [0.21, 0.55, -0.10, 0.18, 0.07]})
MERGE (e2:Episode {id: "EP-002", date: "2026-04-15",
summary: "Built a Python script to ingest CSV freight invoices",
embedding: [-0.35, 0.12, 0.62, -0.04, 0.20]})
MERGE (e3:Episode {id: "EP-003", date: "2026-04-22",
summary: "Reviewed Q2 OKRs draft with CFO Devon Park, focused on margin",
embedding: [0.23, 0.57, -0.08, 0.19, 0.06]})
MERGE (e4:Episode {id: "EP-004", date: "2026-04-29",
summary: "Debugged Python ingestion script, fixed encoding bug",
embedding: [-0.34, 0.14, 0.60, -0.05, 0.22]})
MERGE (e5:Episode {id: "EP-005", date: "2026-05-01",
summary: "User asked for help finalising Q2 OKRs presentation",
embedding: [0.22, 0.56, -0.09, 0.17, 0.08]})
// Entities mentioned across episodes
MERGE (cfo:Person {name: "Devon Park", role: "CFO"})
MERGE (proj:Topic {name: "Q2 OKRs", kind: "planning"})
MERGE (team:Team {name: "Logistics"})
MERGE (script:Asset {name: "freight_ingest.py", kind: "script"})
// Mentions: episode -> entity
MERGE (e1)-[:MENTIONS]->(proj)
MERGE (e1)-[:MENTIONS]->(team)
MERGE (e3)-[:MENTIONS]->(proj)
MERGE (e3)-[:MENTIONS]->(cfo)
MERGE (e5)-[:MENTIONS]->(proj)
MERGE (e2)-[:MENTIONS]->(script)
MERGE (e4)-[:MENTIONS]->(script)
// Sequence: each episode FOLLOWS the previous one (timeline).
MERGE (e1)-[:FOLLOWED_BY]->(e2)
MERGE (e2)-[:FOLLOWED_BY]->(e3)
MERGE (e3)-[:FOLLOWED_BY]->(e4)
MERGE (e4)-[:FOLLOWED_BY]->(e5);
Step 2: Recall, semantic + structural
// Today's question (EP-005). Recall past episodes that are semantically similar
// AND involved the CFO — exactly the context this agent needs to be useful.
MATCH (today:Episode {id: "EP-005"})-[:SIMILAR_TO > 0.85]->(past:Episode)
MATCH (past)-[:MENTIONS]->(p:Person {role: "CFO"})
RETURN past.id AS recalled_episode,
past.date AS date,
past.summary AS summary,
p.name AS involved_exec
ORDER BY past.date DESC;
Step 3: New chat? Auto-extract entities into the memory graph
When a new conversation closes, run extraction on its summary so future recall has structural hooks too:
curl -X POST https://localhost:8443/v2/graph/extract \
-H "Authorization: Bearer $AIDB_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"text": "On May 1, 2026 the user finalised the Q2 OKRs presentation for the Logistics team and asked the CFO Devon Park to review the margin slides before the all-hands.",
"default_node_label": "Entity",
"node_provenance": {"episode_id": "EP-005", "kind": "agent_memory"},
"edge_provenance": {"episode_id": "EP-005"},
"min_confidence": 0.6
}'
What's happening
- Episodes are property bags with
embeddingarrays — the same node-as-property-bag model that carries documents and chat turns. SIMILAR_TO does the HNSW lookup; you do not maintain a parallel vector store. - Two-edge recall (
SIMILAR_TOthen:MENTIONS) is what vector-only memory cannot do: filtering by "involved the CFO" or "related to the Logistics team" requires structure. FOLLOWED_BYmakes the timeline traversable —MATCH (a)-[:FOLLOWED_BY*]->(b)gives you multi-step episode chains, useful for "what did we work on together in late April?"/v2/graph/extractkeeps the structural side of memory growing automatically: new sessions produce new entity edges withepisode_idprovenance so you can attribute facts back to conversations.- Same primitives used by Neo4j-agent-memory, Mem0, and Zep — but here in one engine, no glue service.
Try this next
MATCH (e:Episode)-[:MENTIONS]->(t:Topic {name: "Q2 OKRs"})
RETURN e.date AS date, e.summary AS summary
ORDER BY date;
MATCH path = (start:Episode {id: "EP-001"})-[:FOLLOWED_BY*1..5]->(end:Episode)
RETURN [n IN nodes(path) | n.summary] AS conversation_arc, length(path) AS span;
MATCH (current:Episode {id: "EP-005"})-[:SIMILAR_TO > 0.8]->(past:Episode)-[:MENTIONS]->(asset:Asset)
RETURN DISTINCT asset.name AS likely_relevant_asset;