How a memory works
Introduction
Section titled “Introduction”If you know the LLM Wiki idea, you already know the shape: a raw/ folder of documents nobody edits, a wiki/ folder of Markdown pages linked with [[links]], and a schema file that tells the agent how to write them. A graph memory follows the same shape, but stores each part in PostgreSQL so that every piece knows where it came from and who may see it.
This page explains that model. It covers graph memories; a collection stops at passages, and a personal memory holds short facts.
From LLM Wiki to Brain
Section titled “From LLM Wiki to Brain”| LLM Wiki | Brain | What it is |
|---|---|---|
A file in raw/ |
Source | A document, text or file you add. Never edited. |
| (nothing) | Passage | A piece of a source’s text, kept with where it came from. Search reads these. |
A .md page in wiki/ |
Page | An entity: a company, a person or a topic, with aliases. |
| A paragraph of a page | Section | One fact on a page, with its citations. The API and the web app call it a fact. |
A [[link]] |
Link | A reference from one page to another, tied to the sources that say it. |
| The schema file | Not available yet | Every graph memory uses the same built-in instructions and the same page types. |
A page is not one block of Markdown because the unit that matters is the fact. Each section knows:
- which sources it came from, with a locator for each;
- when it became true, and when it stopped being true;
- which section it replaced;
- whether a person wrote it, and whether it is locked.
So deleting a source retires only the sections that came solely from it, and a page keeps every fact that another source still supports.
Sources and versions
Section titled “Sources and versions”A source is the text you add, with a title, a media type and its labels. Brain stores a hash of the content, and the caller may also give an externalId, its own stable id for the document.
- The same
externalIdwith the same content changes nothing. - The same
externalIdwith new content creates the next version. The old version is superseded: lists and search show only the current one, and only what differs in the compiled pages changes. - Without an
externalId, a source is the same one when its title and content hash are both unchanged.
Deleting and erasing sources is described in the overview.
Passages
Section titled “Passages”A passage is a piece of a source’s text. The model never rewrites it: what you search and what a citation quotes is the text you added.
Reading the file
Section titled “Reading the file”| Type | How it is read |
|---|---|
| Plain text, Markdown, CSV | As it is. |
| HTML | Scripts and styles dropped, block tags become line breaks, entities decoded. |
| Text per page, so the page number survives. | |
| Anything else | Refused, with a message naming the types that work. |
A file with no text fails and the source is kept as failed. That includes a PDF made only of images: Brain does no OCR. By default the extractor also refuses a file with more than 2,000,000 characters of text.
Splitting it
Section titled “Splitting it”Text is cut into pieces of at most 800 characters, with no overlap. A cut falls at the last paragraph break in the window, then at the last sentence end, then at a space, and only where the piece would be at least 200 characters long; otherwise it cuts at 800.
Each passage carries a locator, and that is what a citation shows:
| Source | Cut by | Locator |
|---|---|---|
| Page, then size | p. 3 |
|
| Text with Markdown headings | Heading, then size | §2 |
| Anything else | Size | chars 0-780 |
A page has a type, a title and aliases. Today the types are company, person and topic.
- Aliases. A page keeps every name a source used for it. A name is matched without case, accents or a legal suffix such as
S.r.l., and close spellings are found by trigram similarity. No vectors are involved. - Links. A link from one page to another records which sources say it.
- Conflicts. When two sources disagree, the page records the claim and both versions instead of choosing.
- Revisions. A page change keeps what the page was before it.
- Merges. Merging two pages leaves the losing page pointing at the survivor (
merged_into). - Review. What Brain will not decide alone becomes an item for an admin: labels to confirm, conflicts, empty pages, and possible duplicates, each naming the two pages involved. A pair an admin has judged to be different pages is remembered so it is not proposed again.
Curating a memory shows how an admin works through these.
Compiling a source
Section titled “Compiling a source”With a chat model configured, a new source in a graph memory is compiled into pages in two model calls:
- Extract. The model lists the companies, people and topics the source is about, at most 12 names.
- Compile. For each name Brain finds up to 5 candidate pages and shows the model only what the source’s audience can already read. The model answers with operations on sections, not whole pages: add a new section, update a candidate section into one that cites every source, or noop when a candidate already says it, which only gains the citation.
The result is applied in one transaction that reads the pages again first. If a person deleted a page while the model was running, what was said about it is dropped, not recreated. If the page was merged, its facts go to the surviving page.
The compile queue
Section titled “The compile queue”Compiles are queued in the database, not run inside the request, so they survive a restart. Per memory they run one at a time, in order; different memories run in parallel.
While a source waits or runs, its compile status is pending. If it fails three attempts, the status becomes failed and the source keeps its passages and search. See the overview for the steps as an admin sees them.
Vectors are optional
Section titled “Vectors are optional”Pages, aliases and links never use vectors. Finding a candidate page is names, trigram similarity and full text.
Only passages can have an embedding:
- Passage search always runs full text on PostgreSQL (
tsvector). - When an embedder is configured and answers, semantic search is added on top of it. If the embedder fails, the query still runs as full text.
- A memory server uses one embedding model for all memories, and the size of the vector column is fixed when the schema is created if you configure it.
Embed it in a host has the configuration.
Faces: who sees what
Section titled “Faces: who sees what”Access is decided by labels on sources, and everything compiled from a source inherits them.
- Every source has labels. A label belongs to a family, such as
Department. - A section takes the labels of the source it came from. A page, a link or a graph edge is visible only when it derives from a source the viewer can see.
- A viewer sees a source when they hold any one of its labels.
- A role grants each family one of
none,some(a list of labels) orall. A member holds the labels their roles grant.
A section a person wrote by hand has no source, so it carries its own labels.
Consider an invoice labelled Finance and an identical copy labelled Sales. They say the same thing, but the audiences differ, so Brain never merges them into one section: that would show the Finance copy to someone who only sees Sales. Each audience gets its own section on the same page. Only sources with the same set of labels are consolidated.
Personal memories
Section titled “Personal memories”A personal memory has no pages and no labels. It holds short facts: one standalone sentence each, written by an agent or by the person, readable only by that person. See the overview.
The tables
Section titled “The tables”Every table lives in one PostgreSQL schema. Only the columns that explain the model are shown.
access memories(id, kind, access, owner_id) ├─ members(memory_id, user_id, role, role_ids) ├─ families(id, memory_id, name) │ └─ labels(id, family_id, name) └─ roles(id, memory_id, name) └─ grants(role_id, family_id, mode, label_ids)
raw sources(id, memory_id, label_ids, content_hash, external_id, previous_version_id, superseded_by, compile_status) └─ passages(id, source_id, ordinal, text, locator, embedding, tsv)
wiki pages(id, memory_id, type, title, locked, merged_into) ├─ aliases(page_id, alias, norm, origin_source_id) ├─ sections(id, page_id, text, label_ids, origin, locked, │ valid_at, invalid_at, superseded_by) │ └─ citations(section_id, source_id, locator) ├─ links(page_id, to_page_id) │ └─ link_sources(link_id, source_id) ├─ conflicts(id, page_id, claim, a, b) │ └─ conflict_sources(conflict_id, source_id) ├─ revisions(page_id, at, reason, before) └─ pairs(page_a, page_b) pages judged to be different reviews(id, memory_id, type, subject, source_id, page_id, other_page_id)
personal facts(id, memory_id, text, pending, deleted_at, replaced_by)
work compile_jobs(id, memory_id, kind, source_id, attempts) connectors, connector_runs, rules labelling and ingestion audit(memory_id, action, summary, undo) tokens, token_grants access for agentssections has no foreign key to a source on purpose: deleting a source retires its sections instead of removing them, and only erasing a source deletes them.
Next steps
Section titled “Next steps”- Brain overview for the memory kinds and what runs without a model.
- Curating a memory to review, merge, split and lock pages.
- Embed it in a host to configure models and embeddings.
- Connectors to bring sources in on a schedule.
- HTTP API for every route.
- Use it from an agent and Run the standalone service.