Manage risk

Identity resolution over shared attributes

Every company above a certain age has the same customer three times: once from signup, once from support, once from an acquisition's CRM. The records disagree; what they share — an email, a phone, a card — is how you find them.

The model

The shape of the problem

Make each source record a node and each identifying attribute — email, phone, address, payment method — its own node. Records that share an attribute are now literally connected, and a duplicate cluster is a connected component you can walk with a variable-length path. Matching logic becomes visible structure instead of a tangle of LIKE clauses.

Match transitively, not just pairwise

A shares an email with B; B shares a phone with C. Pairwise SQL matching finds A–B and B–C and misses that all three are one person. A variable-length traversal across shared attributes finds the whole chain in one query.

Keep the evidence

Instead of destructively merging rows, link records to a golden Person node with MERGE. Every match stays inspectable — which attributes connected these records, and when — so a wrong merge is reversible and an auditor can see the reasoning.

Search the messy parts

Names and addresses rarely match exactly. Built-in full-text indexes with BM25 ranking find candidate records despite typos and variant spellings; the graph then confirms or rejects the candidate through harder identifiers.

The full duplicate cluster around one record, via shared identifiers.cypher
MATCH (seed:Record {id: $id})
MATCH path = (seed)-[:HAS_EMAIL|HAS_PHONE|HAS_CARD*1..6]-(dup:Record)
WHERE dup <> seed
RETURN DISTINCT dup.source, dup.name,
       [n IN nodes(path) WHERE NOT n:Record | n.value] AS via

Questions

Identity resolution, asked directly.

Why not just do this in SQL?

Pairwise matching works in SQL. Transitive matching — A matches B matches C — needs recursive self-joins that grow with cluster size. In a graph the cluster is already connected; you walk it.

How do fuzzy matches work?

Full-text indexes with BM25 ranking handle the approximate side — names, addresses, typos. The graph handles the exact side — shared emails, phones, cards. Real pipelines use the first to propose and the second to confirm.

Is merging destructive?

It doesn't have to be. The golden-record pattern links source records to a Person node and keeps every original intact, so a bad merge is one DELETE of a relationship, not a data-recovery exercise.

Can this run continuously?

Yes. New records MERGE their attribute nodes on ingest, which connects them to any existing cluster immediately — resolution becomes a property of the write path rather than a quarterly project.

What about consent and audit?

The graph records why two records were linked — the shared attributes are the edges. The console adds an audit log of every administrative change on the instance itself.

How do I load records from many systems?

Connectors cover the usual sources — Postgres, BigQuery, Snowflake, S3/GCS/Azure, Kafka — and idempotent MERGE loads mean re-running an import can't create the duplicates you're trying to remove.

Start now

~98.7%

token efficiency at 2,000 entities — see the footnotes above

Put your first graph up in a minute.

A free instance takes about a minute and no card. Write two MERGE statements, read them back, and you have a living graph — with provenance on every fact.

First-graph path

Live
  1. Create a free instance

    No card. Ready in about a minute.

  2. Connect your driver

    bolt+ssc:// URI into the driver you already use.

  3. Write two MERGEs

    That's the entire shape of agent memory.

  4. Point an agent at it

    One MCP config block. No integration code.