Structure in agentic memory

Exploring the landscape of structure in existing agentic memory systems and how to implement them in TypeDB

Samuel Butcher


Memory with structure

An agent remembers that you prefer weekly status emails. Three months later, you tell it that monthly summaries are enough. Both statements are stored, but only one reflects your current preference.

A vector search can retrieve both, but similarity alone cannot establish which one supersedes the other, when the change took effect, or which conversation supports the conclusion.

This is the problem that structured memory addresses.

Memory systems need more than a collection of semantically similar passages. They need ways to represent what a memory means, how it relates to other memories, when it was true, where it came from, and what rules govern it.

This post examines how existing agent memory systems approach those problems, and how to model them in TypeDB, where relationships, types, and constraints can be expressed in the database schema itself.

What structure is for

“Structure” covers several separate jobs, and memory systems differ in which of them they take on.

  • Kind. What sort of memory is this: a preference, a decision, a commitment, an event? Recall can filter by kind, and different kinds can age at different rates.
  • Connection. How memories relate to each other and to the people, projects and artifacts they concern. Connections make multi-step recall possible.
  • Time. When something was true, distinct from when the agent learned it. Without this, a changed fact contradicts its predecessor instead of replacing it.
  • Provenance. Whether a memory was observed or inferred, and from what. This is what makes an agent’s beliefs auditable.
  • Constraints. What the store refuses to accept. This matters more when the writer is a language model.

How current systems structure memory

The systems below represent an array of successfully and widely-used agentic memory projects, each benefiting from structure in different ways.

Supermemory: memories that link to memories

Supermemory deliberately avoids subject–predicate–object triples. It extracts atomic facts and links each new fact to existing ones through three relationship types:

  • Updates: the new fact replaces an old one, such as a change of job.
  • Extends: the new fact adds detail without replacing anything.
  • Derives: a new fact is inferred from a pattern in others.

An isLatest field marks the current version of a fact, so searches return current information while history is kept. The result is a graph of facts linked to other facts, where the link types carry most of the meaning.

MemOS: lifecycle and provenance on every memory

MemOS attaches more metadata to each memory than most systems. Every memory has:

  • one of ten lifecycle types, such as working memory, long-term memory, preference memory or skill memory
  • a status: activated, resolving (while a conflict is being handled), archived or deleted
  • a version number and a history of earlier versions, with each archived version kept as its own linked node
  • structured source records saying where the memory came from: a chat turn, a document, a file
  • an optional confidence score

Memories are connected by typed edges, including cause, condition, conflict, follows and parent. A reorganiser clusters related memories and creates new parent nodes that summarise each cluster.

Graphiti and Zep: a declared ontology

Graphiti, the open-source engine behind Zep, stores memory as a temporal knowledge graph of episodes, entities and community summaries. The system records when a fact was true and when it was ingested, and contradicted facts are marked with an end date rather than deleted.

Developers can declare entity and edge types as Pydantic models, along with a map of which edge types may connect which entity types. Zep’s default ontology has nine entity types and two edge types. Precedence between types is written into their descriptions, as instructions to the extraction model: Preference is to be chosen over most other types, and Object only as a last resort. So there is a type hierarchy, but the extraction model is what enforces it.

Hindsight: kinds of knowing

Hindsight separates world facts, the agent’s own experiences, observations drawn from them, and higher-level mental models. These are linked by entity, temporal and causal connections in Postgres, and recall runs semantic, keyword, graph and temporal searches in parallel. When reasoning, it consults mental models first, then observations, then raw facts.

Cognee: a shared vocabulary

Cognee builds a knowledge graph that can be grounded in an RDF/OWL ontology. Extracted entities are matched to canonical ontology names, which removes duplicates across documents, and class hierarchies are copied into the graph. The ontology is optional, and guides extraction rather than constraining what the graph can hold.

Where the structure lives

Each system emphasises different structure: typed links between memories, lifecycle and provenance, a declared ontology, or kinds of knowing. In most of them, the extraction pipeline and application code around the store apply that structure, and in Zep’s case the prompts do part of the work. Another option is to put the structure in the database schema itself, using a system such as TypeDB.

Modelling memory in TypeDB

TypeDB’s data model is a graph that excels at architecting, building, and managing structure.

Each of the structures above can be expressed in TypeDB quite directly, using its entity-relationship modeling and polymorphism.

  • Kind: type hierarchies. Memory categories become types that inherit from an abstract memory type. A query over the parent returns every subtype; a query over one subtype returns only that kind.
  • Connection: n-ary and nested relations. A relation can have any number of roles, so one derivation can link several premises to a conclusion without an intermediate node. Relations can also play roles in other relations, so a source record can attach to a supersession, not only to a memory.
  • Time: attributes on relations. Validity periods and recording times are attributes, on memories and on relations such as supersession. This model can handle Graphiti’s two timelines of when a fact was true and when it was recorded.
  • Provenance: relations to evidence. Whether a memory was observed or inferred is shown by what it is connected to, rather than by a label.
  • Derived knowledge: functions. Functions are reusable query logic stored in the schema, and they can be recursive. Views such as “current memories” or “all the evidence behind this conclusion” are defined on a schema level, allowing them to be shared across any and all clients.
  • Constraints: schema validation. A memory that does not fit the schema is rejected when written.

TypeDB also supports vectors natively. An embedding is stored as one more attribute on a memory, and similarity can be used in the same query as everything above. The combination of all these features is what makes TypeDB such a fantastic platform for architecting graph data, and implementing agent memory systems.

Vector Preview Content

This article uses TypeDB’s upcoming vector support, which is currently only available in a preview form, but with a full release coming soon.

To try it out now, join our Discord server and checkout the #vectors-alpha-testing channel.

A sketch of the design

Let’s look at how one might design a memory system using TypeDB, pulling on ideas from Supermemory and MemOS. Supermemory describes how memories relate to each other as they change. MemOS describes what each memory carries with it: a lifecycle type, a status, its sources and its connections.

IdeaFromIn TypeDB
Updates, extends, derivesSupermemorysupersession, extension and derivation relations
isLatest flagSupermemoryA function: a memory is current if nothing supersedes it
Lifecycle memory typesMemOSSubtypes of an abstract memory type
Status and confidenceMemOSAttributes with allowed values and ranges
Source recordsMemOSsource_record subtypes linked by a sourcing relation
Cause, condition, conflict, follows, parent edgesMemOSOne relation type each
Reorganiser summariesMemOSsummary_memory linked to its members by grouping

The schema below expresses this in TypeQL.

Memories

Every memory has content, an embedding, a recording time and a status, and may have a validity period and a confidence score. The subtypes are a subset of MemOS’s lifecycle types, plus a type for the summaries the reorganiser creates. Querying the abstract memory type returns all of them.

define

attribute content, value string;
attribute embedding, value vector(1024, "float32");
attribute recorded_at, value datetime;
attribute valid_from, value datetime;
attribute valid_to, value datetime;
attribute status, value string @values("activated", "resolving", "archived", "deleted");
attribute confidence, value double @range(0.0..100.0);

entity memory @abstract,
    owns content @card(1),
    owns embedding @card(1),
    owns recorded_at @card(1),
    owns status @card(1),
    owns valid_from,
    owns valid_to,
    owns confidence,
    plays ownership:owned,
    plays sourcing:sourced,
    plays supersession:superseded,
    plays supersession:successor,
    plays extension:base,
    plays extension:addition,
    plays derivation:premise,
    plays derivation:conclusion,
    plays causation:cause,
    plays causation:effect,
    plays conditioning:condition,
    plays conditioning:dependent,
    plays conflict:claim,
    plays succession:predecessor,
    plays succession:follower,
    plays grouping:member;

entity long_term_memory sub memory;
entity user_memory sub memory;
entity preference_memory sub memory;
entity skill_memory sub memory;
entity tool_trajectory_memory sub memory;

entity summary_memory sub memory,
    plays grouping:summary;

Owners, sources and relations

MemOS’s source records become entities, so one chat turn or document can be the source of many memories. Each kind of source is a subtype of the root source record type, so a chat source must have a speaker and a web source must have a URL, while a document source has neither. Supermemory’s three relationships and MemOS’s typed edges each become a relation with named roles. Adding cardinalities allows for further guarantees to be made, such as a supersession needing exactly one old and one new memory or a derivation needing at least one premise.

define

attribute name, value string;
attribute speaker, value string @values("user", "assistant", "system", "tool");
attribute message_id, value string;
attribute path, value string;
attribute url, value string;

entity person,
    owns name,
    plays ownership:owner;

entity source_record @abstract,
    owns recorded_at,
    plays sourcing:evidence;

entity chat_source sub source_record,
    owns speaker @card(1),
    owns message_id;
entity document_source sub source_record, owns path;
entity file_source sub source_record, owns path;
entity web_source sub source_record, owns url @card(1);
entity system_source sub source_record;

relation ownership,
    relates owner @card(1),
    relates owned @card(1);

relation sourcing,
    relates evidence @card(1..),
    relates sourced @card(1);

relation supersession,
    relates superseded @card(1),
    relates successor @card(1),
    owns recorded_at;

relation extension,
    relates base @card(1),
    relates addition @card(1);

relation derivation,
    relates premise @card(1..),
    relates conclusion @card(1);

relation causation,
    relates cause @card(1),
    relates effect @card(1);

relation conditioning,
    relates condition @card(1),
    relates dependent @card(1);

relation conflict,
    relates claim @card(2),
    owns recorded_at;

relation succession,
    relates predecessor @card(1),
    relates follower @card(1);

relation grouping,
    relates summary @card(1),
    relates member @card(1..);

Derived views

Supermemory stores isLatest as a field. Here it is a function instead: a memory is current if it is active and nothing supersedes it. Because the view is computed from the relations, it cannot get out of step with them.

define

fun current_memories($person: person) -> { memory }:
  match
      $memory isa memory, has status "activated";
      $_ isa ownership, links (owner: $person, owned: $memory);
      not { $_ isa supersession, links (superseded: $memory); };
  return { $memory };

Functions can also be recursive. This one follows derivations back through any number of steps, returning every memory a conclusion ultimately rests on:

define

fun evidence($memory: memory) -> { memory }:
  match
      { $_ isa derivation, links (premise: $premise, conclusion: $memory); }
      or
      {
          let $step in evidence($memory);
          $_ isa derivation, links (premise: $premise, conclusion: $step);
      };
  return { $premise };

Writing a memory

  1. A language model extracts candidate memories, each with a lifecycle type and the source records it came from. The schema rejects a memory without content, an embedding or a status, and a sourcing link without a source.
  2. Each candidate’s nearest neighbours by similarity are proposed as Supermemory-style links: supersession if it replaces an older memory, extension if it adds detail, derivation if it is inferred from them. The schema checks each link’s roles and cardinality.
  3. If a candidate conflicts with an existing memory, both are joined by a conflict relation and the new one is marked resolving, as in MemOS. Once resolved, the older memory is archived and a supersession is recorded, so history is kept rather than overwritten.

Recalling a memory

Recall is a single query. The current_memories function decides which memories are eligible, and similarity to the task ranks them. Several of the systems above run separate graph, keyword and vector searches and merge the results afterwards.

Reorganising

A MemOS-style reorganiser clusters related memories by similarity, writes a summary_memory for each cluster and links it to its members through grouping. Inferred memories are linked to their premises through derivation, so the evidence function can always trace them back.

Writing and recalling example

In March, a user tells the agent they prefer weekly status emails. This is stored as a preference memory, owned by that user, with the chat turn as its source. In June, they say a monthly summary is enough now. The new memory is close in meaning to the old one, so a supersession is proposed and validated. The March memory is archived, not deleted.

When the agent next plans a status update, current_memories returns only the monthly preference. If someone asks what the user used to prefer, the March memory, the June change and the chat turns behind both are still there.

Real TypeDB memory systems

There’s already a couple of real-world projects using TypeDB to store agent memory.

Reminisce is agent memory on TypeDB, an Elixir library with an MCP server. It remembers with time, provenance and scope, recalls deterministically and can explain every answer, and never deletes a memory: a fact that stops being true is superseded, not erased.

Skillful-Alhazen is a research agent whose memory is a TypeDB schema. It leverages TypeDB’s strengths in schema evolution, architecting new memory structures on the fly. Anything the agent cannot represent in that schema becomes a proposal to extend it, which a human reviews.

Cognee has recently added a TypeDB adapter, allowing you to use TypeDB as a graph database backend to store Cognee’s memories, fully compatible with Cognee’s existing ecosystem.

Trade-offs and open questions

As with any design, choosing structure has costs, and our sketched design above does leave some questions open.

  • Write-time cost. Extraction, validation and link proposal all happen on write, so each write needs at least one model call. Whether that is worth it depends on how much the application needs facts to stay accurate over time, and whether it needs an audit trail of what the agent knows.
    • A less structured system may require more reads, as it needs to search more information to find things that are baked into the structure of a more structured system.
  • Schema evolution. A fixed set of types will not cover every domain an agent meets. How new types are proposed, reviewed and added, is an open design problem. Reminisce lets a host add its own subtypes and attributes in a separate schema definition, and Skillful-Alhazen files proposals for a human to review.
  • Evaluation. Popular benchmarks like LoCoMo mostly test retrieval, which favours simple designs. LongMemEval’s knowledge-update and temporal-reasoning categories, and newer agentic benchmarks, are likely the most useful tests for a design like this.

Summary

If you want structure in your agent’s memory, there are several good models to borrow from: Supermemory for typed links between memories, MemOS for lifecycle and provenance, Graphiti and Zep for declared ontologies and time, and Hindsight for kinds of knowing. A database with an expressive schema, such as TypeDB, lets you combine these ideas in one place and use them alongside similarity search in a single query.

Join our community of builders developing and experimenting with innovative agent-memory systems. Let’s work together to push the boundary of what AI and Agentic systems can do.

Continue on our docs and join our Discord server.

Share this article

TypeDB Newsletter

Stay up to date with the latest TypeDB announcements and events.

Subscribe to newsletter

Further Learning

Feedback