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:
id
text
required
Stable per-node identifier.
tenant_id
text
required
Tenant isolation — every query filters on this.
project_id
text
required
Project-level scoping within a tenant.
node_type
text
required
Typed category: person, email_thread, slack_message, meeting, file, folder, domain.
source_type
text
required
Origin connector: gmail, slack, fireflies, google_drive, imap, postgres, mysql.
source_ref
text
required
External ID at the source (e.g. Gmail thread ID, Slack message timestamp).
title
text
Display title. Weighted A in the BM25 index.
content
text
Full text. Weighted B in the BM25 index.
source_date
timestamptz
When the underlying event happened.
embedding
vector(1536)
pgvector column for semantic search.
search_vector
tsvector
Full-text search column maintained by trigger.
metadata
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:
WITH RECURSIVE reachable AS (
  SELECT id, 0 AS depth FROM graph_nodes WHERE id = $seed
  UNION
  SELECT n.id, r.depth + 1
  FROM reachable r
  JOIN graph_edges e ON e.source_id = r.id OR e.target_id = r.id
  JOIN graph_nodes n ON n.id = CASE WHEN e.source_id = r.id
                                    THEN e.target_id ELSE e.source_id END
  WHERE r.depth < 2
)
SELECT * FROM reachable;
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.