Memory Projections
The design, in ten lines: what a memory projection IS, so the next one is added the same way.
In this section
The design, in ten lines: what a memory projection IS, so the next one is added the same way.
A memory projection is a derived row, not a cache. It is durable, it is a pure function of the canonical memory rows, and it is rebuildable from them alone. Nothing else may be true of it — a projection that needs a log, a queue, or its own history is a second source of truth wearing a projection's name. (Distinct from a STORAGE projection:
src/open/projectionArtifacts.tsis how the engine persists rows; this is a row the engine derives FROM rows.)One fold, two callers. The incremental job cycle and the full rebuild run the SAME fold function over rows in
(createdAt, id)order. That is what makes "rebuild = identical projection" structural rather than hoped for, andprojections.e2e.test.tspins it on the hash as well as the fields.Every term of the fold is exact under an ordered fold —
min,max, sums, a last. A term that is not (a live count, a distinct set that later rows can shrink) does not go in the row; the door computes it from a posting-list cardinality at read time, which materialises no ids.The counts are historical, not live. A member counts whether or not it was later retracted, because
memoryCountis how many memories were written into this conversation — monotone, and therefore exact incrementally. Under the engine's retention law a retracted row is still a row that happened — see Retire.The cycle's cost is proportional to what was WRITTEN, never to what the store holds. Forward-only over a watermark; the watermark is the mark and nothing is stamped on a canonical row. The lookup of the rows to update is narrowed by conversation id in the index (
in), never by paging the projection subtype — that shape is the maintenance term thecostlane exists to refuse (brainy 4.2.1).A projection row is not a memory. It carries no
stability, which is the fence (episodeJob.ts) that keeps a fold from reading its own output as input and deriving sessions of sessions forever. Pinned. It carries noauthoreither, for the same reasonauthoris not one of rule 3's exact terms: a session can span many episodes written by many different actors, and there is nomin/max/sum/lastof "who wrote it" that is honest about the rest. A session's own members still carry their own authors; askmemory.episodes()for them.Rebuild is a DOOR, never a cadence.
brain.memory.rebuildProjections(). A sweep that re-derived the whole store on a timer would be exactly the cost shape rule 5 forbids.The doors report their own bound. Every answer carries
rowsVisited, the real count of index rows read. A door that claims to be bounded and is not is exactly the defect projections exist to cure —context()'s thread slice, before a session projection existed to answer the question directly, walked ~2,827 rows to answer one question on a 2,669-paragraph brain.A hash, and what it honestly saves. Each row carries its content hash and each digest carries the hash of the lines it named. A caller passing
knownHashback is toldunchangedand handed no bodies. That saves the CALLER's tokens, never the engine's reads, and the module header says so rather than implying otherwise.The engine never writes prose — but it LANDS it atomically. A session row is structure: a span, two counts, a last member. Words about a conversation come from a service or from the language seam (
brain.language.summarize), into a field the engine declares and does not fill. What the engine does own is the landing:memory.abstract()(src/memory/abstract.ts) writes the rollup row, theprocessing.abstractedIntoMemoryIdon every absorbed row and theabstractedIntoedges in one generation-pinned transaction, because three facts that must agree cannot be written by three calls that can half-land.
What exists today
projection | rows | job | doors |
|---|---|---|---|
session |
|
|
|
MEMORY_PROJECTION_NAMES in src/memory/projections.ts is the closed set, and today it names exactly one: session. A name added there without a fold and a door fails to compile.
Episodes are not a projection in this module's sense, and the distinction is deliberate. An episode (Event/memory-episode, one per closed burst, maintained by the memory-episodes job and read through memory.episodes()) predates this module and is built by a different mechanism — a forward-only segmentation walk with no closed-form fold, because an episode can still be open while a session never has that state. See Housekeeping for how episodes are built. What this page's ten rules govern is the session tier above them — and the next projection this module grows, whatever it is, is held to the same ten.