What are entities and relationships in agent memory?

Short answer: Entities are recurring people, projects, or things identified across memories; relationships are the connections between them.

Recognizing an entity lets a system link every memory about the same person or project instead of treating each mention as unrelated text. Formal links between objects enable traversal but cost extra lookups at query time. Denormalizing a name onto the memory avoids that cost and puts the detail into the embedding, at the price of some redundancy. Low-cardinality links may justify formal references; high-cardinality ones often favor denormalization. Engram supports that judgment on real entity relationships.

Memory rarely stays isolated to single, self-contained facts for long. People, organizations, projects, and things keep recurring across many different memories, and the relationships between them, who works with whom, what belongs to what, carry real meaning of their own. This chapter looks at how a memory system actually represents these recurring entities and the connections between them, and what tradeoffs come with the different ways of doing it.

What Does It Actually Mean for a Memory System to Recognize an Entity Rather Than Just Text?

An entity is a specific, recurring thing a memory system can identify consistently across many different stored memories, a particular person, a particular company, a particular project, rather than just a string of characters that happens to appear in some text. Recognizing something as an entity matters because it lets a system connect every memory that mentions that same thing, even when different memories phrase the reference differently, a full name in one place and a nickname or role title in another. Without this kind of consistent recognition, a system risks treating what’s actually the same underlying person or thing as several disconnected, unrelated mentions scattered across its stored memories.

How Do Formal Relationships Between Objects Actually Get Represented in an Underlying Data Store?

A formal relationship, sometimes implemented as a cross-reference, is a direct link from one stored object to another, letting a system explicitly capture that two things are connected without needing to restate the full detail of that connection every single time. This kind of explicit link can represent genuinely complex relationships cleanly, one person connected to several projects, one project connected to several team members, without duplicating information across every single related object. The appeal is real: a formal link keeps a system’s data normalized, each fact stored exactly once and referenced from wherever it’s needed.

Why Does This Formal Approach Come With a Real, Measurable Cost at Query Time?

Resolving a formal link at search time requires an additional lookup, fetching the referenced object separately from the object that pointed to it, and that additional lookup costs roughly as much as looking up both objects independently in the first place. This cost is manageable for an occasional, isolated lookup, but it compounds quickly once a relationship has high cardinality, one entity connected to many others, since resolving that relationship now means many separate lookups rather than just one. A formal link also doesn’t get folded into an object’s own vector embedding, meaning the connected information plays no role in how that object gets found through semantic search, only in what gets fetched afterward once it’s already been found some other way.

What Does the Simpler Alternative of Denormalizing a Relationship Actually Look Like in Practice?

Denormalizing a relationship means directly embedding the relevant connected detail into the object that needs it, rather than making that object point elsewhere to find it. Instead of a project memory formally linking out to a separate team-member object, the project memory simply includes the team member’s name directly as one of its own properties, redundantly stored but immediately available without any additional lookup. This redundancy costs a small amount of extra storage, since the same detail might now be repeated across several related objects, but it avoids the compounding lookup cost of formal links entirely and, importantly, lets that detail actually participate in the object’s own vector embedding, making it directly searchable rather than merely fetchable afterward.

How Should a Team Actually Decide Between These Two Approaches for a Given Relationship?

A relationship with low cardinality, one entity connected to only a handful of others, is a reasonable candidate for a formal link, since the lookup cost stays modest and the normalized structure avoids unnecessary duplication. A relationship with high cardinality, one entity connected to potentially hundreds or thousands of others, is far better served by denormalizing the specific detail a search actually needs directly onto the objects that need it, sidestepping what would otherwise become an expensive, repeatedly resolved link. This isn’t a universal rule so much as a genuine cost tradeoff that has to be weighed against how that specific relationship actually gets used in practice.

How Does Weaviate Engram Let a Team Apply This Judgment to Real Entity Relationships in Memory?

Weaviate Engram stores memories as objects with structured properties, letting a team denormalize a relevant connection directly onto a memory rather than defaulting to a formal, separately resolved link for every single relationship a system happens to track. Consider a genealogy research platform’s family-history assistant, helping researchers trace connections across many generations of a family tree:

from engram import EngramClient

client = EngramClient(api_key=os.environ["ENGRAM_API_KEY"])

client.memories.add(
    "Margaret Ashworth, born in 1897, emigrated from Liverpool to Halifax in 1912 aboard the SS Corsican, traveling with her younger brother Thomas Ashworth after their parents' passing.",
    properties={"tree_id": "ashworth-family-tree", "person_name": "Margaret Ashworth", "related_person": "Thomas Ashworth"},
)

Because the sibling relationship is denormalized directly onto this memory as a property, rather than requiring a separate lookup into a distinct Thomas Ashworth record, a researcher can search for either sibling and immediately surface this shared emigration story without Engram needing to resolve any additional link:

results = client.memories.search(
    query="What do we know about Thomas Ashworth's emigration from Liverpool?",
    properties={"tree_id": "ashworth-family-tree"},
)

A family tree with thousands of interconnected individuals, each potentially related to dozens of others across many generations, would make a formal, separately resolved link for every single relationship prohibitively expensive to query at scale. Denormalizing the specific relationships that actually matter for a given memory, directly onto that memory’s own properties, keeps this kind of cross-generational search fast and keeps the connected names genuinely part of what gets matched during retrieval, exactly the kind of deliberate structural choice this chapter has been describing.

Entities and relationships give memory a way to connect recurring people and things across many separate stored facts, whether through formal links or denormalized properties. A more elaborate structure exists for representing these same kinds of connections at a larger scale, organizing entities and their relationships into a dedicated knowledge graph that complements, rather than replaces, vector search. Our next chapter, How do knowledge graphs complement vector search?, takes up exactly that structure.