Skip to content

Add entity canonicalization/merge step to prevent duplicate nodes across files #207

Open

Description

@yanikengelhardt

Problem

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:

  1. Normalize candidate node labels (casefold, unicode normalization, punctuation/whitespace cleanup).
  2. Resolve aliases via configurable dictionary/rules.
  3. Optionally compute similarity-based candidates with conservative thresholds.
  4. Build canonical IDs and merge equivalent nodes.
  5. Rewire edges/hyperedges to canonical nodes.
  6. Preserve provenance:
    • original node IDs
    • original labels/aliases
    • source files/locations
  7. Recompute graph metrics (communities, centrality, report outputs) on canonical graph.

Suggested implementation details

  • New module: entity_resolution (or similar).
  • New config section for alias rules and thresholds.
  • Optional exported artifact: entity_map.json mapping original IDs -> canonical IDs.
  • Include merge statistics in run report (nodes merged, aliases applied, unresolved candidates)

Activity

  1. yotamleo commented on Aug 23, 2026

    @yotamleo
    Contributor

    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:

    1. 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.
    2. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions