Brainby arc-labs/docs
Concept

How memory works

The mental model for Brain — you write text, Brain embeds and stores it, derives a typed graph in the background, and answers reads with a membership verdict.

A traditional vector database stores opaque embeddings and asks the caller to figure out what they mean. A key-value store stores exact strings and asks the caller to know the right key. Brain sits between those two ideas: you hand it text, it embeds and stores the text as a memory, and it asynchronously extracts a typed graph — entities, and Fact / Preference / Event statements and relations between them. A read then fuses three retrievers and returns not a raw ranked list but an answer the caller can act on directly.

That positioning forces a particular round trip. Text goes in through encode; the write path makes it durable and searchable, then derives edges and the typed graph after the acknowledgement. A later recall fuses semantic, lexical, and graph retrieval and shapes the result into a membership verdict. Brain never mutates a memory in place — corrections are written as new memories and statements that supersede old ones, so the history stays intact.

The four pillars

01

Typed memory + entity graph

You write text. Brain stores the memory and, in the background, extracts entities and Fact / Preference / Event statements. The graph layer indexes who is mentioned where, so retrieval can walk relations instead of reducing everything to cosine distance.

02

Identity by key

An API key is bound to a (namespace, agent, permissions) scope at creation. Clients never construct or send a scope — the server derives it from the key, and every memory, entity, and index is isolated per (namespace, agent).

03

Durable before ack

encode returns only after the write's WAL record is fsynced — the acknowledgement barrier. Everything after (edges, the typed graph, the text index, HyPE questions) is derived asynchronously by per-shard workers.

04

Hybrid retrieval, one answer

Three retrievers run in parallel — semantic, lexical, graph. RRF fuses their ranks, an always-on cross-encoder reranks, and the pipeline returns a membership verdict — Single, Many, or None — over the whole candidate pool.

The round trip

A single round through Brain looks like this:

WRITE (ENCODE)04 STAGESREAD (RECALL)06 STAGESembedding + indextextencodevalidate → embed → reserve → persistasync: edges + typed graphcuerecall3 retrieversRRF fusererankSingle / Many / None
Write writes — read reads. The embedding cross-links the two: `encode` produces it synchronously as the memory's semantic key, and the retrievers consume it on the read path.

You hand encode(text) a string. The synchronous fast path validates it, embeds it with BGE-small into a 384-dim vector, reserves an arena slot and a version-stamped MemoryId, and persists — writing the vector, fsyncing the WAL (the ack barrier), committing the redb row, and inserting the HNSW point. The response returns here. After the ack, per-shard workers derive SimilarTo edges from HNSW neighbours, FollowedBy edges from session adjacency, and — through the three-tier extractor (pattern → classifier → LLM) — the typed graph, alongside the tantivy text index and write-time HyPE questions.

Later, you call recall(cue). The read pipeline embeds the cue, fans out to the three always-wired retrievers, fuses their ranks with RRF (k=60), reranks with the cross-encoder when it's loaded, applies the filter chain (tombstone, kind, context, temporal, confidence, salience, supersession), and shapes the surviving candidates into a membership verdict. There is no "search" verb and no client-side re-ranking — the answer comes back already shaped.

Why typed memory matters

A flat vector store collapses a fact, a preference, and an event into a single similarity-keyed row. When an agent asks "what does this user prefer?" it gets geography, calendar entries, and project notes mixed together. You can't unscramble that at query time without re-reading the content.

Brain's background extractor lifts structure out of the text: entities (the nouns), and statements typed as Fact, Preference, or Event. The read router uses that structure — an entity-anchored query ("tell me about Priya") leans on the graph retriever; an exact-term query ("ticket ACME-1247") leans on lexical; a paraphrase-likely query leans on semantic. The graph is what makes a "where does Priya appear as an object" recall surface possible at all.

Why derivation is asynchronous

Extraction runs an LLM. If it ran inside the acknowledgement path, every encode would pay for a model round trip. Brain refuses that trade: the write is durable the instant the WAL is fsynced, and the graph fills in seconds later. You can observe the derivation live over SUBSCRIBE (keyed by the write's LSN), block for it by passing wait: "derived" on the encode, or inspect any memory's write story later with MEMORY_INSPECT.

Extraction is nonetheless always-on, not a toggle. A write step that populates the graph can't be optional without silently breaking graph-backed reads — so the LLM key is a hard boot requirement and the extractor worker is always provisioned. See Architecture.

Why supersession instead of mutation

When a user says "actually I moved to Berlin" after Brain already knows they live in Lisbon, you have two choices: overwrite, or write a new record and mark the old one superseded. Brain always picks the second. The old memory and statement remain queryable; the new one's superseded_by chain points back, so history, audit, and replay see the full chain. Facts that contradict rather than replace are both kept and the conflict is surfaced, not hidden. See Supersession and provenance.

Where to go from here

Brain is small enough to read in one sitting and big enough that the right starting page depends on what you're building.

Was this page helpful?

On this page