Skip to main content
Every piece of information that OMP stores is called a memory — a single, self-contained record representing one fact, preference, event, or document chunk about a user. Understanding the memory schema is the foundation for working effectively with any OMP provider.

What is a memory?

A memory is the atomic unit of remembered information in OMP. It is always scoped to a specific user (user_id), optionally grouped into a hierarchical namespace (scope), and identified by a provider-assigned id. Because OMP is provider-agnostic, the same schema works whether the backing store is Postgres, Mem0, Supermemory, or any other OMP-compatible backend.

Memory schema

The full JSON representation of a memory looks like this:

Field reference

Required fields: id, content, user_id, and created_at are the only fields a provider must return. All other fields are optional but normalized when present.

Extension fields

OMP supports provider-specific data via x-<provider>-prefixed extension fields. Providers use these to expose proprietary metadata — such as graph node IDs or internal embedding versions — without breaking OMP compliance.
The rules for extension fields are:
  • Extension fields must use the x-<provider> prefix (e.g. x-mem0, x-supermemory).
  • They must not override or conflict with standard field semantics.
  • Any application that does not recognize an extension field must ignore it — forward-compatibility is required.
  • Extensions must not be required for an OMP-compliant client to read or use the memory.
Extension fields let providers add proprietary features without forking the protocol. Your application code stays portable — unknown x- keys are simply ignored.

Memory lifecycle

A memory follows a straightforward lifecycle from creation to eventual removal:
1

Add

Call mem.add() with at minimum content and user_id. The provider assigns an id and returns the stored record. Async providers may return status: "queued" while indexing is pending.
2

Search or list

Retrieve memories with mem.search() for semantic queries or mem.list() for filtered enumeration. Both support scope filtering to narrow results to a specific namespace.
3

Update or supersede

Call mem.update() to modify a memory in place. Pass supersedes=[old_id] on a new mem.add() call to create a replacement record while preserving the history chain.
4

Delete

Call mem.delete(id) to permanently remove a memory. This is a hard delete — use supersedes instead when you want to retain a history trail.

Code example

Here is a complete add() call using multiple optional fields:
You can also pass source as a plain dictionary — the SDK converts it automatically: