ap.Abhishek Patel

Search

Search notes, essays, projects, the library and the AI course. Use arrow keys to choose and Enter to open.

Lesson 1 of 3 25 min

Why graph memory matters

Recognise multi-hop questions that vector memory cannot answer, model facts as nodes, edges and triples, and walk a small graph in plain Python.

Words in this lessonKnowledge graph · Node (in a graph) · Edge (in a graph) · Triple · Multi-hop question · Graph memory
Knowledge graph
A way of storing information as things and the named relationships between them. Like a detective's corkboard of photos joined by labelled red string.
Node (in a graph)
One step in a graph: a function that reads the current state and returns the changes it wants to make. Like one station in a bakery kitchen, such as decorating, which reads the ticket and writes down only what it did.
Edge (in a graph)
A fixed arrow that says which step runs after another. Like the arrow on a kitchen chart saying that baking always goes to decorating.
Triple
The smallest graph fact: a subject, a relationship and an object, like (Aarav, ALLERGIC_TO, peanuts). Like one card, one piece of string and the card at the other end.
Multi-hop question
A question you can only answer by following several relationships in a row. Like asking "who is my cousin's teacher's neighbour?": you have to follow each link in turn.
Graph memory
Agent memory stored as nodes and relationships, so the agent can answer by following connections. Like a family tree and order history kept alongside your diary, instead of only the diary.
On this page
  1. Arjun's first attempt: ask the memory
  2. Priya's corkboard
  3. Walking the strings: one question, five hops
  4. Opening the hood: nodes, edges and triples
  5. The real code: walking a graph in plain Python
  6. When to use which
  7. Where it breaks
  8. Try it yourself
  9. Common mistakes
  10. Recap
  11. Go deeper

The call came at 10:40 on a Tuesday, three weeks before Diwali. Crumb’s dry-fruit supplier was apologetic. The batch of cashews that went into this season’s kaju katli had been processed on a line that also handles peanuts. Their lab had just confirmed it: may contain traces of peanuts.

The kaju katli was in every Classic Diwali Hamper. Crumb had already sold 1,240 of them, and about 700 had been delivered.

Meera did not waste words. “I want a list of every customer who bought a Classic hamper this season and has a peanut allergy in the household: themselves, a child, anyone they’ve told us about. The store managers start calling at noon. Arjun, you have an hour.”

Arjun had spent all of Module 15 building memory. Sunita’s peanut allergy was in there. Her son Aarav’s allergy was in there. This, he thought, was exactly what memory was for.

Important

The idea in one sentence: Vector memory finds pieces of text that sound like your question, but questions like “who bought something containing an ingredient that someone in their family is allergic to” need you to follow relationships across several facts, and a graph, which stores things as nodes connected by named relationships, is built for exactly that.

Arjun’s first attempt: ask the memory

He opened a Python shell and searched the memory store across all customers, staff-side, with the most direct query he could think of: “customer with peanut allergy who bought Diwali hamper”. He asked for the top 20.

The results looked convincing at first glance. Every one of them mentioned peanuts or hampers. Then he read them properly.

Rank Memory Useful?
1 (customer) Asked whether the Diwali hamper contains peanuts Unclear: asking is not being allergic
2 (customer) Allergic to peanuts. Confirmed twice. [Sunita] Yes, but did she buy a hamper this season?
3 (customer) Loves peanut chikki, orders it every week [Rohan] No: the opposite of allergic
4 (son Imran) Cannot eat peanuts [Farah] Maybe: did Farah buy a Classic hamper?
5 (customer) Diwali hamper arrived late; given a voucher No
… … …

Not one result answered the question by itself. Each memory was a separate sentence about one thing. To answer Meera, he needed to combine facts that live in different places:

  • Who is allergic to peanuts? That is in the memory store.
  • Who is in whose family? Also in memory, as text like “(son Imran)”.
  • Who bought a Classic hamper this season? That is in the order database, not in memory at all.
  • What is in a Classic hamper? That is in the recipe archive.
  • Which ingredients may contain peanuts? That is the supplier’s call, from twenty minutes ago.

He tried pasting everything into Claude: 1,240 orders, the hamper recipe, and every memory that mentioned an allergy. The prompt came to about 210,000 tokens. Claude produced a list of 14 names. When he spot-checked it, two of the names had never bought a hamper, and Farah, whose son Imran is allergic, was missing for a reason he could not work out. With people’s health at stake, “mostly right” was not good enough.

At 11:05, he went to find Priya.

Note

Pause and think. Vector search returned Rohan (“loves peanut chikki”) as the third most relevant result for a question about peanut allergies. Why would a similarity search do that?

Show the answer

An embedding captures what a text is about: peanuts, chikki, hampers, customers. “Loves peanut chikki” and “allergic to peanuts” are about very similar things, so their vectors are close, even though the relationship between the person and the peanuts is completely opposite. Similarity search is good at “what is this about?” and poor at “how exactly are these things related?”.

Priya’s corkboard

Priya did not open a laptop. She pinned an index card to the corkboard by her desk and wrote peanuts on it. Then Kaju Katli. She ran a piece of red string between them and wrote on the string: MAY CONTAIN.

“You’ve seen this in every detective film. Photos of suspects, places, objects, and red string between them labelled ‘was seen at’, ‘works for’, ‘owns’. Why do detectives do this instead of keeping a pile of notes?”

“Because the answer is in the connections,” Arjun said slowly. “Not in any single card.”

“Exactly. Your memory store is a pile of notes, beautifully sorted by topic. Your question is a connections question. It says: start at peanuts, follow the string to what may contain them, follow that to what includes it, then to the orders, then to the buyers, then check each buyer’s family. That’s five pieces of string. Your vector search can only ever look at one card at a time.”

She added more cards and string as they talked. Within ten minutes the corkboard showed this:

flowchart LR
  P["peanuts"]
  K["Kaju Katli"] -->|MAY_CONTAIN| P
  H["Classic Diwali Hamper"] -->|CONTAINS_ITEM| K
  O1["Order O1001"] -->|INCLUDES| H
  O2["Order O1044"] -->|INCLUDES| H
  S["Sunita"] -->|PLACED| O1
  R["Rohan"] -->|PLACED| O2
  S -->|ALLERGIC_TO| P
  S -->|PARENT_OF| A["Aarav"]
  A -->|ALLERGIC_TO| P
  F["Farah"] -->|PARENT_OF| I["Imran"]
  I -->|ALLERGIC_TO| P

“Now,” Priya said, “answer Meera’s question by putting your finger on peanuts and walking the strings.”

Walking the strings: one question, five hops

Arjun traced it on the board, out loud. Each step of following a relationship from one thing to the next is called a hop. Questions that need several hops are called multi-hop questions.

Hop 1. Start at peanuts. Which things point to it with MAY_CONTAIN? → {Kaju Katli}

Hop 2. Which products have CONTAINS_ITEM pointing at Kaju Katli? → {Classic Diwali Hamper}

Hop 3. Which orders INCLUDE the Classic Diwali Hamper? → {O1001, O1044}

Hop 4. Who PLACED those orders? → {Sunita, Rohan}

Hop 5. For each buyer: are they ALLERGIC_TO peanuts, or a PARENT_OF someone who is? Sunita: yes, twice over (herself and Aarav). Rohan: no allergy, no recorded family. → Call Sunita.

“What about Farah?” Arjun asked. “Imran is allergic.”

“Follow her string.”

Farah’s PLACED string led to order O1102, which included the Millet Ladoo Box, not a Classic hamper. No path from Farah to the risky kaju katli existed. Farah did not need a call about this recall. On Arjun’s earlier attempt, a model reading 210,000 tokens had been unsure about her. On the corkboard, the answer was obvious and explainable: here is the path, or here is why there is no path.

That last point mattered to Meera more than anything else. When a store manager asks, “Why am I calling this customer?”, the graph can answer: because she placed order O1001, which included the Classic hamper, which contains kaju katli, which may contain peanuts, and she is allergic to peanuts. Every link is a fact somebody can check.

Note

Pause and think. Rohan bought a Classic hamper but has no recorded allergy. The graph says “do not call”. Is that answer certain?

Show the answer

No. It is certain given what the graph knows. If Rohan’s daughter has a peanut allergy he never mentioned, there is no string for it, and the graph cannot invent one. A graph’s answers are exactly as complete as its data. That is why Meera’s team also put a recall notice on the website and WhatsApp for all hamper buyers. The graph decides who gets an urgent personal phone call; it does not replace the broader warning.

Opening the hood: nodes, edges and triples

Now the proper names for what Priya drew.

A knowledge graph is a way of storing information as things and the relationships between them.

  • A node is a thing: a customer, an order, a product, an allergen. Each node has a label that says what kind of thing it is (Customer, Product) and properties, which are key-value details (name: "Sunita", price: 1499).
  • An edge (also called a relationship) connects two nodes. It has a direction and a type, like PLACED or ALLERGIC_TO, and it can have properties too: INCLUDES {quantity: 2}.
  • A triple is the smallest unit of a graph fact: (subject, relationship, object), such as (Sunita, ALLERGIC_TO, peanuts). A whole graph can be written as a list of triples. Each triple is one card plus one piece of string.

This style is called a property graph, because nodes and edges carry properties. It is what Neo4j and Kuzu, the databases in the next lesson, use. You may also run into RDF, an older W3C standard where everything is a triple, often used for public knowledge bases and linked data. The ideas are the same; the tools differ.

Graph memory is this idea applied to agents: alongside (or instead of) storing memories as separate sentences, the agent stores them as nodes and edges, so it can answer by following relationships. “(son Aarav) cannot eat peanuts” becomes two triples: (Sunita, PARENT_OF, Aarav) and (Aarav, ALLERGIC_TO, peanuts). The relationship that was hidden inside a string is now something you can walk along.

The real code: walking a graph in plain Python

Before any database, here is the whole idea in a few lines of plain Python, so you can see there is no magic. The graph is just a list of triples.

# Every fact is a triple: (subject, relationship, object).
TRIPLES = [
    ("Sunita", "ALLERGIC_TO", "peanuts"),
    ("Sunita", "PARENT_OF", "Aarav"),
    ("Aarav", "ALLERGIC_TO", "peanuts"),
    ("Rohan", "LIKES", "Peanut Chikki"),
    ("Farah", "PARENT_OF", "Imran"),
    ("Imran", "ALLERGIC_TO", "peanuts"),
    ("Sunita", "PLACED", "O1001"),
    ("Rohan", "PLACED", "O1044"),
    ("Farah", "PLACED", "O1102"),
    ("O1001", "INCLUDES", "Classic Diwali Hamper"),
    ("O1044", "INCLUDES", "Classic Diwali Hamper"),
    ("O1102", "INCLUDES", "Millet Ladoo Box"),
    ("Classic Diwali Hamper", "CONTAINS_ITEM", "Kaju Katli"),
    ("Kaju Katli", "MAY_CONTAIN", "peanuts"),
]

Two tiny helper functions let you follow an arrow forwards or backwards:

def objects(subject: str, rel: str) -> set[str]:
    """Follow arrows forwards: subject -[rel]-> ?"""
    return {o for s, r, o in TRIPLES if s == subject and r == rel}


def subjects(rel: str, obj: str) -> set[str]:
    """Follow arrows backwards: ? -[rel]-> obj"""
    return {s for s, r, o in TRIPLES if r == rel and o == obj}

And Meera’s question becomes five hops, one line each:

risky_items = subjects("MAY_CONTAIN", "peanuts")                                  # hop 1
risky_products = {p for i in risky_items for p in subjects("CONTAINS_ITEM", i)}   # hop 2
risky_orders = {o for p in risky_products for o in subjects("INCLUDES", p)}       # hop 3
buyers = {c for o in risky_orders for c in subjects("PLACED", o)}                 # hop 4
allergic = subjects("ALLERGIC_TO", "peanuts")
to_call = {b for b in buyers if b in allergic or objects(b, "PARENT_OF") & allergic}  # hop 5

print("risky orders:", sorted(risky_orders))
print("buyers:      ", sorted(buyers))
print("CALL:        ", to_call)

Run it and you should see:

risky orders: ['O1001', 'O1044']
buyers:       ['Rohan', 'Sunita']
CALL:         {'Sunita'}

Look at hop 5’s objects(b, "PARENT_OF") & allergic. The & is a set intersection: “this buyer’s children who are also in the allergic set”. That single expression is the family check that the vector search could never do.

At full scale, Arjun loaded the real data the same way: 4,100 customers, the season’s 1,240 Classic hamper orders, every allergy and family fact from memory, and the hamper recipe. The walk took under a second. It returned 9 households: 6 where the customer was allergic, 3 where a child was. The store managers had their list at 11:52, with a reason attached to every name.

Tip

In plain English: a vector store answers “what have we written that sounds like this?”. A graph answers “what is connected to this, and how?”. Most real agents need both questions answered at different times.

When to use which

Priya was clear that graphs are not a replacement for everything Arjun had built.

Question Better tool Why
“What did Sunita say about her last delivery?” Vector memory Fuzzy, about one person, wording varies
“Find handbook pages about refund rules” Vector search (RAG) Meaning-based lookup over long text
“Which customers with allergic children bought hampers?” Graph Several hops across people, orders and products
“Which products share an ingredient with the walnut brownie?” Graph Product → ingredient → product
“How many orders did Baner deliver last week?” Plain SQL Simple counting over one table

The common production pattern is hybrid. Use vector search to find the starting point when the question is fuzzy (“the lady who complained about the late hamper” → Sunita’s node), then use the graph to walk relationships from there. You will hear this called GraphRAG. Microsoft Research’s GraphRAG project, for example, builds a knowledge graph from a document collection so it can answer broad questions that span many documents.

Where it breaks

The graph only knows what you put in. Rohan’s undeclared allergic daughter has no node. The graph cannot find a relationship nobody recorded. Garbage or gaps in, wrong answers out, delivered with false confidence because the query “ran successfully”.

Names are slippery. In the real memory store, one customer said “groundnuts”, another “peanut”, a third “peanuts”. As text, those are three different nodes, and a walk starting at “peanuts” misses two of them. Deciding that different names refer to the same real thing is called entity resolution, and it is the hardest part of building graph memory from conversations. Lesson 3 of this module tackles it.

Someone has to design the shape. Which node labels exist? Is “may contain” its own relationship, or a property on CONTAINS? Should a family member be a Person or a Customer? These choices, called the schema, decide which questions are easy to ask. Too loose, and the graph becomes a tangle; too strict, and new kinds of facts do not fit.

Plain Python does not scale. Arjun’s subjects() scans every triple on every call. With 2 million triples and a five-hop question, those nested loops crawl. Real graph databases store edges so that following one is almost instant, and give you a query language to describe a whole walk in one statement. That is the next lesson.

Try it yourself

Add these triples to TRIPLES:

("Asha", "PLACED", "O1203"),
("O1203", "INCLUDES", "Walnut Brownie Box"),
("Walnut Brownie Box", "CONTAINS_ITEM", "Walnut Brownie"),
("Walnut Brownie", "CONTAINS", "tree nuts"),
("Asha", "ALLERGIC_TO", "tree nuts"),

Then write a new walk that answers: “Which customers bought something containing an allergen they themselves are allergic to?” Do not hard-code “tree nuts” or “peanuts”.

Show the answer

Walk forwards from each customer instead of backwards from one allergen:

customers = {s for s, r, o in TRIPLES if r == "PLACED"}
for c in customers:
    my_allergens = objects(c, "ALLERGIC_TO")
    for order in objects(c, "PLACED"):
        for product in objects(order, "INCLUDES"):
            items = objects(product, "CONTAINS_ITEM") | {product}
            for item in items:
                hits = (objects(item, "CONTAINS") | objects(item, "MAY_CONTAIN")) & my_allergens
                if hits:
                    print(c, "->", order, "->", product, "->", item, "->", hits)

You should find Asha (walnut brownie, tree nuts) and Sunita (Classic hamper, kaju katli, peanuts). Notice how much harder this is to read than the forward-then-backward walk, and how easy it would be to miss a hop. That is exactly the pain a graph query language removes.

Common mistakes

  • Expecting similarity search to answer relationship questions. It finds related text, not related facts.
  • Hiding relationships inside strings. “(son Aarav) cannot eat peanuts” stores a relationship as prose. Make it a pair of triples if you will ever need to walk it.
  • Treating “no path” as “no risk”. A graph is only as complete as its data. Pair graph answers with broader safeguards when safety is involved.
  • Reaching for a graph for every question. Simple counts belong in SQL; fuzzy lookups belong in vector search. Graphs earn their place on multi-hop questions.
  • Skipping the schema conversation. Decide your labels and relationship types before loading thousands of facts.

Recap

  • Vector memory answers “what sounds like this?”; graph memory answers “what is connected to this, and how?”.
  • A knowledge graph stores nodes (things, with labels and properties) and edges (named, directed relationships), and every fact is a triple.
  • Multi-hop questions need you to follow several relationships in a row; each step is a hop.
  • Graph answers are explainable: every result comes with the path that produced it.
  • Graphs are only as good as their data and their names; entity resolution and schema design are the hard parts.

At 11:58 Arjun sat with Meera and the store managers. “Imagine a detective’s corkboard,” he said. “Every customer, order, product and allergy is a card, and the string between them says how they’re connected. I start at peanuts and follow the string: to kaju katli, to the Classic hamper, to the orders, to the people who placed them, to their children. Nine households. For each one I can show you exactly which strings led there.”

“And Farah?” asked the Koregaon Park manager, who knew her well. “Her boy is allergic.” Arjun pulled up the path. “She bought the millet ladoo box this year. No string to the kaju katli.” The manager nodded and moved on. That evening Priya told Arjun the plain-Python version had been the right call for an emergency, and the wrong one for a system. Tomorrow he would put the graph into a real graph database and learn the language for asking it questions: Cypher.

Go deeper

Check your understanding

3 quick questions
1. Vector search for "customer with peanut allergy who bought Diwali hamper" ranked "Loves peanut chikki" highly. Why?
2. Which of these is a triple?
3. The graph says Rohan bought a Classic hamper but has no path to a peanut allergy. What can you safely conclude?