Skip to main content
Knowledge is stored as typed, content-addressed blocks organized into five memory modules. Each module bundles its blocks, a Merkle DAG that pins the exact composition of a version, and the indices needed to query it.

Blocks

A block is a small immutable record with a payload wrapped in an envelope. The envelope is what block_id is computed over, and it has exactly five keys:
Blocks are never mutated. Every operation that looks like a change produces a new block, a new composition, or a new snapshot. See Memory types for the five schemas.

Content a block names but does not carry

A payload is JSON, canonically serialized and hashed on every access, so a datum large enough to matter does not belong inside one. A block may instead name its content by digest and leave the bytes in the store — which is what a canonical block has always done with the original it describes.
put_content stores the bytes and returns a ContentRef for a payload to name. It writes no block and publishes no snapshot: until a committed block names them, the bytes are unreachable and a prune reclaims them. The reference it returns is a fact, not a claim: size is measured from the bytes, and media_type must be a bare, lowercase type/subtype — no parameters, no trailing punctuation. Media types are parsed with the standard library’s RFC 2045 parser and then required to round-trip exactly, because image/png, IMAGE/PNG and image/png; charset=utf-8 are the same type to compare and three different strings to hash. Both fields are hashed into the resulting block_id and are what a consumer reads to decide whether to fetch content it does not hold, so a wrong value cannot be corrected later — only superseded by a different block. A payload assembled by a proposer rather than by put_content is checked in the validation gate instead, which also compares the declared size against the stored bytes when the brain holds them. Both checks sit on the write path and never on decode. NormalizedView extends ContentRef and takes its media type from a third-party normalization pipeline, so a malformed one may already be inside a published block_id; refusing it at decode would make this SDK unable to read a brain an older one wrote. Canonical has named its original since the beginning. Semantic, episodic and procedural blocks gained an optional content field in schema version 2, so an interpretation whose subject is an image, a recording, or any other file can state what it claims about that file without inlining it. The text stays required — statement, summary and goal are what a natural-language query can reach, and when the content is binary they are the interpretation.
A block is written under the oldest schema its payload satisfies. A payload naming no content is still built as v1, so adding a version does not re-version knowledge that does not use it. Since schema_version is inside the hashed envelope, that choice is a choice of block_id: a brain only becomes unreadable to an older client at the point where it genuinely uses something that client has no schema for.
Content is not evidence. Evidence is canonical — it lives in the canonical composition, other blocks cite it, and dropping it cascades to everything derived from it. Content is the block’s own datum, so nothing cites it and nothing needs to; it lives and dies with its block. A source other blocks will cite is a canonical block, through register.
Everything that must account for those bytes — packing a layer, marking reachability before a prune, destroying them on redaction — asks the block through content_digests rather than testing its type. So a schema that starts naming content is handled correctly by all of them at once, and an index is handed a reader for exactly this reason.

Publishing across SDK versions

An artifact declares which schema versions each of its modules holds, in the ai.gaussia.boltzmann.schema-versions manifest annotation. A pull checks it against the schemas the client implements, scoped to the modules being installed, and refuses before fetching any blob:
The declaration is per module, so a client that lacks a schema for the semantic module can still install the episodic one. An artifact published before the annotation existed declares nothing, and absence is read as unknown rather than as permission: those fall through to the decode-time check instead.

Compositions

A Composition is an immutable set of block identities committed by a single Merkle root. Deriving one returns a new one:
Because the root is computed over sorted leaves, it is a pure function of the set of blocks: two parties that assembled the same blocks in different orders obtain the same root.
A root can be verified but not inverted, so the leaf list is stored alongside it as a composition document (composition.json). Without it a snapshot would identify a version it could not reopen.

Modules

A Module is one memory module at one version: a composition, the store its bytes live in, and its indices.
with_blocks and without_blocks derive new versions. Module exposes no write method at all — extending a brain goes through commit, which is the only write path.

Snapshots

A Snapshot is the state of a brain: one ModuleRef per installed module, plus the snapshots it succeeds, which form an auditable history.
parents is a list because history is a DAG rather than a chain: a linear history carries one entry, a root snapshot none, and a snapshot that joined two histories two or more. Order matters in exactly one way — the first parent is the one every rule meaning the parent refers to. See Reconciliation. Each ModuleRef carries the module’s root, the composition digest, the block_count, the Merkle layout identifier, the identities whose bytes were deliberately destroyed in tombstones, and the embedding_model behind a travelling vector index when one ships with it. Tombstones remain composition members: they preserve membership while making the loss explicit and signed. Snapshots written before the field existed remain readable; newly written snapshots always emit the list, including when it is empty.
A brain may hold a subset of modules. Selective installation is the point of packaging each module separately, so “not installed” is a legitimate state — and therefore an error rather than an empty module when you ask for one you do not have.
The mutable state a brain has is which snapshot is current — and, while one is being resolved, a reconciliation in progress:
ancestry and reachable_history answer different questions. The first is the line an audit follows. The second is what a containment check asks — a history merged in is genuinely contained without appearing on the first-parent chain, which is why a push compares against that one.

The store

Blocks live in a BlockStore. Two ship with the SDK: A store must not normalize: bytes that are not canonical do not decode, and bytes that do not hash to the digest they are filed under are refused. A redacted block is tombstoned, never silently missing — a removed block must never look like a corrupted one.