Object Store (OODB)
EAV-shaped knowledge graph. Schema is data. 4.4× smaller than the 2-table relational form.
The Object Store is the substrate underneath the Memory component. Fundis create, relate, and retrieve typed objects in real time over it. It is not a relational database, not a document store, and not a system of record. It is working memory shaped as a knowledge graph.
Logically, the model is Entity-Attribute-Value (EAV). Every fact is a (subject, predicate, object) triple — which is also a knowledge-graph edge and a retrievable RAG chunk. There is no document chunking step because there are no documents; there are nodes and attributes.
WHY EAVThe logical model
Three properties make EAV the only viable logical model for the knowledge graph.
1. Runtime schema evolution
When a Fundi invents a new object type, it is a data write — not a code deployment. Fixed-schema tables cannot accommodate types that did not exist at deploy time. EAV can, because the "schema" is just two more rows.
2. Sparse storage
The FDA medical-device taxonomy IOBOXX loaded for the CSP procurement workflow has 7,066 device types inheriting nine fields from a root MedicalDevice type. Most leaves populate three or four. A wide table allocates all nine columns per row; EAV stores only the attribute rows that have values.
3. Memory efficiency
The smartphone budget — roughly 33–62 MB total, of which about 6.2 MB is the knowledge graph at phone scale (~5K nodes) — forces sparseness. A wide row of 72 mostly-null columns costs ~1,472 bytes; an EAV attribute row costs ~195 bytes, and only when a value exists.
SCHEMA IS DATATypes are objects
__TypeDef and __FieldDef are not tables — they are object types in the same graph as everything else. A type is a node; its fields are child nodes related by a parent edge. Inheritance is a parent pointer. The no-code UI calls defineObjectType() and addFieldToType() — these are reducers, not DDL. There is no ALTER TABLE, no migration, no deployment.
Inheritance resolves CSS-style. A leaf type inherits every field declared on every ancestor, with the leaf able to override. The Unified Field Model means that when a field is declared once on the root, every descendant gets it for free — no schema ceremony per subtype.
PHYSICAL MODELThe migration
The logical model is stable. The physical model migrated. The original implementation expressed EAV as two SpacetimeDB tables — oodb_object + oodb_attribute — joined by integer FK. SpacetimeDB was designed as a deterministic game-engine compute layer; it did not understand what those two tables were doing. Every knowledge-graph-specific need had to be bolted on as another table or another client-side index.
Five bolt-ons accumulated: a derived index table written through on every attribute upsert; a natural-key registry of composite strings because there were no multi-column uniqueness constraints; a separate embedding table because vectors were not first-class; three relevance tables with hand-tuned decay constants because the engine has no learned scoring; and a client-side materialisation step that joined the two core tables in JavaScript on every subscription update.
ioboxx-core collapses all of that into one native typed Node struct. Attributes live inline in a sparse map. Edges are typed and first-class. The embedding sits inline on the node as a TurboQuant 3-bit compressed vector. Surprise and momentum scalars sit inline as well, replacing the three relevance tables. Indexes and uniqueness are native engine features, not write-through derived state. The query returns pre-materialised nodes — the client no longer joins anything.
| Aspect | SpacetimeDB (then) | ioboxx-core (now) |
|---|---|---|
| Entity + attributes | Two tables, FK join | One struct, inline BTreeMap |
| Edges | Integer columns (parent_ref, ref_object_id) | Typed edge list, first-class |
| Type hierarchy | String field, resolved by client hook | TypeId with native parent chain |
| Indexes | Derived table, write-through | Native B-tree on attribute keys |
| Uniqueness | String registry table | Native constraint on (type, field, value) |
| Vector | Separate table | Inline optional field, TurboQuant compressed |
| Relevance | 3 tables + hand-tuned constants | Inline surprise + momentum, engine-native |
| Materialisation | Client-side JS | Server-side, query returns whole nodes |
MEMORY4.4× smaller at production scale
The collapse is not cosmetic. At production scale — 77,500 nodes, 294,000 attributes, one embedding per node — the SpacetimeDB physical model needed about 420 MB. ioboxx-core needs about 96 MB for the same logical content. The dominant savings come from inline TurboQuant embeddings and from eliminating the five derived tables.
| Component | SpacetimeDB | ioboxx-core |
|---|---|---|
| Objects | 16.5 MB (77.5K × 213 B) | ~60 MB (nodes incl. attrs, 77.5K × ~780 B) |
| Attributes | 57.3 MB (294K × 195 B) | inline (counted in node row) |
| Indexes | 26.6 MB (117.6K × 226 B) | ~5 MB native overhead |
| Embeddings | ~318 MB (77.5K × ~4,100 B, f32) | ~30 MB (77.5K × ~384 B, TurboQuant 3-bit) |
| Total | ~420 MB | ~96 MB |
At smartphone scale (5K nodes, before embeddings) the same arithmetic lands at ~6.2 MB for the new physical model versus ~15.5 MB for the old. The phone budget — discussed under tensor as substrate — is what made the migration mandatory.
SURFACEThe MCP contract did not move
Consumers of the Object Store — the observation surface, the no-code UI, every Fundi calling MCP tools — did not change. The MaterializedObject shape is byte-identical. The get_object MCP response is byte-identical. All 24 MCP tools have the same signatures. The __TypeDef / __FieldDef schema-as-data contract is identical. Only the engine internals — what produces those responses — changed.
This is the load-bearing property of the migration. The logical model was always right. The physical model was wrong for the workload, in a way the bolt-ons made progressively worse. Collapsing it into native nodes removes the bolt-ons without breaking anything above the engine line.
Related
- /docs/architecture/memory — the Memory component this substrate sits under.
- /docs/architecture/memory/tensor-as-substrate — why EAV decomposed all the way down is a sparse tensor and matches the model's native primitive.