The single-owner model
memloom’s default store is PGLite: real Postgres compiled to WASM, embedded in the daemon
process, with data in a plain folder. PGLite allows exactly one connection, so
memloom serve is the single owner of the store, and every other surface is a client of
the daemon. (The same daemon runs on an external Postgres via MEMLOOM_PG_URL; the
single-owner API model stays, only the storage moves out of the process.)
The daemon exposes the store two ways from one process: the HTTP API on 127.0.0.1:4319
(which also serves the viewer bundle at /), and, on the embedded tier, the raw Postgres
wire protocol on 127.0.0.1:54329 for database tools (with MEMLOOM_PG_URL set there is
no bridge; point your tools at the server). Any CLI command auto-starts the daemon if it
isn’t running; memloom stop shuts it down gracefully and releases the data-dir lock.
Because PGLite is single-connection, a connected wire client (Drizzle Studio, psql, an IDE
database panel) holds an exclusive lock and every API call queues behind it. The API detects
this and answers 503 within ~1.5 seconds instead of hanging; the serve terminal warns
when a wire client attaches. Disconnect the client to resume.
Packages
The engine contract
Every surface programs against one interface, MemoryEngine:
Two implementations exist: Memloom (the real engine, in-process) and HttpMemloomClient
(the same interface over HTTP). The CLI and MCP use the HTTP client, which is why many
clients can share one store safely: they were never holding the store in the first place.
Storage tiers
Storage is an adapter interface (query, exec, tx), with two implementations:
PgliteAdapter: the embedded tier. What the daemon uses. Zero setup, data in a folder.
PgAdapter: a real Postgres server over the wire (Docker, Supabase, any managed
Postgres with pgvector), pooled. Selected with MEMLOOM_PG_URL in config.env; see
Self-hosting.
The schema is identical in both: memloom’s migrations are plain SQL and its one stored
function (memloom_fuse) is LANGUAGE sql rather than plpgsql, so it runs identically on
PGLite and server Postgres.
Design rules worth knowing
- All config is injected. Core never reads
process.env or global state. The daemon is
the only place environment/config is read, then everything is passed in as constructor
arguments. This is why the whole engine is testable without a network.
- The edge table has no foreign keys.
memory_edges links memories, entities, and
context chunks in one table. Those are different node tables, so FKs are impossible by design. The
trade: cascade deletes can’t clean edges, so every chunk/memory removal path deletes its
edges explicitly.
- Nothing is destroyed. Superseded memories go
status = 'stale', edges deactivate
(active = false) rather than being deleted. This is what makes conflict resolution and
versioning reversible.
- Migrations are ordered and idempotent.
Memloom.init() runs pending migrations, then
verifies the store’s embedding fingerprint: a store embedded with one model refuses to
open under another, because mixed vector spaces make similarity silently meaningless.
Data layout