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_episodewhen 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_factsand keep current state out of the extractioncontent.
Related
- The five-tier model — where the graph sits (Tier 4)
- The bi-temporal model — how edges track time
- Trust & memory formation — what earns a place in the graph
- Ingest & search the graph — the how-to