πŸ’» Tilores Studio is now available. Run entity resolution locally on your machine.Download free

← Back to Blog
Engineering July 8, 2026 Β· 8 min read

What an Entity Resolution Graph Looks Like and How It Is Queried

Steven Renwick
Steven Renwick
CEO, Tilores
What an Entity Resolution Graph Looks Like and How It Is Queried

TL;DR: An entity resolution graph is source records as nodes, connected by probabilistic and deterministic match links as edges, resolved into one entity view per person or business. We build that graph at ingestion, and our GraphQL API lets you query the resolved entity directly, by ID or by any linked record, to retrieve its connected records, match edges, and aggregated Record Insights in a single call.

Book a demo to see a resolved entity graph on your data, or get the evaluation build to query it locally. For the retrieval pattern, explore Tilores IdentityRAG.

What does an entity resolution graph actually look like?

Every record that reaches us becomes a node, and the original values are always kept. During ingestion we can also normalise them: a phone number standardised, a name tidied, an email corrected. We persist that improved version alongside the original. Matching runs on the normalised data, and because it’s persisted you can use the cleaned-up version downstream too. The graph holds both, the raw evidence and the improved form.

Now the edges. An edge is a connection our matcher draws between two nodes when it has enough evidence they describe the same real-world person or business, and every edge carries the rule that created it. An edge is never just β€œthese two records match.” It is β€œthese two records match because an exact tax ID comparison fired between them” or β€œbecause a fuzzy name comparison plus a shared phone number cleared the threshold.” That rule reference lets a team trace a resolved identity back to the exact evidence behind it, the same trail you would want on hand for an audit, a dispute, or a false-positive review.

Fold enough connected nodes together and you get a resolved entity: every node reachable from every other through at least one match edge becomes part of a single entity view, sometimes called a golden record or a 360 profile. The entity is not a new record that overwrites the sources; it is a view over the connected records, walkable in either direction, from the entity down into a source record or from one record out to the graph around it. Every entity also carries a match score, so you can see how confident the resolution is without losing sight of why a merge happened. Getting that threshold right is its own discipline, covered in precision and recall in entity identity resolution.

How do we build this graph at ingestion?

Matching happens when a record arrives, not when someone queries it later. As each new record lands, we compare it against what has already been resolved, using deterministic rules for high-certainty fields (an exact tax ID match, a verified email, a government identifier) and probabilistic, fuzzy matching for everything else: name variants, address formatting differences, phonetic near-matches, and partial overlaps across several weaker fields at once. Where a rule clears its threshold, we add an edge; where enough edges connect a set of records, we fold them into one resolved entity.

That resolution and assembly happens at ingestion. A query later on retrieves the entity as it currently stands, already resolved, rather than re-running the match logic on demand, which matters wherever query-path latency is constrained: a support agent pulling up a customer, a fraud check at signup, an AI agent mid-conversation.

Getting that ingestion pipeline right, blocking so comparisons stay tractable at volume, survivorship rules for which value wins when two records disagree, retraining as sources shift, is a substantial engineering surface on its own. We cover the real complexity of building it correctly in the complexities of entity resolution implementation.

Entity resolution graph querying: the three ways into the API

Once records are resolved into a graph, entity resolution graph querying comes down to three entry points, and which one you use depends on what you already know about the person or business you are looking for.

  • search: match field values you have on hand against the resolved graph when you do not yet hold an internal identifier.
  • entity: retrieve the full resolved view for an entity ID your application already tracks.
  • entityByRecord: find the entity a single source record currently belongs to.
Entry pointWhat you provideWhat you get backBest for
searchField values (name, email, phone, tax ID, address)Candidate resolved entities, ranked by match scoreFirst contact, when you do not yet hold an internal entity ID
entityA known entity IDFull resolved view: records, edges, duplicates, insightsA 360 view once your application already tracks the entity ID
entityByRecordA single source record IDThe resolved entity that record currently belongs toReconciling one system’s own record ID after a new match reshapes the graph around it

How do you write a GraphQL query against a resolved entity?

This is where GraphQL earns its place in the stack. A resolved entity can carry many linked records and repeated fields, and different consumers want different shapes back: a support dashboard wants names and open cases, a fraud model wants distinct identifiers and counts, an AI agent wants a compact summary it can reason over in one turn. GraphQL lets each caller ask for exactly the fields and aggregations it needs in a single request, instead of pulling every linked record and reshaping the response client-side.

Here is a traversal query against a resolved entity, requesting the linked records, the match edges, and an aggregation over one field through Record Insights:

query GetResolvedCustomer {
  entity(input: { id: "e_9f2a1c" }) {
    id
    score
    records {
      id
      sourceSystem
      firstName
      lastName
      email
      phone
      accountId
    }
    edges
    duplicates
    recordInsights {
      count
      emails: valuesDistinct(field: "email")
      phones: valuesDistinct(field: "phone")
    }
  }
}

A response to that query, for an entity resolved from a CRM contact, a support ticket, and a payments profile, looks like this:

{
  "data": {
    "entity": {
      "id": "e_9f2a1c",
      "score": 0.94,
      "records": [
        { "id": "rec_crm_001", "sourceSystem": "CRM", "firstName": "Jordan",
          "lastName": "Alvarez", "email": "jordan.alvarez@acme.com",
          "phone": "+1-555-0134", "accountId": "A-58213" },
        { "id": "rec_support_447", "sourceSystem": "Support", "firstName": "Jordan",
          "lastName": "Alvarez", "email": "j.alvarez@acme.com",
          "phone": "+1-555-0134", "accountId": null },
        { "id": "rec_payments_112", "sourceSystem": "Payments", "firstName": "J.",
          "lastName": "Alvarez", "email": "jordan.alvarez@acme.com",
          "phone": null, "accountId": "A-58213" }
      ],
      "edges": [
        "rec_crm_001:rec_support_447:R1_PHONE_EXACT",
        "rec_crm_001:rec_payments_112:R2_EMAIL_EXACT",
        "rec_support_447:rec_payments_112:R3_NAME_FUZZY"
      ],
      "duplicates": {},
      "recordInsights": {
        "count": 3,
        "emails": ["jordan.alvarez@acme.com", "j.alvarez@acme.com"],
        "phones": ["+1-555-0134"]
      }
    }
  }
}

Three source records, three systems, three slightly different spellings of the same person, resolved into one entity with the evidence for every connection intact. The edges array shows exactly which rule linked which pair of records, the score gives a confidence read at a glance, and recordInsights returns a distinct or aggregated view (unique emails, counts, filters, grouping) without writing that logic in your own application. The full set of filtering, statistics, and aggregation options is in our API documentation.

How do you feed resolved customer entity data into an AI agent or RAG pipeline?

An agent or a RAG pipeline that reasons over β€œthe customer” without a resolved entity graph behind it is reasoning over whichever fragment happened to surface: one support ticket, one payment record, one old CRM row, never the whole picture. Feeding resolved customer entity data into an agent means the retrieval step returns the entity, not the record, from one entity resolution graph API call.

In practice, the query above sits in front of the agent’s retrieval step: the agent asks for the entity (by ID, or via search on a name or email from the conversation), gets back the resolved records and aggregated insights, and reasons over that instead of stitching fragments together itself. Our MCP server exposes this same resolved layer as tools an assistant can call directly. It is public, stateless, and read-only, holding no customer data of its own and never writing to a Tilores instance, giving an agent typed query templates and validation against the resolved graph without a custom retrieval layer for every assistant you connect.

An LLM is genuinely useful at reasoning over ambiguous language mid-conversation, two names that might be the same person, a message referencing an account by nickname, but it should not be the system deciding whether two customer records refer to the same entity. That decision belongs in the resolution layer, upstream of the agent and its prompt, for the reasons in can LLMs be used for entity resolution.

Why query a resolved graph instead of raw records?

A generic graph database will happily store nodes and edges you hand it, and a vector index will happily return records that look similar in embedding space, but neither decides which records deserve to be connected in the first place. That decision, deterministic rules for fields that should never be ambiguous plus probabilistic matching for everything else, is the actual hard part, and it is what an entity resolution graph adds on top of generic graph or vector storage.

This layer sits next to your existing MDM, CDP, data warehouse, and KYC-AML systems rather than replacing any of them; those systems still own governance, lineage, and workflow. The resolved entity graph adds the matching and linking logic underneath, so every downstream system queries the same current, resolved view instead of running its own reconciliation. We cover how it fits alongside master data management in entity resolution technology for master data management.

FAQ

What does an entity resolution graph look like and how is it queried?

An entity resolution graph is source records as nodes, connected by match edges (each carrying the rule that created it), folded into one resolved entity view per person or business. You query it over GraphQL through three entry points: search by field values, entity by ID for the full resolved view, or entityByRecord to find the entity a given record now belongs to.

What are the nodes and edges in an entity resolution graph?

Nodes retain the original source records alongside any normalised values persisted during ingestion. Matching runs on the normalised data, while the original remains available. Edges are the connections our matcher draws once enough evidence links two nodes, each labeled with the rule that created it, so a resolved entity always traces back to its match evidence.

How do you query a resolved entity over GraphQL?

Call entity with a known entity ID, search with field values, or entityByRecord with a source record ID, and request the fields you need: records, edges, duplicates, score, and recordInsights or edgeInsights. GraphQL lets each caller shape the response instead of receiving every linked record.

What is the difference between a record and a resolved entity?

A record is one source system’s raw representation: a CRM contact, a support ticket, a payment profile. A resolved entity is the connected set of records our matcher has linked with enough confidence that they describe the same real-world person or business.

How do you feed resolved customer entity data into an AI agent or RAG pipeline?

Put the resolved entity graph in front of the retrieval step so the agent asks for the entity, not the raw record. A single GraphQL call, by entity ID or by search on a name or email from the conversation, returns the current linked records and aggregated insights behind one person.

Does querying the graph re-run entity matching every time?

No. Matching happens at ingestion. A query retrieves the entity as it currently stands, already resolved, rather than recomputing matches on the fly, which keeps query-time latency predictable for fraud checks and agent conversations.

Can an entity resolution graph replace a graph database or vector database?

It solves a different problem. Generic graph and vector stores hold whatever nodes, edges, or embeddings you give them; they do not decide which records should be connected. An entity resolution graph adds the matching logic that makes that decision, then exposes the result as a queryable graph, sitting alongside your MDM, CDP, warehouse, and KYC-AML systems rather than replacing them.

See what resolved entity data does for your business β€” and your AI.