What are graph-based memory architectures?

Short answer: Pattern Four stores entities and edges so retrieval can traverse relationships, not only match embedded text.

Multi-hop questions about shared suppliers, failures, and compatibility beat flat vector recall. Engram stays the personalization and experiential layer; the graph holds the relational world model. Most systems combine both and keep writes consistent across layers. Maintain edges like memories: close old links, do not only append. Authority for official decisions still needs a governed context plane.

Pattern Four stores memory as entities and edges, not only as embedded paragraphs. A node might be a cultivar, a rootstock, or a failed graft. An edge might say compatible_with, caused, or supersedes. Retrieval becomes traversal as much as similarity. That is why graph memory shows up when multi-hop questions keep beating flat vector recall. “Which rootstocks both failed on clay soil and share a supplier with last spring’s batch?” needs structure. Weaviate Engram still matters in this architecture. It remains the strong layer for user-scoped personalization and curated experiential text. The graph holds the relational world model. Most production systems combine them rather than replacing Engram with a graph alone.

This chapter defines graph memory, when to add it on top of flat or tiered Engram stores, how to keep writes consistent across both layers, and a hybrid retrieve loop. The enterprise context layer that follows is about organizational authority, not only topology.

What problem does a graph solve that Engram’s flat memories do not?

After tiered architectures, long-horizon agents can page the right text into context. They can still fail on relational questions. Flat Engram memories are excellent at “this user prefers X” and “last time we learned Y.” Hybrid search finds semantically close notes. It does not guarantee a path from supplier to lot to failure mode unless that path was written into one sentence. Graph research on agent memory emphasizes explicit relationships, temporal edges, and traversals that survive when wording changes.

Vector haze is the twin failure. Similar but wrong episodes retrieve together. On a graph, contradictions about the same entity can sit on one node with conflicting edges, which makes supersession policies easier to express. Temporal edges can close validity intervals when a fact changes. Engram transform already reconciles textual memories. Graphs reconcile structured claims about shared entities.

Use a graph when your domain is entity-heavy and questions are multi-hop. Skip it when the product is mostly preference chat and ticket continuity. Pattern Two or Three with Engram is enough there, and cheaper to operate.

How should Engram and a graph divide responsibility?

Once you decide a graph is warranted, split jobs cleanly. Engram owns personalization and experience prose: scoped by user_id, extracted through pipelines, searched with hybrid retrieval, maintained with prune and supersede. The graph owns canonical entities and typed links that many users or agents may share: parts, sites, batches, dependencies. Application code writes both when an event is rich enough. A graft failure note becomes an Engram memory for the grower’s style and a graph update linking cultivar, rootstock, soil class, and outcome.

Do not dump every Engram string into the graph as a node. That recreates a flat store with worse query ergonomics. Extract entities deliberately. Keep edge types small and documented. Prefer deterministic keys for entities so “Honeycrisp” does not fork into three spellings.

Read paths should be intent-aware. Preference questions hit Engram first. Root-cause and lineage questions traverse the graph first, then optionally pull Engram notes attached to the same entity ids via properties. Fusing both into the prompt beats forcing one store to do both jobs poorly.

What does a hybrid Engram-plus-graph turn look like?

Knowing the split, assemble context from both. Imagine an orchard agent on orchard-grafting-bench-6. Engram remembers the grower’s handling prefs. A lightweight in-app graph remembers compatibility and past failures.

import os
from collections import defaultdict
from engram import EngramClient, HybridRetrieval

client = EngramClient(api_key=os.environ["ENGRAM_API_KEY"])
grower = "grower-amina-holt"
bench = {"bench_id": "orchard-grafting-bench-6"}

# Minimal entity graph for the relational tier (app-owned).
NODES = {
    "cultivar:honeycrisp": {"type": "cultivar", "name": "Honeycrisp"},
    "rootstock:m7": {"type": "rootstock", "name": "M.7"},
    "rootstock:b9": {"type": "rootstock", "name": "Bud.9"},
    "soil:clay-loam": {"type": "soil", "name": "clay loam"},
    "event:graft-fail-2025": {"type": "event", "name": "spring 2025 graft failure"},
}
EDGES = [
    ("cultivar:honeycrisp", "grafted_on", "rootstock:m7", {"year": 2025}),
    ("event:graft-fail-2025", "involves", "cultivar:honeycrisp", {}),
    ("event:graft-fail-2025", "involves", "rootstock:m7", {}),
    ("event:graft-fail-2025", "observed_on", "soil:clay-loam", {}),
    ("rootstock:m7", "weak_on", "soil:clay-loam", {"note": "wet feet risk"}),
    ("cultivar:honeycrisp", "preferred_rootstock", "rootstock:b9", {"for_soil": "clay-loam"}),
]

def neighbors(node_id: str, rel: str | None = None):
    for src, edge, dst, meta in EDGES:
        if src == node_id and (rel is None or edge == rel):
            yield edge, dst, meta
        if dst == node_id and (rel is None or edge == rel):
            yield f"rev:{edge}", src, meta

def multi_hop(start: str, max_hops: int = 2):
    seen = {start}
    frontier = [start]
    paths = []
    for _ in range(max_hops):
        nxt = []
        for node in frontier:
            for edge, other, meta in neighbors(node):
                if other in seen:
                    continue
                seen.add(other)
                nxt.append(other)
                paths.append((node, edge, other, meta))
        frontier = nxt
    return paths

def engram_context(query: str):
    return client.memories.search(
        query,
        user_id=grower,
        group="personalization",
        properties=bench,
        retrieval_config=HybridRetrieval(limit=5),
    )

def record_event_and_preference():
    # Relational write
    EDGES.append(
        ("event:graft-fail-2025", "lesson", "rootstock:b9", {"text": "prefer B.9 on clay"})
    )
    # Experiential / personal write through Engram
    run = client.memories.add(
        "On orchard-grafting-bench-6, after the Honeycrisp/M.7 failure on clay loam, "
        "I prefer Bud.9 for Honeycrisp in wetter blocks.",
        user_id=grower,
        group="personalization",
        properties={**bench, "entity_ids": "cultivar:honeycrisp,rootstock:b9,soil:clay-loam"},
    )
    client.runs.wait(run.run_id)

def answer_prep(query: str):
    paths = multi_hop("cultivar:honeycrisp", max_hops=2)
    graph_lines = [
        f"- {a} -[{e}]-> {b} {meta}" for a, e, b, meta in paths
    ]
    memories = engram_context(query)
    mem_lines = [f"- {m.content}" for m in memories]
    return (
        "You are a grafting advisor for orchard-grafting-bench-6.\n"
        "Graph context (relational):\n"
        + "\n".join(graph_lines)
        + "\nGrower memories (Engram):\n"
        + "\n".join(mem_lines or ["- (none)"])
    )

record_event_and_preference()
print(answer_prep("What rootstock should I use for Honeycrisp on clay?"))

The graph answers lineage. Engram answers the grower’s adopted preference in natural language, scoped to the bench. Properties can carry entity ids so later jobs join the two stores without brittle string matching.

What operational burdens come with graph memory?

Graphs demand ontology discipline. Edge inflation makes traversal noisy. Entity resolution failures split the world into duplicates. Dual writes can drift if one store updates and the other does not. Put a single application service in front of both Engram and the graph so events commit through one API. On failure, retry or compensate. Verify with multi-hop eval questions, not only recall@k on text.

Temporal and causal edges help supersession, but only if writers set them. Without validity metadata, a graph becomes a second place for stale facts to hide. Apply the same maintenance instincts you use for Engram: close old edges, do not only append.

When the hard problem is no longer “how are these entities linked” but “what has the organization officially decided,” you need an authority layer above both personal Engram memory and exploratory graphs.

Our next chapter, What is the enterprise context layer pattern?, covers governed organizational knowledge as a distinct memory plane agents must respect.