You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add entity canonicalization/merge step to prevent duplicate nodes across files #207
Graph construction currently treats extracted nodes as unique by technical ID, which is often file/chunk-
local.
As a result, the same real-world concept/entity can appear as multiple graph nodes, causing fragmentation and
noisy analytics.
Symptoms
Duplicate semantic nodes with different IDs.
Alias/casing/punctuation variants not merged.
Related neighborhoods split across disconnected components.
Distorted centrality, clustering, and query results.
Expected behavior
Graph should support a canonical entity layer so semantically identical nodes are merged into one canonical
node while preserving provenance.
Proposed solution
Introduce an Entity Resolution phase after extraction and before clustering/export:
Adding a concrete data point for the cross-file half of this, from a 14,505-note Obsidian vault extracted with 0.9.48.
Because node identity is minted per source file, the same real-world entity ends up as several nodes whenever the surface form varies at all across files. Counts from one run:
39 twin pairs of the form @handle vs bare handle — i.e. the only difference is a leading @.
12 duplicate groups where a hand-curated note and one or more importer-generated notes describe the same entity, differing only by case and/or the @ prefix (e.g. @cyrilxbt / cyrilXBT / @cyrilXBT).
2 of those groups are byte-identical filenames in two different directories, which downgrades from "graph noise" to a correctness problem in the host app: Obsidian's [[wikilink]] resolution becomes ambiguous, so the human-facing links break too.
The existing fuzzy-dedup pass does not catch any of these, because it operates within a single file's extraction result — every one of these duplicates is a cross-file collision by construction.
Two observations that might make an Entity Resolution phase cheaper to scope than it looks:
In this corpus, case-folding plus stripping a leading @ would have collapsed all 39 twin pairs and all 12 groups on its own. A conservative deterministic normalization step (no fuzzy matching, no embeddings) already captures the bulk of the damage — the expensive similarity machinery could stay opt-in for the long tail.
Choosing the survivor is well-signposted: every non-canonical copy we inspected was a mechanical stub (frontmatter + a ## Connections edge list, zero prose), while the hand-curated note carried all the real content. "Prefer the node whose source has non-generated body content" picked correctly in 12/12 groups.
Related: #296 covers the same mechanism from the node-bloat angle.
Activity
yotamleo commentedon Aug 23, 2026
Adding a concrete data point for the cross-file half of this, from a 14,505-note Obsidian vault extracted with 0.9.48.
Because node identity is minted per source file, the same real-world entity ends up as several nodes whenever the surface form varies at all across files. Counts from one run:
@handlevs barehandle— i.e. the only difference is a leading@.@prefix (e.g.@cyrilxbt/cyrilXBT/@cyrilXBT).[[wikilink]]resolution becomes ambiguous, so the human-facing links break too.The existing fuzzy-dedup pass does not catch any of these, because it operates within a single file's extraction result — every one of these duplicates is a cross-file collision by construction.
Two observations that might make an Entity Resolution phase cheaper to scope than it looks:
@would have collapsed all 39 twin pairs and all 12 groups on its own. A conservative deterministic normalization step (no fuzzy matching, no embeddings) already captures the bulk of the damage — the expensive similarity machinery could stay opt-in for the long tail.## Connectionsedge list, zero prose), while the hand-curated note carried all the real content. "Prefer the node whose source has non-generated body content" picked correctly in 12/12 groups.Related: #296 covers the same mechanism from the node-bloat angle.