skip to content

Match Identities

Triage the duplicate-and-match queue to confirm who's who and clear false positives.

What it does

noticed is always proposing that two records are the same person across sources: an email-only contact ↔ their LinkedIn or GitHub, or two near-duplicate people. This skill walks that review queue with you and resolves each one:

  • Confirm a match → merges the two records into one (the survivor keeps your notes and history).
  • Mark as different people → a durable "not the same person" so noticed stops suggesting it.
  • Dismiss → a soft "not now" for the ones you're unsure about.

It reads the queue freely and acts deliberately: obvious duplicates merge in a couple of calls, false positives (like a shared hello@company.com matched to one person) get cleared in a batch, and anything ambiguous waits for your call. When noticed has counter-evidence, it won't merge until you've seen the conflict and confirmed.

When to use it

  • "Go through my proposed matches" / "approve the identity suggestions"
  • "Any duplicate contacts I should merge?"
  • "These matches for [person] look wrong, clear them"
  • Tidying who's-who after a big import (email contacts matched to LinkedIn/GitHub)

How it works

It wraps the noticed identity tools: list_identity_matches (the pending review queue, with a status filter), then accept_identity_match, mark_different_people, and dismiss_identity_match to resolve each row, keyed by the match's candidate_id, a person pair, or the profile_a/profile_b pair for the email→person queue. It respects noticed's safety gates: a contested merge returns the conflicting evidence for you to review before it proceeds, and a verified-sign-in conflict is routed to an admin.

Prompts to try

  • Go through my proposed matches
  • Are there any duplicate contacts to merge?
  • Review the identity matches for Filipe and clear the wrong ones
  • Clean up the Apple matches, they look wrong

Use this skill

Every noticed skill ships together. Install the pack once, then ask your agent to use this skill by name.

Install all noticed skills
View SKILL.md

match-identities

noticed continuously proposes that two records are the same human across sources: an email-only contact ↔ their LinkedIn/GitHub, or two near-duplicate people. This skill walks that review queue with the user and resolves each one: accept (they're the same → merge), mark different (they're not → durable disconnect), or dismiss (not sure / not now → soft).

flow

  1. Pull the queue. list_identity_matches({ status: "pending" }). Each row has:

    • identity_a / identity_b: the two sides (name + person_id; the person_id is null when a side is an email-only contact with no person yet).
    • confidence, channel (how it was proposed: email_equality, company_overlap, external_search, name_fuzzy, …), and a reason.
    • the handle to act on: exactly one of:
      • candidate_id (a proposed merge between two real people), or
      • profile_a / profile_b (the review queue's usual case: an email:<addr>github:<id>/linkedin:<vanity> pair, where one side has no person and candidate_id is null).

    Scope to one person with person when the user is reviewing a specific cluster ("review the matches for Filipe"). Pass a bigger limit (max 100) to see the whole queue.

  2. Judge each row: same person or not? Lead with the evidence in reason and the two names/identifiers. Decide per row:

    • Clearly the same (matching name + handle/email, or email_equality) → a candidate for accept.
    • Clearly different: different names/people, OR a shared/role email matched to one individual (hello@, contact@, general@, team@, info@ at a company → almost always a false positive from company_overlap) → mark different.
    • Genuinely unsuredismiss (or leave it; don't guess).

    When a row is ambiguous (a thin profile, a first-name-only email, a handle collision), research before deciding (see research before deciding below) rather than guess.

  3. Act, passing whichever handle the row carries:

    • Accept (merge): accept_identity_match({ candidate_id }) OR accept_identity_match({ profile_a, profile_b }) OR accept_identity_match({ person_a, person_b }). The first/target survives; the user's own person always survives.
      • If noticed has counter-evidence it does not merge, it returns needs_confirmation: true with the conflicting evidence and a confirmation_token. Relay that evidence to the user. Only if they review it and still say it's the same person, call again with the confirmation_token. Never pre-set the token.
      • admin_required: true (a verified sign-in says the identity belongs to someone else) → tell the user an admin must resolve that one on the dashboard; don't try to force it.
    • Mark different (durable): mark_different_people({ profile_a, profile_b }) (or candidate_id / person_a, person_b). Records a lasting "not the same person" so noticed stops suggesting it. Reversible by an admin.
    • Dismiss (soft): dismiss_identity_match({ … }). Stops it surfacing for now; it may return later if new evidence appears.
  4. Batch sensibly. A cluster of obvious same-company false positives (e.g. a dozen *@apple.com emails matched to one person) → mark each different, then report the count ("cleared 11 false positives for Filipe Lopes"). Don't ask per-row for the obvious ones; do ask before any merge that isn't a clear duplicate.

  5. Close out. Summarize what changed: merged / marked-different / dismissed counts, and offer to re-pull status: "pending" to confirm the queue shrank.

research before deciding (ambiguous rows)

Some rows can't be judged from the queue alone: a bare profile with no name, a first-name-only email (josh@, mario@), a generic LinkedIn vanity, or a famous GitHub handle. Don't guess on these. Research first, then decide; for the ones that stay uncertain, the user usually knows, so ask.

  1. Pull the in-network side's dossier. One side of the pair is usually already a person (identity_b.person_id). get_person({ person_id }) returns their headline, company, tags, and sometimes a known email in the notes. If the candidate email's domain or surname matches the dossier, it's the same person; if the dossier's company or surname conflicts, they're different.
  2. Identify a bare profile from the open web. A github:<id> resolves to a real account at https://api.github.com/user/<id> (login, name, company, bio, blog). For a linkedin:<vanity>, web-search the vanity or the name plus a company. Use this to learn who the profile actually belongs to before matching an email to it.
  3. Show the user a batch (about 10 at a time) in a table: unmatched side, who the profile really is, same person?, and your recommendation. Act on the clear ones and let the user settle the rest.

Patterns that look like matches but usually aren't (research, don't auto-accept):

  • Handle collision. A famous, low-numbered GitHub account whose login equals a generic first-name email alias (josh@company-a and josh@company-b both matched to GitHub josh) is almost always different people.
  • Same company, different surname. maria.adriana.cardoso@acme.com matched to Maria Inês Fonseca is a colleague, not her → mark different.
  • Too-thin target. A LinkedIn/GitHub record with no name or headline can't be confirmed; don't merge on a first-name match alone.

The inverse is also true: research often CONFIRMS a non-obvious real match, then accept. The email's surname is the person's; a personal-domain address (hello@theirname.com); a founder's name@theircompany.com; or the dossier already lists that exact email.

fill profile gaps (add a profile)

The flip side of the queue: contacts noticed has no profile for: email-only people with no LinkedIn/GitHub. They can't be matched until noticed knows their profile, so you give it one.

  1. List the gaps. list_profile_gaps() → email-only contacts (person_id, name, email, and any URL already submitted). This is the "Add a profile" list.
  2. Find their profile, network first. Before searching the open web, search noticed itself: search_people (or resolve_person) on the contact's name or email handle. They're often already in your network with a LinkedIn/GitHub attached, in which case it's a duplicate to resolve (accept / mark different) and you already have the URL. Only when they're NOT already in noticed do you go to the open web for their LinkedIn/GitHub.
  3. Attach a URL. Once you have it, add_profile_to_person({ person_id, url }) with a linkedin.com/in/… OR github.com/… URL. Two outcomes:
    • applied → that profile was already in noticed → matched + merged into one record.
    • queued → noticed couldn't merge it outright: it'll fetch + match the profile (if it's new), or route a conflict to a quick admin review. Tell the user it'll resolve shortly (and may need a quick review).
  4. Use this to ENRICH a thin contact, not to merge two existing records (that's accept_identity_match / suggest_identity_match). One URL per call (LinkedIn XOR GitHub).

the rule

  • Reads are free; writes are deliberate. Pull and judge the whole queue freely. Merging two records is a structural change, so only do it for clear duplicates without asking, and ask first for anything ambiguous.
  • Respect the confirmation gate. If accept returns needs_confirmation, show the evidence and get a real yes before passing the token back. If it's admin_required, route to an admin; don't force it.
  • Shared/role emails are not people. hello@, contact@, support@, team@, general@ matched to an individual is a false positive → mark different.
  • Mark-different is durable; dismiss is soft. Use mark-different for "these are genuinely different people"; dismiss for "not now / unsure".
  • Everything is reversible by an admin, but say what you did, plainly.

tool needs

  • noticed: list_identity_matches (the queue), accept_identity_match, mark_different_people, dismiss_identity_match (the actions), list_profile_gaps + add_profile_to_person (fill missing profiles), and search_people / resolve_person / get_person (find someone already in your network before going to the open web + context when a row is ambiguous).

explicitly NOT in scope

  • Adding people, logging interactions, or editing notes/tags (that's other skills).
  • Guessing on ambiguous pairs: dismiss or ask instead of forcing a merge.
  • Overriding a verified-sign-in (admin_required) conflict: that's admin-only.

examples

Review the whole queue. Input: go through my proposed matches Action: list_identity_matches({ status: "pending", limit: 100 }) → group by the named side, judge each, then act: accept the clear duplicates, mark-different the role-email/company-overlap false positives, dismiss the unsure ones. Report counts.

Clear a false-positive cluster. Input: the Apple matches are all wrong Action: for each pending row whose email is *@apple.com matched to the same person, mark_different_people({ profile_a, profile_b }). Close with "cleared 11, they were a company-overlap false positive."

Confirm a real duplicate. Input: diogoribeiro@redlightsoft.com is the same as Diogo Freitas Ribeiro Action: find the row, accept_identity_match({ profile_a, profile_b }). If it returns needs_confirmation, relay the evidence; merge with the token only after the user confirms.

Hit the confirmation gate. Accept returns needs_confirmation with "their commit email points to a different GitHub account" → show that to the user. They still say same person → call accept_identity_match again with the returned confirmation_token.

Add a profile to a thin contact. Input: erick@gomry.com is github.com/erickz Action: find the contact via list_profile_gaps() (or search_people), then add_profile_to_person({ person_id, url: "https://github.com/erickz" }). If it comes back queued, tell the user noticed will fetch + match it shortly.

Work the gap list. Input: any contacts missing a profile? Action: list_profile_gaps() → for each, search the network first (search_people on the name/handle) in case they're already in noticed; then offer to attach a LinkedIn/GitHub URL (from the network, or failing that the open web) for any the user can provide.

your agent is only as good as its memory

These skills run on noticed, the relationship memory behind your agent. Request access and bring your network with you.

Request access