Knowledge graph

The knowledge graph is the data substrate under everything. The four namespaces (memory, skills, tasks, user) live on top of it; so do every entity in every module (CRM, content, analytics).

If the agent ever cites a number, it came from here.

Shape

A typed graph in Postgres with three core tables:

TableContents
graph_nodesEntities — contacts, deals, accounts, documents, conversations, memories, skills, tasks. Each has a kind, a JSON data payload, and an embedding.
graph_edgesTyped relationships — belongs_to_account, replied_to, linked_to_deal, etc. Edges have their own data payload.
graph_node_embeddingsDecoupled embedding storage; supports 1536-dim (cloud) and 768-dim (selfhost) in parallel columns so swapping embedding providers doesn't require a rewrite.

Every module persists its entities as nodes. The agent doesn't query modules; it queries the graph. That's why agents can synthesize cross-module ("show me the deals that are stuck AND whose primary contact hasn't engaged with our content in 30 days") — the data is shape-compatible at the graph level.

Semantic search

The single most important RPC is graph_semantic_search. It accepts:

graph_semantic_search({
  p_tenant_id: uuid,           // always explicit — RLS contract
  p_query_text: string,        // for fuzzy fallback
  p_query_embedding: vector,   // primary signal
  p_kinds?: string[],          // filter by node kind
  p_limit?: number,            // top-N
  p_min_score?: number,        // floor
})

Returns ranked nodes with a score field combining:

  • Vector cosine — embedding similarity (primary)
  • pg_trgm — fuzzy text match (fallback when embedding dim mismatches, e.g. self-host on 768-dim)
  • Recency boost — newer nodes get a small boost
  • Kind weights — module-tunable

The agent calls this on every run. You can see what it pulled in the run drawer → Tool calls.

Embeddings

Cloud uses text-embedding-3-small (1536 dim). Self-host defaults to nomic-embed-text from Ollama (768 dim). Both write to graph_node_embeddings — into the column matching their dimension.

When graph_semantic_search is called, it picks the column matching the runtime embedding provider. If the embedding column for the runtime provider is empty (e.g. selfhost switched to OpenAI mid- deployment), it falls back to pg_trgm fuzzy on query_text. Recall degrades gracefully; the agent still gets results.

Citations

Every claim the agent makes is annotated with the graph nodes it came from. In the UI, hover any number, name, or summary — the citation drawer slides in showing the source rows. Click through to the entity for the full record.

The citation contract is enforced at the orchestrator's post-generation pass: if the model produces a number with no matching source in the assembled context, the post-processor flags it and the run is marked partial-confidence (still surfaces, with a visible warning).

Invalidation

When an entity changes — a deal moves stage, a contact gets edited — the graph node's updated_at bumps and any dashboard card or Today card that cited it gets invalidated. The card's regenerator re-runs, the user sees fresh content. See Dashboard surface for the invalidation plumbing.

Triggers + backfill

Every module's table has a Postgres trigger that mirrors writes into the graph. The triggers are generated and shipped as part of each release, so adding a new module type auto-wires it. The backfill scripts handle the initial population when you upgrade past a schema change.

For operators: trigger code is shipped as migrations. You shouldn't have to touch it day-to-day — ccd upgrade handles the migration apply.

Multi-tenant safety

Every graph query is RLS-enforced. Even graph_semantic_search takes p_tenant_id as an explicit parameter; the function does its own tenant-isolation check before falling through to the underlying tables (which also have RLS). Defense in depth.

The single exception is the cross-org benchmarks flow (cloud only) — it operates on aggregates only, with k-anonymity (k≥5) enforced. Raw nodes are never crossed. See Security model.

Performance

Common operations and their characteristics:

OperationLatency (typical)Notes
graph_semantic_search (kind-filtered, top 10)30–80 msIVFFlat index on embeddings, GIN on kind
Entity write + trigger fanout5–15 msTrigger is synchronous; keep payloads small
Card invalidation cascade<100 msAsync; runs in worker, not on the write path
Bulk import (1k contacts)~3 sEmbeddings batched; triggers honor batch context

The graph is sized for typical tenants up to ~5M entity nodes. Beyond that, sharding by tenant becomes interesting; the schema supports it but no automation yet.

Read next