Skip to content

The data model

How the record holds together

A memory system is only as good as what it can say about the things it stores — that this claim replaced that one, that these four are about the same person, that this date was chosen over that one and here is why. Those are relationships, and a list cannot hold them.

This page is the shape of the store: the tables, the links between them, and what each link carries. It is the other half of the architecture page, which follows material through the pipeline. That one is about how things move. This one is about how they relate.

The rows below are invented. They illustrate the model with one plausible fortnight of a person’s record. Nothing is fetched, nothing is anybody’s memory, and no account is read to build this page. The shape is real: every table, column and link drawn here exists in the schema, and a test fails the build if one of them is renamed away.

Readings

Meaning at the top, the claim in the middle, where it came from underneath. Reading downwards asks why the system believes something; reading upwards asks what it means and who can see it.

The drawing needs more width than this screen has, and shrunk to fit it would be a picture of a diagram rather than one. Every row it draws is in the list below, and every link it draws is in the record beneath that.

All 39 rows

Owner

Filing

Meaning

Beside the claim

The claim

Grounds

Material

Origin

8 bands, 39 rows. Choosing one shows what it connects to and why.

Reading it

Shape says what a row is

  • a claim, or something recorded beside one

    memories · memory_versions · conflicts · tasks

  • a thing, and the links between things

    entities · entity_aliases · relationships

  • a rule about who and what

    users · spaces · space_collaborators

  • where it came from

    evidence · documents · sources · conversations · conversation_messages

Colour says only what state it is in

Nothing on this page is coloured to look alive. A hue appears when a row carries a state-bearing column and reports its value, and nowhere else — so when two claims disagree, the disagreement is the only coloured thing in view.

  • currentactive, confirmed, accepted, or the side a conflict was decided for
  • helduncertain, open, invited but not accepted — known and not yet settled
  • supersededreplaced, expired or ended. Still true of the past, so struck rather than removed
  • contradictsthe one relation that disagrees: a passage cited against a claim

Every kind of link, and where it lives

A link is either a row in a table of its own, which can carry columns — a confidence, a role, a reason in words — or a foreign key on one of its two ends, which cannot. Almost every interesting thing on this page comes from that difference.

memories.user_id

foreign key

owns

Every owned table carries a user_id that cascades. Ownership is a foreign key rather than a column somebody has to remember to filter on.

space_memberships

link table

is filed in

A row, not a column, so one memory sits in several Spaces at once and carries a kind, a confidence and a reason in words for each. Copying the memory per Space would turn one fact into three that drift apart the first time any is corrected.

spaces.parent_space_id

foreign key

nests under

Reach, not permission. Asking for the parent can mean everything under it; a memory in a child Space is not thereby visible to everyone who can see the parent.

space_collaborators

link table

may read

Visibility, never ownership. Nothing is copied into the collaborator's account, so revoking is one timestamp and their conflict set never mixes with the owner's.

conversations.space_ids

foreign key

files into

An array on the conversation rather than a link table: the scope of a conversation is known when it starts, and every memory extracted from it inherits these.

conversation_messages.conversation_id

foreign key

is a turn of

Ordered by sequence, not by timestamp, because turns can arrive faster than a millisecond.

memory_entities

link table

is about

A junction, because “what else concerns this person” is asked as often as “who is this about”, and an array can only be walked one way. Carries the role and the words the source actually used.

entity_aliases

link table

is also called

The resolution lookup: who is known by this name. Confidence belongs to the alias, not to the entity.

relationships

link table

relates to

An edge between two entities with its own validity window, because a link outlives both ends: a thing stops being true without either entity changing.

relationship_memories

link table

was asserted by

A link with no memory has no grounds.

relationship_evidence

link table

is grounded by

One sentence, two claims: the observation that grounds a memory usually grounds the relationship extracted from the same words.

memory_evidence

link table

is grounded by

The relation lives on the pairing rather than on the observation, because the same passage can support one claim and contradict another.

evidence.document_id

foreign key

is a passage of

Required. A passage with no document is a quote from nowhere, which cannot be checked and therefore cannot ground anything.

evidence.source_id

foreign key

was seen through

Nullable, unlike the document: a connector can be removed while the material it produced stays checkable.

memory_sources

link table

came from

Association, which is not the same as citation. A memory merged from three sources may have evidence from only two — and the third is often the one that made it well-founded, since corroboration counts distinct origins.

memory_versions

link table

is a version of

Append-only. What it said before, what changed, and why.

memories.superseded_by

foreign key

was replaced by

The old row stays, keeps its evidence and its filing, and points at what replaced it. Overwriting would destroy the only record that the belief ever changed.

conflict_memories

link table

is a side of

A junction rather than two columns, because three sources can disagree three ways about one subject and a two-column shape would record that as three conflicts nobody can resolve together.

conflicts.winner_memory_id

foreign key

was decided for

Set null on delete: the record that a conflict happened outlives the row that won it.

tasks.memory_id

foreign key

came from

Four typed columns rather than one polymorphic pair, so the database can still check the reference. All four set null rather than cascade: a to-do outlives the transcript it was agreed in.

What this deliberately does not draw

A diagram is read as a claim about the system, so an absence is worth as much as a line. These are the four things it would have been easy to add and wrong to.

memory_pins
The table is real — (user_id, memory_id, pinned_at) — and it is left out because of who it belongs to rather than because it is small. A pin is one person’s opinion ABOUT a claim, not a property of the claim: the same memory is pinned for its owner and not for someone reading it through a shared Space. Everything drawn here is true for every reader of a row, and a pin is the one edge that would not be.
A membership row for Universal
Universal is the base layer rather than a Space. Every memory is in it, so a row saying so would be one row per memory carrying no information — and querying Universal is simply querying the user’s own memories.
candidates, vectors, jobs, events and the rest of the machinery
They are how material gets here and how it is retrieved, which is the pipeline the architecture page explains. This page is only about how the things that survive that pipeline relate to each other.
What a collaborator may DO
`space_collaborators.role` is real and this page draws the grant without it. A viewer reads a Space, an editor also files into it, an owner shares it onward — three different powers over the same edge, which is a second dimension this drawing has no way to carry: it distinguishes kinds of link by shape, and these are all one kind. The dashboard is where a role is read and changed.

The pipeline that produces all of this is on the architecture page, and the API that reads it is in the documentation.