What the Engine owns
src/pmb/core/engine/base.py builds one Engine per workspace, composed from
focused mixins. The constructor opens SQLite immediately but keeps BM25, LanceDB,
embedding models, and graph caches lazy - so pmb stats and pmb config don’t
pay the vector-store cold start.
Config
Workspace detection, layered settings, model choice, and feature gates.
Stores
SQLite for truth, LanceDB for vectors, BM25 for exact terms, graph tables for links.
Runtime
Session tracker, recall cache, write outbox, embed queue, touch buffer.
Surfaces
Write, batch, recall, lessons, overview, goals, dedup, health, ambient APIs.
Storage schema
SQLite is the source of truth (events.sqlite); LanceDB is the vector side index
beside it.
Write path
Writes are durable first: the event lands in SQLite before any optional background work. Embeddings, graph extraction, and dedup may lag - they have retry paths and safe fallbacks - and recall can still find a fresh event via SQLite + BM25 while the embedder warms.Recall path
The hot path never calls an LLM. Optional LLM work happens at write time, maintenance time, or for explicit commands (consolidate, reflect, distill).
Concurrency & durability
SQLite WAL + busy-timeout
Dashboard, MCP, hooks, and CLI can touch one workspace at once.
Single warm runtime
The daemon keeps one warm Engine/model/vector store instead of loading per agent.
Crash-safe queues
Async writes and pending embeddings have durable tables that replay after restart.
Buffered touches
Recall access-count updates are coalesced so parallel recalls don’t fight SQLite.