crm-enrichment
Skill2 starsChanged 27 days agoMITView on GitHub
Clean up and enrich a messy CRM: fill the missing identifiers on accounts and contacts first (LinkedIn, domain, email), write them back into blank fields only, then deduplicate on those identifiers with crm-deduplication. Keeps records filled and refreshes them when they go stale. Triggers: "keep our CRM accounts filled", "keep our CRM contacts filled", "nobody refreshes the company records", "nobody refreshes the contact records", "enrich my CRM", "refresh stale firmographics", "every new CRM company", "every new CRM contact", "contacts are missing emails, LinkedIn URLs, or titles". HubSpot, Salesforce, Attio, Cargo project. Skip when: the records are not in a CRM. A supplied company list is enrich-company-data, a supplied LinkedIn URL list is enrich-linkedin-profile, and a one-time email lookup is find-work-email.
- Reads credentials
- Installs packages
In this repository46 other files it gives its agents.
- llms.txtllms.txt
- account-scoringSkill
- agentic-engagementSkill
- create-gtm-skillSkill
- apollo-to-cargoSkill
- ask-cargoSkill
- build-tam-listSkill
- call-captureSkill
- clay-to-cargoSkill
- crm-deduplicationSkill
- enrich-company-dataSkill
- enrich-linkedin-profileSkill
- expansion-signalsSkill
- extract-sales-navigator-searchSkill
- find-b2b-leadsSkill
- find-companies-using-techSkill
- find-linkedin-urlSkill
- find-portfolio-companiesSkill
- find-stakeholdersSkill
- find-work-emailSkill
- goal-coverageSkill
- inbound-qualificationSkill
- linkedin-contentSkill
- meeting-prepSkill
- monitor-buying-signalsSkill
- new-hire-detectionSkill
- next-step-trackerSkill
- readiness-scoringSkill
- research-accountSkill
- score-leadsSkill
- social-listeningSkill
- stalled-deal-nudgeSkill
- standupSkill
- tam-buildingSkill
- track-funding-roundsSkill
- track-job-changesSkill
- verify-email-listSkill
- visitor-identificationSkill
- waterfall-enrichmentSkill
- web-captureSkill
- website-auditingSkill
- website-buildingSkill
- weekly-planningSkill
- win-loss-reviewSkill
- workspace-healthSkill
- zoominfo-to-cargoSkill
The file
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 6 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Very specific: line 88
Every write fills blanks only, and cargo_last_enriched_at is stamped only after a successful write, so a contact where nothing resolved, …
- Very specific: line 125
Verify the live CRM company schema and both LinkedIn company actions.
- Distinctive: line 139
No paid call or CRM write occurs during either audit.
- Distinctive: line 199
Keep every optional provider field as its own decision row.
- Distinctive: line 211
Fill approved blanks only: Every business-field mapping uses a CRM-native blank-only flag or an equivalent fresh-read guard.
- Very specific: line 230
One contact play and three tool resources: Keep contact orchestration in enrich_contacts and use only Find Email, Find LinkedIn Profile …
- Distinctive: line 234
No unresolved write: When email cannot resolve to LinkedIn, stop without custom enrichment, CRM write, or successful freshness.
- Distinctive: line 237
No contact creation or movement tracking: Update only the triggering contact.
- Distinctive: line 240
Run the compiled graph contract from this skill's folder after every adaptation:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CRM enrichment
State: to-be-approved. This pipeline has not been deploy-verified against a live workspace.
Review cargo-ai project plan, the compiled graph, and the live connector schemas before deployment.
The outcome
Choose the account path, contact path, or both. Each selected path has its own model, tool contract, play, field approval, cost preview, pilot, and acceptance criteria. Approval for one path does not authorize the other.
Path 1: Account enrichment
enrich_accounts keeps approved company identity and firmographic fields filled. It calls
account_enrichment, selects the LinkedIn company route or domain fallback, and writes approved
blanks to the triggering CRM account. A row takes only one provider route.
The starting HubSpot destinations are linkedin_company_id, name, domain, website,
linkedin_company_page, and numberofemployees.
Path 2: Contact enrichment
enrich_contacts is one play calling three gated tool resources:
- Cargo-native Find Email runs only when LinkedIn is present and email is blank.
- Cargo-native Find LinkedIn Profile from Email runs only when email is present and LinkedIn is blank.
- Custom Contact LinkedIn Enrichment runs only after a LinkedIn URL is available.
| Starting identifiers | Calls | Outcome |
| Email and LinkedIn | Contact LinkedIn Enrichment | Fill approved LinkedIn identity and role blanks |
| LinkedIn only | Find Email, then Contact LinkedIn Enrichment | Fill email and approved LinkedIn blanks |
| Email only | Find LinkedIn Profile from Email, then Contact LinkedIn Enrichment | Stop without a write if no profile resolves |
| Neither | None | Stop without a write |
The starting HubSpot destinations are email, linkedin_person_id, linkedin_profile_url, and
jobtitle. This path contains no customer split, movement detection, relationship mutation, note
creation, or alerting.
Shared write contract
Both plays run directly on their CRM extracts and match the CRM record ID. The custom tools own
provider access and have no CRM access. The plays own every CRM write. Business fields fill blanks
only. cargo_last_enriched_at and cargo_enrichment_status are stamped only after a successful
write.
The checked example targets HubSpot companies and contacts by hs_object_id. Adapt the connector,
extracts, record IDs, write action, and blank-only behavior together for Salesforce or Attio.
Example
Keep our HubSpot contacts filled: find the missing email or LinkedIn profile, then fill job title, but never overwrite a value a rep already entered.
Illustrative output, fictional records:
| Contact (hs_object_id) | Started with | Fields filled | cargo_enrichment_status |
| Priya Raman (51007) | LinkedIn only | email priya.raman@northwind.example, linkedin_person_id, jobtitle VP RevOps | succeeded |
| Tomás Ortega (51022) | Email only | linkedin_profile_url, linkedin_person_id, jobtitle Head of Sales Ops | succeeded |
| Mei Lin (51040) | Email and LinkedIn | jobtitle left as entered; linkedin_person_id filled | succeeded |
| Sam Hale (51055) | Email only | nothing: no profile resolved | (not stamped) |
Every write fills blanks only, and cargo_last_enriched_at is stamped only after a successful write, so
a contact where nothing resolved, like Sam Hale, is never marked enriched.
Put it in your project
Install the required authoring skill first. If cargo-project is absent, run:
npx skills add getcargohq/cargo-skills --skill cargo-projectThen read .agents/skills/cargo-project/SKILL.md and follow its state, plan, and deployment rules.
From the Cargo project, install this example with:
cargo-ai project add cookbook/crm-enrichmentOr start a project with:
cargo-ai project init <dir> --cookbook crm-enrichment
cd <dir>
npm installReconcile default connectors and models with existing project resources. Do not deploy duplicate
slugs. Append environment requirements to the project .env.example; never overwrite it.
Choose the account path, contact path, or both before the audit. Follow the selected path sections
in references/audit.md, references/configure.md,
and references/run.md. Walk the matching path in
evals/acceptance.md. If both paths are selected, both must pass separately.
Account path setup
Verify the live CRM company schema and both LinkedIn company actions. Approve the account field
contract before adapting crm_accounts, account_enrichment, or enrich_accounts.
Contact path setup
Instantiate the two Cargo-native tools and replace:
REPLACE-WITH-FIND-EMAIL-TOOL-UUIDREPLACE-WITH-FIND-LINKEDIN-PROFILE-FROM-EMAIL-TOOL-UUID
Confirm their live contracts. This example expects Find Email to accept linkedin_url,
first_name, and last_name and return email. It expects Find LinkedIn Profile from Email to
accept email and return linkedin_url.
No paid call or CRM write occurs during either audit. Deploy selected resources disabled only after the matching field contract is approved. Run a paid pilot only after the operator approves that path's priced population.
What you will be asked
Derive what can be discovered before asking the operator. Ask only for decisions that change the selected path's field contract, target population, cadence, or spend.
Shared inputs
| Input | Kind | How it is answered | Why it matters |
crm_shape | derived | Inspect authenticated connectors, selected extracts, and live CRM properties | Determines objects, record IDs, destinations, and write behavior |
selected_paths | derived | Read whether the request names account enrichment, contact enrichment, or both | Sets which resources, audits, approvals, and pilots are in scope |
Path 1: Account inputs
| Input | Kind | How it is answered | Why it matters |
account_provider_schema | derived | Inspect both live LinkedIn company actions | Prevents guessed output paths and types |
account_field_contract | asked | Review one decision row per company provider property | Controls every company mutation |
account_population | derived | Count mutually exclusive LinkedIn URL and domain fallback routes | Sets account scope and spend |
account_run | asked | Review disabled resource links, exact population, and maximum spend | Authorizes only the account pilot |
Path 2: Contact inputs
| Input | Kind | How it is answered | Why it matters |
contact_tool_contracts | derived | Inspect profile enrichment and both instantiated Cargo-native tools | Prevents guessed inputs, output paths, and prices |
contact_field_contract | asked | Review one decision row per contact provider property | Controls every contact mutation |
contact_population | derived | Count both-identifiers, LinkedIn-only, email-only, and neither routes | Sets contact scope and spend |
contact_run | asked | Review disabled resource links, exact population, and maximum spend | Authorizes only the contact pilot |
What you can change
Shared variations
| Variation | When it is right | How | What it costs |
crm_shape | The consumer uses Salesforce or Attio | Replace the connector, selected extracts, object names, record IDs, write action, and blank-only guard together | Generated types and write semantics must be reverified |
refresh_cadence | The default window does not fit the data policy | Change freshness independently for each selected path and report repeat-spend impact | Faster cadence repeats provider charges more often |
Path 1: Account variations
| Variation | When it is right | How | What it costs |
account_fields | The approved company contract differs from the starting fields | Change account tool outputs, CRM mappings, and transformations together | Every added field expands schema and write review |
account_eligibility | Only a governed company subset should be enriched | Intersect the account play filter with approved lifecycle, tier, or ownership conditions | Narrower scope reduces coverage and paid calls |
account_refresh_policy | Approved populated company fields must be refreshed | Remove blank-only protection only for approved fields and compare against a fresh CRM read | The provider snapshot can replace CRM-authoritative values |
Path 2: Contact variations
| Variation | When it is right | How | What it costs |
contact_fields | The approved person contract differs from the starting fields | Change custom tool outputs, contact mappings, blank filters, and transformations together | Every added field expands schema and write review |
contact_eligibility | Only a governed contact subset should be enriched | Intersect the contact play filter with approved lifecycle, tier, or ownership conditions | Narrower scope reduces coverage and paid calls |
native_tool_contracts | The deployed native tools expose different schemas | Update each toolRef, call payload, and result access from the verified live schema | A guessed path can create paid calls with no usable result |
profile_provider | The workspace uses an equivalent approved profile action | Replace the provider call inside contact_linkedin_enrichment and remap its output | Coverage, fields, and unit price change |
Keep every optional provider field as its own decision row. State the live unit price and expected fill rate before recommending a paid route.
What should not change
Shared invariants
- Each path runs on its direct CRM extract. The account play matches the company record ID and the contact play matches the contact record ID. A second identity system makes writes look successful while nothing lands.
- Tools enrich and plays write. Custom tools contain no CRM access. Plays do not duplicate provider connector actions.
- Fill approved blanks only. Every business-field mapping uses a CRM-native blank-only flag or an equivalent fresh-read guard. Do not overwrite an authoritative CRM value without an explicit field-level refresh approval.
- Freshness follows a write. Stamp
cargo_last_enriched_atandcargo_enrichment_status=succeededonly on successful CRM write paths. - Selected plays deploy disabled and use
noConcurrency. Removing either expands an unapproved pilot.
Path 1: Account invariants
- LinkedIn first, domain fallback. One account takes exactly one provider route. Rows without either identifier make no paid call.
- One account tool and one account play.
account_enrichmentowns provider routing;enrich_accountsowns the only company write. - Account eligibility does not require a blank destination. Populated stale records remain eligible for an explicitly approved refresh policy.
Path 2: Contact invariants
- One contact play and three tool resources. Keep contact orchestration in
enrich_contactsand use only Find Email, Find LinkedIn Profile from Email, and Contact LinkedIn Enrichment. - Real branch gating. Find Email runs only for LinkedIn-only rows. Find LinkedIn Profile from Email runs only for email-only rows. Contact LinkedIn Enrichment runs only with a LinkedIn URL.
- No unresolved write. When email cannot resolve to LinkedIn, stop without custom enrichment, CRM write, or successful freshness.
- Null-safe blank filters. Pair
isNullandisEmptyfor every contact string destination. - No contact creation or movement tracking. Update only the triggering contact. Do not create, merge, move, or alert on contacts in this path.
Run the compiled graph contract from this skill's folder after every adaptation:
node --import tsx evals/contract.mjsDone when
Complete only the selected paths. If both were selected, both lists must pass.
Path 1: Account completion
- The approved account field contract names every destination, type, transformation, and write policy.
- The plan contains one account model, one account enrichment tool, and one disabled account play.
- The compiled graph proves one mutually exclusive company provider route and one play-owned CRM write.
- The account write matches the audited company record ID and preserves populated business values.
- The account write probe and approved pilot pass.
- The account report includes route, processed, written, skipped, failed, fill-rate, spend, and direct-link evidence.
Path 2: Contact completion
- The approved contact field contract names every destination, type, transformation, and write policy.
- Native tool UUIDs and live schemas are verified and placeholders are gone.
- The plan contains one contact model, exactly three contact tool targets, and one disabled contact play.
- The compiled graph proves both native calls are gated and every custom enrichment call has a LinkedIn URL.
- Unresolved and identifier-free rows stop without custom enrichment, CRM write, or successful freshness.
- The contact write probe and approved pilot pass.
- The contact report includes route, processed, written, unresolved, skipped, failed, fill-rate, spend, and direct-link evidence.
The complete evidence checklist is in evals/acceptance.md.
What it costs
Fetch live prices from the workspace before every preview. Cost only the selected paths. A failed CRM write can cause provider calls to be billed again on retry, so probe write capability before a batch.
Path 1: Account cost
Quote both LinkedIn company actions. Multiply the mutually exclusive LinkedIn URL and domain fallback populations by their respective live unit prices. Rows with neither identifier make no paid call.
Path 2: Contact cost
Quote both Cargo-native tools and the LinkedIn profile enrichment action. Per contact, the maximum paid chain is:
- LinkedIn only: Find Email plus Contact LinkedIn Enrichment.
- Email only: Find LinkedIn Profile from Email plus Contact LinkedIn Enrichment when resolution succeeds.
- Both identifiers: Contact LinkedIn Enrichment only.
- Neither identifier: no paid call.
Show mutually exclusive route counts and maximum spend before requesting approval for either path.
Composes into
crm-deduplication after account enrichment, find-stakeholders for coverage gaps, segment-accounts
for activation, and track-job-changes as a separate one-time movement check.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

