Back to Playbooks

Salesforce L2A Governed Matcher

Private Beta
~45 min

Pre-conversion account context for Salesforce with audit-safe apply. Init emits a downstream automation risk audit, scout produces disagreements against the incumbent field, and apply writes only to a narrow Gremlin-owned Lead surface with plan-hash confirmation and receipts. The playbook below describes the current pilot surface; broader availability follows pilot feedback.

The Slack Message

VP
VP Revenue Operations10:14 AM

Our L2A field feeds assignment rules and territory. I don't trust it but I cannot touch it until we prove what changes if we do. Can you show me what Gremlin would do differently without writing anything, and let me approve before any field moves?

The Prompt

Kicked off from the terminal with mode and risk posture spelled out.

We have 50K open Leads in Salesforce and our incumbent L2A field (Account_Match__c) is feeding assignment rules. I don't know if I can trust it.

I need to:
1. Scan the org for downstream automation that references Account_Match__c
2. Run Gremlin salesforce l2a in observe_existing mode against a 1K sample
3. Hand the RevOps team disagreements.csv and the blind-spot digest
4. After they sign off, dry-run the fix plan and apply only the safe Gremlin-owned writes

No OwnerId writes, no territory, no conversion. Nothing touches Account_Match__c until we've acknowledged the risk audit.

The risk surface is not the field — it is the automation

Writing a matched-account field can trigger assignment rules, record-triggered Flow, territory logic, and account-side updates. That is why init emits a downstream automation risk audit and observe_existing is the default entry mode. Init → scout → disagree → dry-run → apply. Nothing customer-owned changes without an acknowledged risk hash.

Core Workflow

Init, scout, review, dry-run, apply. Dry-run is the default on every write path.

1

Init + Risk Audit

Scan Apex, Flow, assignment rules, and validation rules for references to the incumbent field. Emit risk_audit.md before any write mode is locked.

2

Scout in observe_existing

Evaluate open leads deterministically, compare against the incumbent field, and emit grouped exceptions and a hashed fix plan.

3

Disagreement Review

Hand disagreements.csv, blind_spot_digest.md, and grouped_exceptions/ to RevOps. Decision codes replace threshold arguments.

4

Dry-Run Fix Plan

Revalidate preconditions, emit APPLY_DRY_RUN_OK per row, and surface any precondition drift since scout ran. Nothing changes yet.

5

Apply with Plan Hash

Promote to live writes with --confirm-plan-hash, --ack-risk-audit, --max-actions, and --max-per-account. Receipts on every row.

After you paste this prompt, Claude Code:

"50K open Leads, incumbent field Account_Match__c feeds assignment rules. The risk surface is not the field — it is the automation that wakes up when the field changes. Start with init in observe_existing so no customer field is written until the risk audit is acknowledged."

"Step 1: init --mode observe_existing, scan downstream automation. Step 2: scout on 1,000-lead sample. Step 3: review disagreements.csv and blind_spot_digest.md. Step 4: trace ambiguous rows. Step 5: dry-run fix_plan.json. Step 6: apply with caps."

Running
$ g-gremlin salesforce l2a init \ --org-alias prod \ --mode observe_existing \ --existing-field Account_Match__c \ --known-account-map ./known_accounts.csv
Discovered 12 candidate fields on Lead. Scanned 47 automation references: 1 assignment rule, 2 record-triggered Flows, 4 formula fields, 0 Apex triggers. Risk: Account_Match__c = high_risk (feeds assignment rule "L2A Route"). Wrote gremlin.l2a.yaml, risk_audit.md, candidate_fields.csv.

"Account_Match__c is high_risk — confirmed. Observe_existing is the correct entry mode. Now scout a 1K sample against open leads."

Running
$ g-gremlin salesforce l2a scout \ --config ./gremlin.l2a.yaml \ --lead-filter "IsConverted = false" \ --sample 1000
Evaluated 1,000 leads in 12.4s. Decision codes: L2A_SAFE_EXACT_DOMAIN_SINGLE_ACCOUNT=612, L2A_SAFE_KNOWN_ACCOUNT_MAP_SINGLE_ACCOUNT=41, L2A_REVIEW_DOMAIN_MULTI_ACCOUNT=187, L2A_REVIEW_CONSUMER_EMAIL_ONLY=78, L2A_REVIEW_PARENT_CHILD_COLLISION=44, L2A_BLOCK_SHARED_FIELD_NONEMPTY=31, SKIP_NO_RELEVANT_CHANGE=7. Disagreements vs Account_Match__c: 214 rows. Plan hash 9f2c...e104.

"214 disagreements against the incumbent field. That is the artifact RevOps actually needs. Trace a disputed row before handoff."

Running
$ g-gremlin salesforce l2a trace \ --config ./gremlin.l2a.yaml \ --email [email protected]
lead_id 00Q5g000012345EAA. decision_code L2A_REVIEW_DOMAIN_MULTI_ACCOUNT. Candidates: Acme Holdings (parent, high band), Acme North America (child, high band). Evidence: 2 accounts share normalized_domain acme.com. Existing Account_Match__c = Acme Holdings (disagreement). Risk: Account_Match__c feeds assignment rule "L2A Route". Planned action: none (review required).

Artifacts ready for RevOps: summary.md, scout_summary.json, disagreements.csv (214 rows), safe_matches.csv (653 rows), grouped_exceptions/, blind_spot_digest.md, fix_plan.json (plan_hash 9f2c...e104, 653 actions).

The Disagreement Queue

This is what RevOps sees. Each row carries a decision code, candidate account, and the incumbent value. Operators mark rows for apply or escalate them to manual review.

lead_idemailgremlin_decision_codeexisting_field_valueagreementoperator_action
00Q5g000011AAA1[email protected]L2A_SAFE_EXACT_DOMAIN_SINGLE_ACCOUNT001xx000003DEF9disagreeapply_gremlin_match
00Q5g000012BBB2[email protected]L2A_REVIEW_DOMAIN_MULTI_ACCOUNT001xx000003ABC1disagreemanual_review
00Q5g000013CCC3[email protected]L2A_SAFE_KNOWN_ACCOUNT_MAP_SINGLE_ACCOUNTnulldisagreeapply_gremlin_match
After you paste this prompt, Claude Code:

"RevOps reviewed disagreements.csv. They want to apply only the gremlin_owned writes — no changes to Account_Match__c. That means mode stays observe_existing and the fix plan only fires lead.set_gremlin_match and lead.set_gremlin_state. Risk audit hash acknowledged separately."

Running
$ g-gremlin salesforce l2a apply \ --config ./gremlin.l2a.yaml \ --plan ./artifacts/salesforce_l2a/20260418_101530Z/fix_plan.json
Dry-run complete. 653 actions revalidated: APPLY_DRY_RUN_OK=648, APPLY_SKIPPED_PRECONDITION_CHANGED=5 (5 leads updated by another process since scout). No APPLY_FAILED_PLAN_HASH_MISMATCH.

"Dry-run looks clean. 5 rows drifted — that is expected for a live org. Apply with caps: --max-actions 100 for the first batch to validate permissions and receipts before scaling."

Running
$ g-gremlin salesforce l2a apply \ --config ./gremlin.l2a.yaml \ --plan ./artifacts/salesforce_l2a/20260418_101530Z/fix_plan.json \ --apply \ --confirm-plan-hash 9f2c...e104 \ --ack-risk-audit 3ab1...7c55 \ --max-actions 100 \ --max-per-account 5
Applied 100/100 actions. APPLY_SUCCESS_SET_GREMLIN_MATCH=87, APPLY_SUCCESS_STATE_ONLY=13, APPLY_FAILED_WRITE_REJECTED=0. Receipts written to apply_receipt.json and apply_receipt.csv with before/after for every row.

First 100 actions applied with receipts. 0 failures. Account_Match__c untouched. Gremlin_Matched_Account__c, Gremlin_Decision_Code__c, Gremlin_Receipt_Id__c now populated on 100 leads. Remaining 548 actions ready for the next batch.

Safety Guarantees

Dry-run by default. Live writes require --apply plus --confirm-plan-hash and --ack-risk-audit.
Action caps enforced per run: --max-actions and --max-per-account never exceeded.
Plan-hash revalidation blocks any row that changed in Salesforce since scout generated the plan.
Writes touch Lead only. No Account, Contact, Opportunity, Case, or OwnerId.
Receipts on every dry-run and live apply with before/after values and decision code.
Shared customer fields default to if_empty_only and refuse to run without an acknowledged risk audit.

What This Playbook Will NOT Do

Non-goals are enforced by the CLI, not just documented. Any drift into these lanes moves the product from governed matcher to routing platform.

No OwnerId writes, no territory writes
No lead status mutation, no lead conversion
No Account, Contact, Opportunity, or Case writes
No Apex deployment, no Flow deployment
No auto-deploy of AI-generated metadata
Will not overwrite non-empty customer-owned shared fields by default
Will not silently auto-resolve ambiguity with fuzzy or AI reasoning
Will not promise real-time in-org trigger behavior

Requirements

Salesforce Connected App

Read Lead and Account; write Gremlin-owned Lead fields only

Required

Gremlin CLI with l2a module

g-gremlin salesforce l2a init | scout | trace | apply

Required

gremlin.l2a.yaml

Emitted by init — drives mode, fields, and fingerprint

Required

Risk audit acknowledgment

--ack-risk-audit hash required for shared_existing live apply

Required

Results

1,000
Open leads evaluated
214
Disagreements
100
First-batch writes
0
Incumbent field writes
Incumbent routing field untouched

Account_Match__c remained read-only; no assignment-rule side effects fired.

Full audit trail

apply_receipt.json carries plan hash, decision code, and before/after for every row.

Request pilot access

The Salesforce L2A module is in private beta with a limited pilot cohort. Start conversations with init --mode observe_existing against a sandbox — the risk audit and disagreement report carry the story before any field is touched.

Related: Salesforce Lead-to-Account Matching guide · LeanData vs audit-first L2A · Salesforce dedupe playbook