You've got 500 documents. A contract references a statement of work. That SOW links to three project plans. Those plans mention a vendor agreement. The vendor agreement connects back to the original contract through a compliance clause.
Most document tools treat this as a retrieval problem: search for "contract," get results, click through manually. AiFiler treats it as a relationship problem. The knowledge graph is what makes that distinction possible.
The Problem We Solved
Traditional document systems are flat. They have documents, metadata, and search indexes. That's it. When you want to find related documents, you're either:
- Searching again (slow, requires you to know what to search for)
- Manually linking (doesn't scale past 50 documents)
- Hoping the AI guesses (unreliable, no audit trail)
We needed something different. We needed to store the actual relationships between documents in a queryable way, without making every operation slower.
The challenge: 8 different types of relationships exist in real work. A document can reference another, depend on it, contradict it, supersede it, implement it, or relate to it in ways that don't fit a single category. Build a system that handles all 8 types, and you can answer questions like "show me every document that depends on this contract" instantly.
Build it wrong, and you're doing graph traversals that take 30 seconds.
The Architecture: How We Connect Without Latency
Here's how the knowledge graph actually works in AiFiler:
The 8 Edge Types:
- References — Document A mentions Document B (informational, no dependency)
- Depends On — Document A requires Document B to be valid or complete
- Supersedes — Document A replaces or updates Document B
- Contradicts — Document A conflicts with Document B (compliance risk)
- Implements — Document A is the execution of Document B (requirement → implementation)
- Related To — Document A is thematically connected to Document B
- Cites — Document A formally references Document B (legal, regulatory)
- Derived From — Document A was created from Document B (analysis, summary, extract)
Each edge is directional and weighted. When Claude analyzes a document, it doesn't just say "this relates to that." It assigns:
- Edge type (which of the 8)
- Confidence score (0.0–1.0, based on textual evidence)
- Supporting excerpt (the actual sentence that triggered the connection)
- Directionality (A → B, not bidirectional)
The data model in Supabase looks like this:
CREATE TABLE knowledge_edges (
id UUID PRIMARY KEY,
workspace_id UUID,
source_doc_id UUID,
target_doc_id UUID,
edge_type TEXT,
confidence FLOAT,
excerpt TEXT,
created_at TIMESTAMP,
updated_at TIMESTAMP,
INDEX (workspace_id, source_doc_id),
INDEX (workspace_id, target_doc_id),
INDEX (workspace_id, edge_type)
);
Why these indexes matter: When you open a document in AiFiler, we run three queries:
- Find all edges from this document (outgoing relationships)
- Find all edges to this document (incoming relationships)
- Filter by edge type if the user is looking for something specific
Without the right indexes, query 1 alone would scan millions of rows. With them, it's sub-100ms even at 100K+ documents.
The Real-Time Indexing Pipeline
Documents don't just magically get connected. Here's the flow:
Step 1: File Ingestion
When you upload a document (via the New Document button in the sidebar or drag-and-drop into a Matrix), it goes to lib/ingest/parseFile.ts. We extract text, metadata, and structure.
Step 2: Vector Embedding & Storage
The document text is sent to Anthropic's Files API (lib/ai/fileStore.ts). This gives us a persistent file reference that Claude can reason about without re-uploading.
Step 3: Relationship Analysis Claude reads the document and runs a structured analysis:
Analyze this document. For each other document in the workspace:
1. Does this document reference it? (edge type: references)
2. Does this document depend on it? (edge type: depends_on)
3. Does this document supersede it? (edge type: supersedes)
... [repeat for all 8 types]
For each relationship you identify, return:
- target_doc_id
- edge_type
- confidence (0.0-1.0)
- excerpt (the sentence proving the relationship)
Step 4: Edge Storage
Results are written to knowledge_edges table. If an edge already exists, we update the confidence score and excerpt. This prevents duplicate edges and keeps the graph clean.
Step 5: Query-Time Retrieval When you open a document in View Mode or run a search, we fetch related documents from the graph. The Related Documents panel (right sidebar in View Mode) shows:
- Outgoing edges (this document points to these)
- Incoming edges (these documents point to this)
- Filtered by edge type if you click a specific type
The query in lib/knowledge/ hooks uses SWR with localStorage prefixing for offline-first data fetching. If you're on a slow connection, you see cached results instantly while fresh data loads in the background.
Why Directionality and Confidence Matter
Here's where most knowledge graph implementations fail: they treat all relationships equally.
AiFiler doesn't. A "depends on" edge is not the same as a "related to" edge. If Document A depends on Document B, and B gets updated, A might need review. If A is merely related to B, an update to B is just context.
The confidence score prevents noise. If Claude is 65% sure about a relationship, it still stores it—but the UI can filter it out or show it differently. In the Related Documents panel, you can toggle "Show low-confidence edges" to see tentative connections.
This matters in practice: You're reviewing a contract. The Related Documents panel shows:
- Depends On (high confidence): Statement of Work, Budget Approval
- Supersedes (high confidence): Previous Contract v2.1
- References (medium confidence): Vendor Terms, Compliance Checklist
- Related To (low confidence): Team Handbook, Project Timeline
You can immediately see what's critical vs. what's just context.
The Performance Tradeoff
Building this system meant making hard choices.
We could have:
- Stored every possible edge (3 documents × 8 types = 24 edges minimum) → bloated the database, slow queries
- Run relationship analysis on every search (real-time, but 5-second latency per search)
- Built a full graph database (Dgraph, Neo4j) → operational complexity, cost, another system to maintain
We chose:
- Analyze relationships once per document (on upload or manual refresh)
- Store only edges above a confidence threshold (configurable, default 0.6)
- Index aggressively for query speed
- Use SWR for client-side caching (no repeated API calls)
This means relationship analysis happens in the background (users don't wait), and queries are fast because we're hitting a relational database with good indexes, not traversing a graph in real time.
What This Means For You
Three concrete things change how you work:
1. Find related work instantly Open any document. The Related Documents panel shows what's connected. No search queries, no manual linking. Click a related document to jump to it. In the Universal Command (Ctrl+Shift+A), type "related:" to search by edge type.
2. Spot risks automatically
The Contradicts edge type flags documents that conflict. A compliance checklist contradicts a contract clause? The system shows you. You can filter the Matrix view by edge type using the search operator edge:contradicts to see all conflicting relationships in one place.
3. Understand document lineage The Derived From and Implements edges show you the chain of creation. A spreadsheet analysis was derived from raw data? You can trace back to the source. A policy document implements a requirement? You can see the requirement instantly.
These aren't nice-to-haves. In regulated industries (finance, legal, healthcare), understanding document relationships is the difference between compliance and risk. In product teams, it's the difference between shipping on time and discovering mid-project that a design contradicts an earlier decision.
The Lessons We Learned
Directionality is everything. We initially tried bidirectional edges. It made queries simpler but made the data ambiguous. "A relates to B" could mean anything. "A depends on B" is actionable.
Confidence scores prevent false positives. Claude is smart, but it's not perfect. Storing confidence lets us show uncertain relationships without cluttering the UI. Users can opt into seeing them.
Indexes are not optional. We learned this the hard way. A 500-document workspace with 4,000 edges ran fine. A 10,000-document workspace with 80,000 edges didn't—until we added the three indexes above. Now it scales to 100K+ documents.
Offline-first caching matters. The knowledge graph is most useful when you're reading documents, which often happens in meetings or on the go. SWR with localStorage means you see related documents instantly, even if the network is slow.
The knowledge graph isn't magic. It's a deliberate choice to store relationships explicitly, analyze them once, and query them fast. It's the difference between a document tool and a document understanding tool.
Next time you're in AiFiler and the Related Documents panel shows you exactly what you need, that's the graph working. Next time you spot a contradiction before it becomes a problem, that's the edge types doing their job. And next time you trace a document back to its source in milliseconds, that's the indexes earning their place in the schema.
Enjoyed this article?
Get more articles like this delivered to your inbox. No spam, unsubscribe anytime.