Skip to main content

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