The moment a nail tech, a colorist, or a lash artist connects their booking system, a flood of customer records arrives. And every single one poses the same quiet question: is this a new person — or someone we already remember? A regular the pro hand-added at the chair last week. A paper client from before they ever went digital. The same human, arriving through a different door.
Get that question wrong in one direction and you fracture a regular's history across duplicate records — annoying, fixable. Get it wrong in the other direction and you do something genuinely harmful: you staple one person's private history onto a different human. Their notes, their conversations, the things they told their stylist in confidence — now surfacing under someone else's name. For a product whose entire job is remembering people accurately, there is no worse failure.
This is the same-person problem, and it's deceptively deep.
Why it's hard
The naïve instinct is "match on email." It falls apart immediately, in both directions.
Names are slippery. Nat is Natalie. Mike is Michael. A maiden name becomes a married name. A full name gets packed into one field, or split wrong, or transliterated, or simply mistyped. "Same name" is neither necessary nor sufficient for "same person."
Identifiers are shared. This is the part that breaks every simple rule. Families and front desks share contact information constantly. A mother books her daughter's lash fill under her own email. Couples share a phone number. A stylist puts their own email on file for three different regulars who never check email. So an exact email match does not mean the same human — it might be a mother and her two daughters, all on one inbox.
"Same email" is not "same person." It's "same household, probably." Those are very different claims, and the gap between them is where people get hurt.
The classic tools all stumble here. Exact-match rules miss Nat/Natalie. Fuzzy string matching has no idea that a Jr and a Sr are deliberately different people, or that families share phones on purpose. A trained classifier needs labeled identity pairs we don't have at this scale — and when it's wrong, it can't tell you why. None of them carry the thing a human receptionist uses instinctively: world knowledge.
The stakes decide the shape
Before any code, we wrote down the asymmetry, because the whole design bends around it:
- A wrong match leaks one person's life into another's profile. Catastrophic.
- A wrong "new person" creates a duplicate row that can be merged later in one tap. Cheap.
- An honest "I can't tell" costs the provider a single tap to decide. Trivial.
When the costs are that lopsided, the rule writes itself: when in doubt, never merge. Bias the entire system toward "new person" or "ask a human," and reserve "match" for when the evidence is genuinely strong. Everything that follows is in service of that one principle.
The shape: a deterministic net, judgment in the middle, safe by default
1. A deterministic shortlist does the recall
The first stage is cheap, code-only, and deliberately broad. We scan the existing roster for any record that shares a real signal with the incoming one — a matching contact identifier, an overlapping name token. Anything with a plausible connection makes a shortlist. Its only job is to decide who is worth a closer look — not who matches. And critically: if nothing connects — no shared identifier, not even a shared name fragment — it's trivially a new person, and we stop right there. No further thought required, no model call spent.
2. Canonicalization is code's job, not the model's
Gmail ignores the dots in an address and everything after a +. Phone numbers arrive in a dozen formats with and without country codes. Whether two strings refer to the same mailbox or the same line is deterministic knowledge — so we normalize it in code, before anything else looks at it. The model should never have to know the gmail-dots trick. We want it spending its intelligence on the human question, not on string trivia a regex can settle.
3. A reasoning model sits in the judgment seat
For the records that survive the shortlist, we hand a language model the full incoming record alongside the candidates and ask it to decide the way a seasoned receptionist would — someone who's seen ten thousand customers and knows how names and families actually behave. It returns one of three answers: this is that client, this is someone new, or I genuinely can't tell.
The reframe that made this click — the single idea that dissolves most of the hard cases — is this:
The identifier establishes that there's a connection. The name decides who on that connection.
A shared phone number doesn't tell you a booking is from a particular person. It tells you the booking is linked to a household. The name then picks which member. A mother and son on one email aren't a paradox — the incoming name resolves the ambiguity the identifier created. Once you stop asking "do the identifiers match?" and start asking "which human on this shared contact is this?", the whole problem reorganizes.
4. Hard floors the model is not allowed to cross
Judgment is powerful and judgment drifts, so we fence it with a small set of bright lines the model can never step over — the encoded form of "things a careful shop owner simply knows":
- Never merge on a name alone with no shared identifier. Different people share names, even unusual ones.
- A generational suffix — Jr, Sr, II, III — means presumed-different people. That's a parent and a child on the family's shared phone, not one person.
- An exact identifier match never overrides a clearly different name. A different human on a shared or even a unique contact is a different client — full stop.
Beyond those floors, we don't enumerate rules — we hand over the full context and trust the model's world knowledge. It already knows Nat is Natalie and that maiden names change; making it re-derive that from a rulebook would only make it worse. Fewer rules, more context. The floors aren't there to do the thinking — they're there to catch the rare case where the thinking goes somewhere a human never would.
5. Vote, then default to safe
Borderline identity cases sit right on the decision boundary, where a single model sample can wobble. So we don't trust one read — we ask a few times in parallel and take the majority. No clear majority means the case is genuinely undecidable, and undecidable resolves to "I can't tell." That answer isn't a failure; it's the safe sink. It never merges silently. It surfaces a clean side-by-side comparison and lets the provider settle it with one tap — exactly the cheap, reversible outcome the stakes call for.
6. Fail safe, every time
The model times out. The API has an outage. It returns malformed output. It names a candidate that wasn't even on the shortlist. Every one of those — every conceivable failure — collapses to the same safe answer: "I can't tell, ask the human." An identity question is never permitted to crash an incoming webhook, and it is certainly never permitted to guess when it's confused.
Why we think this is a different way to connect identities
Entity resolution is old. The usual ingredients are deterministic rules, fuzzy string distance, and sometimes a trained classifier — none of which carry world knowledge, and none of which can explain themselves to the person who has to trust the result.
Putting a reasoning model in the precision seat changes what's possible: it resolves identity the way a person would, weighing nicknames and family structure and the full messy context of a real record — and it writes down a sentence of reasoning for every call it makes. But a probabilistic model, alone, has no business making an irreversible decision about a real human's private history. So the novelty isn't "use an LLM." It's the shape around it: a deterministic net that bounds what the model may even consider, hard floors it cannot cross, a vote that smooths its noise, a safe default for when it abstains, and a human one tap away whenever it's unsure.
The model brings judgment. The system brings safety. Neither would be trustworthy without the other.
The lesson
The general pattern is one we keep reaching for whenever a probabilistic model has to touch a high-stakes, irreversible decision: don't hand the model the whole problem, and don't keep it out either. Surround it. Put deterministic recall in front of it to bound the inputs. Put bright-line floors around it to bound the outputs. Put a vote underneath it to quiet the noise. Put a safe default beneath that for when it abstains, and a human beside it for when it's unsure. Then let it do the one thing it's uniquely good at — judgment with world knowledge — inside a box engineered so that even its worst day can't do real harm.
That's how you let a model decide whether two records are the same person — without ever betting a real person's privacy on it being right.
More from the team → Patron Engineering. Related reading: why we never let a model invent a memory. Building Patron with us? Get in touch.