Skip to main content
Fabric’s knowledge graph is a typed, weighted, timestamped model of what it knows. Nodes are people, threads, meetings, channels, folders, domains, customers. Edges are the relationships between them — ten typed edge kinds today. It lives in Postgres as two tables: graph_nodes and graph_edges. You can SELECT against it, join it to your operational data, and inspect it in any Postgres client.
No Neo4j. No graph database to sync. One database for everything.

Node structure

Each node is one row in graph_nodes:
text
required
Stable per-node identifier.
text
required
Tenant isolation — every query filters on this.
text
required
Project-level scoping within a tenant.
text
required
Typed category: person, email_thread, slack_message, meeting, file, folder, domain.
text
required
Origin connector: gmail, slack, fireflies, google_drive, imap, postgres, mysql.
text
required
External ID at the source (e.g. Gmail thread ID, Slack message timestamp).
text
Display title. Weighted A in the BM25 index.
text
Full text. Weighted B in the BM25 index.
timestamptz
When the underlying event happened.
vector(1536)
pgvector column for semantic search.
tsvector
Full-text search column maintained by trigger.
jsonb
Source-specific fields.

Typed edges

Each edge is one row in graph_edges with source_id, target_id, relation, weight, source_date, and metadata.
Email or Slack message → the person who wrote it. Most-common edge type in a Gmail-heavy tenant.
Message B is a reply to message A. Enables thread reconstruction without relying on provider threading.
Individual messages link to their parent thread node. Lets “summarize this thread” work across sources.
Slack message → channel node. Enables #channel-scoped search.
Fireflies meeting → person. Foundation for “who was in the Phoenix kickoff?”
Meeting node → organizer person. Different from attended — tracks initiative.
Generic participation edge, used for email threads with recipients who aren’t sender.
Drive file → parent folder. Enables folder-scoped queries.
Person node → domain node. Surfaces cross-domain relationships (internal vs external).
Person entity → the person:email@company.com alias. Enables entity resolution.
Edges are directional but queried bidirectionally — follow_edges(node_id) returns all edges where the node is either source_id or target_id.

Why typed edges matter

Glean’s Enterprise Graph is a closed system — you can’t query it, inspect it, or join it with other data. Fabric’s graph is two Postgres tables. The structure is the point.
A vector store knows “Cole Smith” is semantically near “Project Phoenix.” It doesn’t know why. The graph knows Cole attended the Phoenix kickoff on April 7, sent_by three emails about the launch, and replied_to the legal review thread. Typed edges turn “find similar text” into “reason about who, when, and why.”

Querying the graph

Natural-language questions implicitly traverse the graph.
Q: Who has context on the billing migration?
Fabric finds the X topic node, follows attended, participant, and sent_by edges outward, and ranks the returned people by edge weight and recency.

Multi-hop traversal

The reasoning loop walks 2–3 hops out from a seed node via Postgres recursive CTE:
This enables questions like:

Who has worked with Cole on billing-related topics?

Cole → threads where Cole is participant → other people participant in those same threads.

What decisions were made about Phoenix?

Phoenix topic → meetings attended for Phoenix → observations linked to those meetings.

Graph explorer

The explorer in the web UI is an interactive visualization — search for any entity, expand its neighborhood, filter by edge type, navigate by clicking.

Verify extraction

Confirm new connectors are producing the edges you expect.

Audit answers

Understand “why did the answer include this person?”

Explore unknowns

Find connections you didn’t know existed.

Graph maintenance

Entities and edges update on each sync. Deleted source content triggers node and edge removal on the next run. Edge weights decay slightly per week unseen, so old relationships fade unless reinforced by new activity.
The graph reflects the current state of your data, not a frozen snapshot.

Why Postgres and not Neo4j

Fabric uses Postgres with pgvector for everything — graph, memory, vector search, full-text search, operational data.

One database

No sync overhead between a graph DB and a relational DB.

Recursive CTEs

Fast enough for 2–3 hop traversal at current scales.

pgvector mature

HNSW indexes, cosine distance, production-grade.

Inspectable

Any Postgres client, any BI tool, any ORM you already run.
When the graph outgrows recursive CTEs, Postgres is straightforward to pair with a dedicated graph store as a read replica — the two tables are simple enough to replicate without rework.