Mental model · 8 min

Concepts

Eight primitives, in the order you'll encounter them. Read this once and you'll know how every endpoint composes.

Project

A project is the isolation boundary. Every API key authenticates exactly one project; every memory, entity, thread, and graph lives inside one. Cross-project reads are impossible — there's no API for it. You'll typically run one project per app (or per app + per environment, e.g. billing-bot-prod and billing-bot-staging).

Projects are created from the dashboard. The bootstrap step also creates one default group graph so the first POST /memory/add call works without any extra setup.

org (acme)
└── project (billing-bot)
    ├── default group graph    ← created on bootstrap
    ├── user graphs (per end-user)
    └── named group graphs (your call)

Graph

A graph is a named container of memories, entities, and edges. There are two kinds, indicated by the kind enum:

  • USER — one per end-user, auto-created on POST /v1/users or the first ingest that targets a user.
  • GROUP — shared knowledge that's not tied to one user. Company policies, product docs, the support team's playbook.

Search composes additively across graphs — one call can scope to one user's graph plus the policies group graph. See /v1/memory/search.

User

Users are first-class. You bring your own externalId (Clerk id, Auth0 sub, your DB primary key — anything unique inside the project). Creating a user atomically materializes their user graph; deleting a user cascades the graph plus every thread, message, memory, and entity reference.

POST /v1/users

curl -X POST "$MEMOS_URL/v1/users" \
  -H "Authorization: Bearer $MEMOS_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "externalId": "user_clerk_abc123", "email": "[email protected]" }'

Thread

A thread is a conversation session for one user. Append messages with POST /v1/threads/:id/messages; user-role messages auto-ingest into the user's graph through the same extraction pipeline as raw text. Assistant, system, and tool messages are stored for replay but not treated as facts about the user.

Memory

A memory is a single typed fact, addressable by id, with full provenance back to its source chunk and ingestion job. Every memory carries a type drawn from the built-in ontology:

  • FACT — stable claim about the world ("Alice works at Acme")
  • EVENT — something that happened at a point in time
  • PREFERENCE — what the subject likes or wants
  • DECISION — a choice made by the subject
  • STATE — current observable status
  • RELATIONSHIP — connection between two entities
  • BELIEF — opinion or expressed view
  • SKILL — ability or competence
  • GOAL — explicit intent or objective

Memories carry a confidence score, a validity window (validFrom / validUntil), an isActive flag, and a supersededBy pointer for conflict surfacing.

Episode

An episode is a cross-session summary — "the last 14 days of Alice's billing conversations" — generated periodically by the summarization worker. Episodes let the agent recall the gist of past activity without re-ingesting every message. They surface in GET /v1/users/:id/context automatically when the user has enough thread volume.

Entity + relation

Extraction also auto-builds a knowledge graph alongside the memory list. Each entity has a canonical name, a type (PERSON, ORGANIZATION, LOCATION, …), and a list of aliases. Relations are predicate-typed edges between entities — WORKS_AT,MEMBER_OF, LOCATED_IN, plus anything your ontology adds. The dashboard's Graph view renders this directly; the API exposes it atGET /v1/memory/entity-graph.

text:      "Alice works at Acme on the billing team"
memories:  Alice WORKS_AT Acme           (FACT)
           Alice MEMBER_OF billing-team  (FACT)
entities:  Alice (PERSON)
           Acme (ORGANIZATION)
           billing-team (TEAM)
edges:     Alice --WORKS_AT--> Acme
           Alice --MEMBER_OF--> billing-team

Supersession + conflicts

When a new memory contradicts an existing one, MemHQ doesn't delete the old row — it marks it superseded (isActive = false, supersededBy = newId) and writes a CONTRADICTS edge linking the two. The dashboard surfaces every CONTRADICTS edge on the Conflicts page so an operator can resolve disagreements. Audit history is a property of the storage layer, not a separate log.

Ontology + backfill

Beyond the built-in memory types, projects on Scale and Enterprise can define custom entity types and predicates — SubscriptionPlan, ESCALATED_TO, anything domain-specific. Changes don't strand old data: POST /v1/ontologies/:id/backfill rewrites every existing memory in the project against the new ontology version. See /docs/api/ontology.

Audit chain

Every write (memory create, supersession, ACL grant, ontology bump) appends a row to the audit log with a SHA-256 hash chained to the previous row. The chain is verifiable end-to-end via POST /v1/audit/verify — if anyone tampers with history, the verify call surfaces it.

One model to keep in your head

Project → Graph (user or group) → Memory (typed, with provenance). Everything else — threads, ontologies, ACLs, episodes — is metadata or organization on top.