Protocols
Embedder, LLMClient, and other provider seams.
pgmem is model- and driver-agnostic: it depends on narrow, structural Protocols,
not on any concrete SDK. You bring implementations and the PgMem composition root
wires them together. Anything that matches the shape works — no base class to
inherit.
Provider seams (you bring these)
Embedder
class Embedder(Protocol):
dimension: int
async def embed(self, texts: Sequence[str]) -> list[list[float]]: ...Turns text into dense vectors for the semantic graph. dimension must be 1536
(the schema's vector(N) width). Required for Tier 4 ingestion, search, and
community building.
LLMClient
class LLMClient(Protocol):
async def generate(self, prompt: Prompt, *, response_model: type[T]) -> T: ...Structured generation for pgmem's internal work (entity/edge extraction, dedup,
contradiction resolution, compaction summaries, saga briefs). It receives a
Prompt (a list of messages) and the Pydantic model to fill, and must use the
provider's structured-output feature to return a validated instance. This powers
pgmem's internal calls — generating the user-facing reply is your own call
against compile_context().messages.
CrossEncoder (optional)
class CrossEncoder(Protocol):
async def rank(self, query: str, passages: Sequence[str]) -> list[float]: ...Reranks passages against a query. Optional — wiring one selects the cross-encoder search recipe.
Internal seams
Tokenizer
class Tokenizer(Protocol):
def count(self, text: str) -> int: ...Counts tokens for the context compiler's budget. Defaults to tiktoken's
o200k_base; configure via TokenizerConfig
or inject your own.
Executor
class Executor(Protocol):
async def execute(self, query, /, *args) -> Any: ...
async def fetch(self, query, /, *args) -> Any: ...
async def fetchrow(self, query, /, *args) -> Any: ...
async def fetchval(self, query, /, *args) -> Any: ...
async def executemany(self, query, args, /) -> Any: ...The database surface the storage layer needs. Satisfied structurally by pgmem's
Driver, an asyncpg pool, and a transaction Connection alike — you don't
implement this; it's why repositories run standalone or inside a transaction
without binding to a concrete driver.
Wiring
mem = await PgMem.create(
dsn,
embedder=embedder, # Embedder
llm=llm, # LLMClient
cross_encoder=reranker, # CrossEncoder | None
tokenizer=tokenizer, # Tokenizer | None (defaults to tiktoken)
)The session/turn/memory/context surface needs no providers; embedder + llm are
required only for the semantic graph and compaction.
Related
- PgMem — where providers are injected
- Configuration — tokenizer + recipe selection
- Your first memory-backed agent — bringing providers