Data
Where memory physically lives. Object Store + Supabase, per-tenant sovereignty, commitlog-first.
Memory is an architectural concern — see /docs/architecture/memory for the substrate (Object Store + MCP). Data is a storage concern: where the bytes physically sit, how durability is guaranteed, how each tenant's working memory is kept apart from every other tenant's. This page describes that physical layer.
SHAPETwo storage layers, deliberately
IOBOXX runs two database engines in production. They serve fundamentally different workloads and are connected by JWTs, not by joins. The split is permanent — the techstack-positioning thesis names “single-tenant Postgres-only architecture” as an explicit non-goal. We do not collapse them.
| Layer | Holds | Shape |
|---|---|---|
| Object Store operational memory | Every working object an agent reads or writes. The Entity-Attribute- Value substrate, BPMN execution state, embeddings, access logs, chat, AR observation surface state — anything that participates in the live loop. | In-memory primary, commitlog-first writes, per-row subscriptions over WebSocket. |
| Supabase control-plane surfaces | A narrow set of slow-changing surfaces: Keycloak event projection landing, run history, audit events landing before they ship to S3 Object Lock. Not the entire control plane — specific surfaces where PostgreSQL's relational shape pays for itself. | Disk-first PostgreSQL with mature operator tooling (PITR, WAL streaming, replication). |
Working memory does not belong in Postgres and slow-changing relational data does not belong in the Object Store. Each engine does what it is best at, and neither one is asked to compromise to cover for the other.
OPERATIONALThe Object Store layer
The Object Store is the physical home for working memory. Its storage characteristics matter because they shape what kinds of reasoning Fundis can do over it.
- In-memory primary. All live tables sit in RAM. Reads are pointer chases, not disk seeks. Subscriptions push row changes to clients in the same tick the reducer commits.
- Commitlog-first writes. Every reducer invocation is appended to a per-instance commitlog and fsynced before the transaction returns. The in-memory tables are a materialised view of the log, not the source of truth. RPO is zero.
- Binary + JSON snapshots. Periodic snapshots of the in-memory state let recovery truncate old log entries. Snapshots are file-level — safe to copy while the instance is running.
- Full WAL replay. On restart, the instance loads the latest snapshot and replays every commitlog entry after that snapshot's transaction number. Determinism guarantees the resulting state is byte-identical to what existed before the crash.
- S3 Object Lock for immutable audit. Audit event batches are shipped to S3 Object Lock buckets in compliance (WORM) mode. Once written, they cannot be altered or deleted within the retention window — by anyone, including us.
CONTROLThe Supabase layer
Supabase is intentionally narrow. It holds a small set of surfaces where PostgreSQL's 30-year operator ecosystem (PITR, replication, query optimisers, encryption-at-rest, audit extensions) outweighs the cost of running a second engine:
- KC events landing. Keycloak emits identity events (logins, role grants, organisation membership changes); they land in Postgres before being projected into Object Store
kc_*tables. - Run history. Long-form records of completed BPMN runs and agent sessions live where SQL aggregation and mature reporting tooling can reach them.
- Audit-immutable backup landing. Audit events are batched in Postgres and then shipped to S3 Object Lock. Postgres is the staging surface; S3 is the immutable home.
What does not live in Supabase: working memory, embeddings, chat, BPMN tokens, anything Fundis read or write through MCP. Those are Object Store concerns and stay there.
SOVEREIGNTYPer-tenant isolation at the substrate level
IOBOXX does not isolate tenants by row-level rules inside a single shared database. Each twin gets a sovereign SpacetimeDB instance and a sovereign ioboxx-core vault. Isolation is at the substrate — the data plane itself is dedicated, not a partition of a shared schema.
This makes three things easier and one thing irrelevant. Easier: regulator questions about data residency (the data physically lives where the tenant says); operator rituals like backup, restore, and capacity planning (each tenant is its own unit); and self-hosting (a sovereign instance is the deployment model, not a downgrade from it). Irrelevant: the entire class of row-level multi-tenancy bugs where a missing tenant_id guard leaks data across tenants. There are no row-level guards because there is no shared table to guard.
BOUNDARYMemory vs data
The line is worth holding clearly. Memory is the substrate the platform converges onto — the typed object graph plus the MCP that reaches it. /docs/architecture/memory is the right page for that. Data is where memory's bytes live: the storage engines, the durability model, the per-tenant boundary. This page is the right page for that. Access to either is gated by /docs/architecture/authorization— JWT-claim-driven against Keycloak roles and organisation membership, with no row-level multi-tenancy primitive to lean on or get wrong.
READING ORDERWhere to go next
- For the substrate this layer holds — memory.
- For who can reach it — authorization.