Your GTM belongs in gitRegister
Cookbooks
CRM

CRM deduplication

Keep CRM accounts and contacts duplicate-free: audit company and person identity, run recurring deduplication plays directly on CRM models, merge safe exact matches, and route uncertain clusters to manual review.

Installation
Set up the crm-deduplication cookbook in this project. 1. If the Cargo CLI is not installed yet, follow every step in https://api.getcargo.io/INSTALL.md 2. From the Cargo project, run: cargo-ai cdk add cookbook/crm-deduplication 3. Then follow .claude/skills/crm-deduplication/SKILL.md. Stop for my approval before any paid call, and stop at "cargo-ai project plan" before deploying anything.
Paste it into Claude Code, Cursor, Codex, Gemini CLI, Devin or Replicas.

Say this to your agent

Merge duplicate HubSpot companies automatically only when the LinkedIn company ID matches, and send everything else to #crm-hygiene for a human to approve.

Illustrative output #

Fictional records, to show the shape of what comes back.

text
#crm-hygiene · Review CRM account merge into 18234
Duplicate score: 72/100
Survivor: 18234
Records to merge: 20911
Identity conflict: false
Protected ID conflict: false
Parent/subsidiary warning: false
Evidence:
18234, linkedin id none, linkedin northwind-traders, domain northwind.example, protected no, parent none
20911, linkedin id none, linkedin northwind-traders, domain northwind.example, protected no, parent none
[Approve]  [Decline]

Two records sharing a LinkedIn page and a domain, but no LinkedIn company ID, wait for a reviewer; approve merges 20911 into 18234, while decline or 24 hours of silence leaves both untouched.

What you will be asked #

InputWhy
crm_shapeLooked upSets objects, IDs, search, merge, and property behavior
selected_pathsLooked upSets which audits, resources, approvals, and pilots are in scope
action_costsLooked upEach path’s handoff must disclose its current maximum cost
account_evidenceLooked upGrounds the account policy in current CRM records
account_policyAskedControls every company candidate and unattended merge
account_reviewAskedGives manual company decisions an accountable owner
account_authorizationsAskedSeparates deployment from company-record mutation
contact_evidenceLooked upGrounds the contact policy in current CRM records
contact_policyAskedControls every person candidate and unattended merge
contact_reviewAskedDefines the non-automatic contact path
contact_authorizationsAskedSeparates deployment from contact-record mutation

Done when #

  • the operator selected account deduplication, contact deduplication, or both
  • each selected path has its own reconciled audit, policy approval, disabled-build approval, priced pilot approval, and final report
  • the selected resources use one CRM connector, a Slack connector for every path with active review, object-specific CRM models, and no candidate or staging model
  • generated types confirm selected search and merge payloads plus Human Review for paths that use it
  • node --import tsx evals/contract.mjs, cargo-ai cdk types, cargo-ai cdk check, and cargo-ai cdk plan pass after adaptation
  • every selected play is disabled, noConcurrency, and limited to 15 CRM rows before its pilot
  • the account audit reconciles company identifier coverage, mutually exclusive classes, conflicts, survivor evidence, and proposed merge IDs to the source total
  • deduplicate_accounts runs directly on crm_accounts and searches companies live
  • its graph contains preparation, native Scoring, the guarded Branch, native Human Review, and CRM merges only on automatic or approved paths
  • automatic merge requires exact shared LinkedIn company ID and every account conflict guard
  • every account survivor and absorbed company ID is verified in the CRM
  • the contact audit reconciles source totals, person identifier coverage, unique candidate clusters, conflicts, survivor evidence, and proposed merge IDs, and reports overlapping matched classes separately
  • deduplicate_contacts runs directly on crm_contacts and performs both direct and transitive live searches
  • its graph contains preparation, native Scoring, the guarded Branch, optional native Human Review, and CRM merges only on automatic or approved paths
  • automatic merge requires one approved high-confidence class plus every global contact guard
  • the merge is the final CRM write, and every contact survivor and absorbed ID is verified in the CRM

What it costs #

Immediately before each selected path’s preview, run cargo-ai connection integration get <crm> and, when review is enabled, cargo-ai connection integration get slack. Record the CLI version, lookup time, action slugs, and current cost metadata. The repository does not hard-code pipeline action prices.

Path 1: Account cost

Price the account search, automatic or approved company merge, and Human Review actions against the exact account population. Approval authorizes only the company clusters shown for that pilot.

Path 2: Contact cost

Price both contact searches, automatic or approved contact merge, and enabled Human Review actions against the exact contact population. Approval authorizes only the person clusters shown for that pilot.

Enabling either recurring schedule is a separate final approval after that path’s pilot passes.

This pipeline has two independent paths. Deploy account deduplication, contact deduplication, or both. Each path has its own CRM model, identity audit, matching and survivor policies, disabled play, cost preview, pilot approval, and acceptance checklist. Both paths search the live CRM before each merge decision and avoid candidate or staging models.

Path 1: Account deduplication #

The account path searches companies by LinkedIn company ID, LinkedIn company page, and non-generic domain. Only an exact shared LinkedIn company ID without identity, protected-ID, or parent-subsidiary conflict can merge automatically. Every other candidate reaches Human Review.

flowchart LR
  account["CRM account"] --> search["Search live companies"]
  search --> evidence["Normalize, score, select survivor"]
  evidence --> exact{"Exact LinkedIn ID<br/>and no conflict?"}
  exact -->|yes| merge["Merge automatically"]
  exact -->|no| review["Human Review"]
  review -->|approved| merge
  review -->|declined or timeout| stop["Keep separate"]

Account resources

ResourceKindPurpose
crm_accountsModelDirect CRM company extract
deduplicate_accountsPlaySearch, score, review, and merge duplicate companies
infra/scripts/accounts/CodeNormalize company identity, cluster, rank, and summarize

Company name is excluded from candidate generation and scoring. The deterministic survivor policy protects business IDs and prefers customer and commercial history.

Path 2: Contact deduplication #

The contact path searches people by LinkedIn person ID, normalized LinkedIn URL, exact email, and exact stored phone. It expands direct matches with a second live search so high-confidence identity chains form one cluster. The second search and in-cluster comparison use normalized phone values derived from records already retrieved.

flowchart LR
  contact["CRM contact"] --> direct["Search direct identity keys"]
  direct --> transitive["Expand keys and search again"]
  transitive --> evidence["Normalize, score, select survivor"]
  evidence --> safe{"Approved identity class<br/>and no global conflict?"}
  safe -->|yes| merge["Merge automatically"]
  safe -->|no, review enabled| review["Human Review"]
  safe -->|no, review disabled| stop["Keep separate"]
  review -->|approved| merge
  review -->|declined or timeout| stop

Contact resources

ResourceKindPurpose
crm_contactsModelDirect CRM contact extract
deduplicate_contactsPlaySearch, score, review, and merge duplicate contacts
infra/scripts/contacts/CodeNormalize person identity, expand clusters, rank, and review

Exact LinkedIn person ID, conflict-free LinkedIn URL, conflict-free non-generic email, and conflict-free transitive chains are the automatic classes. A LinkedIn conflict, generic or shared email, or phone-only match always leaves the automatic path. The native merge is the final CRM write. Normalization and enrichment belong in a separate workflow.

Install and adapt #

This folder is a worked HubSpot example. Salesforce and Attio require adapting the connector, extracts, record IDs, search and merge actions, and property slugs together. Keep one CRM shape across selected paths.

This pipeline requires the cargo-cdk authoring skill:

shellscript
npx skills add getcargohq/cargo-skills --skill cargo-cdk

From a Cargo CDK project:

shellscript
cargo-ai cdk add cookbook/crm-deduplication

Before planning or deploying:

  1. Reconcile the default CRM connector and any active Slack review connector with compatible project resources.
  2. Choose the account path, contact path, or both. Remove unselected model and play files, then update the contract’s exact resource inventory, connector assertions, and object-specific assertions.
  3. Audit and approve each selected path independently.
  4. Resolve each selected path’s object-specific policy inputs, identity fields, survivor policy, and active review destination.
  5. Typecheck, inspect the compiled graph, and deploy only approved plays disabled.
  6. Request separate approval for each exact 15-row maximum merge-capable pilot.

Nothing in this folder deploys, runs, or touches customer data by itself.

Verify #

shellscript
npm run typecheck
node scripts/check-pipelines.mjs
node --import tsx crm-deduplication/evals/contract.mjs

Use the selected sections in the audit contract, configuration guide, runbook, and two-path acceptance checklist.

File map #

PathPurpose
SKILL.mdRoute selection, approvals, invariants, and completion
infra/connectors/Shared uncached CRM and Slack connectors
infra/folders/Shared skill-owned model and play folders
infra/scripts/common/Record reading and value helpers both paths share
infra/models/crm-accounts.tsAccount path CRM extract
infra/plays/deduplicate-accounts.tsAccount path workflow and trigger
infra/scripts/accounts/Company identity, clustering, survivor policy, and evidence
infra/models/crm-contacts.tsContact path CRM extract
infra/plays/deduplicate-contacts.tsContact path workflow and trigger
infra/scripts/contacts/Person identity, clustering, survivor policy, and evidence
references/Object-specific audit, configuration, and run instructions
evals/acceptance.mdIndependent acceptance checklists for both paths
evals/contract.mjsExecutable graph and safety contract

Give your agents a runtime

Bring the agents you have.Start free, deploy in one command.