pgmem
Concepts

The knowledge graph

How Tier 4 stores durable facts as a bi-temporal entity graph.

pgmem's semantic memory (Tier 4) is a knowledge graph: entities as nodes, relationships as edges, each edge carrying bi-temporal validity. It's what survives across sessions — the durable, queryable "what's true" layer beneath a single conversation. It lives in plain Postgres (pgvector + full text), so there's no graph database to run.

Why it matters

Claims (Tier 3) answer scoped, evidence-backed assertions. The graph answers "what do we know about this customer, across every conversation?" through explicit add_episode ingestion — deduped into stable entities and related edges, so graph knowledge learned in March is still retrievable, connected, and corrigible in July. Without it, knowledge is a pile of disconnected notes; with it, it is a model of your domain.

The model

Four node/edge kinds make up the graph:

  • Entities — the nouns (a customer, a plan, a ticket). Deduped by name and embedding, so "Acme" and "Acme Corp" resolve to one node.
  • Edges — relationships between entities (Acme —subscribes_to→ Enterprise), each with a valid-time interval. A contradiction closes the old edge and opens a new one; history is never overwritten.
  • Episodes — the source text an edge came from, kept for provenance.
  • Communities — clusters of related entities with an LLM-written summary, built on demand for higher-level recall.

How it works

You write to the graph with add_episode, which runs LLM extraction over text — pulling out entities and edges, deduping against what's already there, and resolving contradictions bi-temporally:

await mem.add_episode(
    content="Acme upgraded from Pro to Enterprise on a 2-year term.",
    group_id="acme",
)

For facts you already trust (a webhook, a validated tool result), skip extraction and assert them verbatim with provenance via external_facts — see Events vs state.

You read from it with mem.search, hybrid retrieval over the graph — vector similarity and full-text and graph traversal, fused into one ranked result:

results = await mem.search("what plan is Acme on?", group_ids=["acme"])
results.nodes   # matched entities
results.edges   # matched relationships (with validity)

Within a turn, the graph is one of the tiers the context compiler pulls from — you rarely call search directly on the response path.

Tradeoffs & pitfalls

add_episode requires both an embedder and an llm, and extraction is an LLM call — it's heavy. Keep it off the response path; ingest after you've replied, or in a background job.

  • Graph ingestion is explicit. Claim projections may be rebuilt asynchronously after a session ends, but session claims do not create graph entities or edges. Use add_episode when graph knowledge is required.
  • Extraction is only as good as its input. Feed it state-laden text and it may mint a state edge that goes stale. Assert events through external_facts and keep current state out of the extraction content.

On this page