Buying Committee Mapper
π‘ GrowthMaps stakeholders in target accounts to buying roles (Economic Buyer, Champion, Technical, User, Influencer, Blocker)
Adam's take
Single-threading kills more deals than pricing does. This one pays for itself the first time a champion goes quiet and you already know who else to call.
Required tools
Optional tools
Expected value
Reduces single-threaded deal risk; earlier multi-threading
Deployable workflow pack
The full pack includes the agent spec, deployment guide, architecture diagrams, n8n workflow skeleton, Clay template, HubSpot properties, JSON schemas, OpenAPI spec, cost and ROI calculators, demo data, QA checklist, and governance notes.
View pack on GitHubHow to deploy this agent
Single-threaded deals are one of the most common, and most preventable, causes of lost or stalled opportunities. This agent classifies every known contact at a target account into a buying role, scores committee coverage, and flags gaps before a deal stalls.
Core signals
Input schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://deploytheagent.com/schemas/agent-002/input.schema.json",
"title": "Buying Committee Mapper β Input",
"description": "One contact record on a target account entering classification. Identity fields required; enrichment and engagement may be null.",
"type": "object",
"required": ["account_domain", "contact_id", "full_name", "is_target_account"],
"additionalProperties": false,
"properties": {
"account_domain": { "type": "string" },
"contact_id": { "type": "string", "description": "HubSpot contact ID, or 'sourced:<provider>:<ref>' for candidates not yet in CRM" },
"full_name": { "type": "string", "minLength": 1 },
"email": { "type": ["string", "null"] },
"job_title": { "type": ["string", "null"] },
"department": { "type": ["string", "null"] },
"seniority": { "type": ["string", "null"], "enum": ["c_level", "vp", "director", "manager", "ic", "owner", null] },
"is_target_account": { "type": "boolean", "description": "Gate: only true records are processed" },
"is_sourced": { "type": "boolean", "default": false, "description": "True when the contact came from Apollo/Sales Nav and does not exist in CRM yet" },
"unsubscribed": { "type": "boolean", "default": false },
"engagement": {
"type": "object",
"additionalProperties": false,
"properties": {
"replies_90d": { "type": ["integer", "null"], "minimum": 0 },
"meetings_90d": { "type": ["integer", "null"], "minimum": 0 },
"last_activity_date": { "type": ["string", "null"], "format": "date" },
"rep_flagged_objection": { "type": "boolean", "default": false }
}
}
}
}Output schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://deploytheagent.com/schemas/agent-002/output.schema.json",
"title": "Buying Committee Mapper β Output",
"description": "Contact-level classification plus account-level coverage. Champion/Blocker appear only as candidate_* pending human confirmation; the rules engine may not emit them as confirmed roles.",
"type": "object",
"required": ["contacts", "account"],
"additionalProperties": false,
"properties": {
"contacts": {
"type": "array",
"items": {
"type": "object",
"required": ["contact_id", "buying_committee_role", "buying_role_confidence", "buying_role_model_version", "review_required"],
"additionalProperties": false,
"properties": {
"contact_id": { "type": "string" },
"buying_committee_role": {
"type": "string",
"enum": [
"economic_buyer",
"technical_buyer",
"user_buyer",
"influencer",
"unknown",
"candidate_champion",
"candidate_blocker"
],
"description": "Confirmed 'champion' and 'blocker' values are written only by the human review workflow, never by this agent"
},
"buying_role_confidence": { "type": "number", "minimum": 0, "maximum": 1 },
"buying_role_model_version": { "type": "string" },
"classification_basis": { "type": "string", "description": "Which rule or signal produced the role, for audit" },
"review_required": { "type": "boolean", "description": "True for candidates and sourced contacts" },
"unreachable": { "type": "boolean", "default": false, "description": "Unsubscribed; mapped for completeness, excluded from outreach suggestions" }
}
}
},
"account": {
"type": "object",
"required": ["account_domain", "committee_coverage_score", "committee_gaps", "single_threaded_risk", "committee_scan_date"],
"additionalProperties": false,
"properties": {
"account_domain": { "type": "string" },
"committee_coverage_score": { "type": "integer", "minimum": 0, "maximum": 100 },
"committee_gaps": {
"type": "array",
"items": { "type": "string", "enum": ["economic_buyer", "champion", "technical_buyer", "user_buyer"] }
},
"single_threaded_risk": { "type": "boolean" },
"committee_scan_date": { "type": "string", "format": "date" },
"engaged_contacts_90d": { "type": "integer", "minimum": 0 }
}
}
}
}Scoring / classification logic
Confirmed roles weigh 1.0 toward committee coverage; candidates and sourced-but-unconfirmed contacts weigh 0.5. An account is flagged single-threaded risk when two or fewer contacts have engaged in the last 90 days, independent of total contact count.
HubSpot CRM properties
n8n workflow skeleton
Weekly committee mapping: pull contacts on target accounts, classify buying roles by rule, detect champion/blocker candidates from engagement, score coverage, write agent-owned HubSpot fields, queue reviews. Configure every node marked CONFIGURE.
- Weekly mapping triggerCONFIGURE: default Tuesday 06:00 (after Agent #001's Monday scan). Add a webhook trigger for on-approval mapping if #001 is live.
- Load role-mapping configCONFIGURE: replace with your versioned role config built from real closed-won deals. Version string flows to buying_role_model_version.
- Fetch contacts on target accountsCONFIGURE: {{HUBSPOT_CREDENTIAL}}. Pull contacts on companies where icp_match_band = Target (or your manual target list). Include title, department, seniority, engagement, marketable status.
- Fetch sourced candidates from ClayCONFIGURE: Clay table export URL + {{CLAY_API_KEY}}. Sourced rows carry contact_id 'sourced:<provider>:<ref>' and is_sourced=true. Only sourced for accounts with committee_gaps.
- Validate + gate target accountsDrops non-target-account rows and structurally invalid rows; logs drops.
- Classify buying rolesRules-first classification. Champion/blocker emitted only as candidate_* from engagement signals; this node cannot output confirmed champion/blocker. LLM fallback for unmatched titles goes here if enabled (store prompt version in model_version).
- Score committee coverageConfirmed roles weigh 1.0, candidates and sourced weigh 0.5. Gaps = required roles with no confirmed contact. Single-threaded when <=1 engaged contact in 90d.
- Write contact fields (CRM contacts only)CONFIGURE: writes buying_committee_role, buying_role_confidence, buying_role_model_version for contacts whose contact_id is a HubSpot ID. Sourced contacts are NEVER written here; they go to the review queue.
- Write company coverage fieldsCONFIGURE: writes committee_coverage_score, committee_gaps, single_threaded_risk, committee_scan_date.
- Queue reviews (sourced + candidates)CONFIGURE: {{SLACK_CREDENTIAL}}, channel #committee-review. Lists sourced contacts pending CRM approval and champion/blocker candidates pending confirmation. Confirmation happens in HubSpot by a human; this workflow never creates contacts or confirms roles.
- Alert single-threaded TargetsCONFIGURE: channel. Posts Target accounts with single_threaded_risk=true and their gaps.
- Write run logAudit: run date, config version, role distribution, review queue size. Alert if influencer share of classified contacts >40% (loose config symptom).
Agent-driven Clay integration (optional)
The default deployment path pulls Clay data through a static export URL, shown above. Teams already running Clay through its official CLI/MCP (clay-run/agent-plugins) can use this agent-driven path instead.
Clay MCP/CLI Integration β Agent #002
Pack Version: 1.0.0
Optional path. Use this instead of a manual `clay-template.csv` import when the mapper's contact-sourcing step should call Clay through the official CLI/MCP rather than a static export.
Source: `clay-run/agent-plugins` (github.com/clay-run/agent-plugins), the official Clay plugin for Claude Code, Cursor, and Codex. Bundles a `clay` CLI and an MCP server, authenticated with a `CLAY_API_KEY`.
When to use this
The mapper sources candidate stakeholder contacts per account and checks job-change status on already-mapped contacts (see the "Stale committee treated as current" failure mode in `agent-spec.md`). If that job-change check runs on a schedule against a live Clay table rather than a periodic export, the MCP/CLI path lets the workflow query Clay directly instead of waiting on the next CSV refresh.
If contact sourcing only happens at account-approval time with no ongoing job-change monitoring, the CSV template is enough.
Setup
- Create a Clay API key: Clay β Settings β Account.
- Store it as `CLAY_API_KEY` in the n8n credential store, scoped to this workflow. Never place it in the workflow JSON or the repo.
- Install the plugin in the build environment: `/plugin marketplace add clay-run/agent-plugins` then `/plugin install clay@clay-plugins` (Claude Code), or the Codex/Cursor equivalents in the plugin README.
- Verify before first production run: `clay whoami`. Exit 0 confirms the key resolves to the correct workspace. Exit 3 means the key is missing or invalid.
Scope for this agent
- Read stakeholder candidate rows and job-change status: unattended, matches this agent's existing scoring logic.
- Write role classification back to Clay for downstream reference: unattended, agent-owned columns only.
- Create or update HubSpot contacts from Clay data: not allowed through this path. Contact creation stays gated behind the human-review step this agent's Governance section already requires (`no_auto_crm_writes_without_review`).
Governance addition
Log every Clay CLI/MCP call this workflow makes (table, action, row count, timestamp) alongside the existing `audit_trail_required` flag. Rotate `CLAY_API_KEY` if the n8n instance or its execution logs are ever exposed.
Fallback
The default deployment path stays the CSV import described in `clay-setup-guide.md`. This file documents the agent-driven alternative for teams already running Clay through its official CLI/MCP elsewhere in their stack.
Cost calculator
Cost Calculator β Agent #002
Pack Version: 1.0.0
Cost drivers
| Driver | Unit | Typical price | Notes |
|---|---|---|---|
| Person enrichment (title/dept/seniority) | credit/contact | $0.05β$0.15 | Only for contacts with missing or junk titles |
| Contact sourcing | credit/candidate | $0.10β$0.30 | Apollo/Sales Nav via Clay, gaps only, capped 3/gap |
| Job-change monitoring | credit/contact/mo | $0.02β$0.05 | Mapped contacts only |
| n8n | instance | $0β$50/mo | Shared with other agents |
| LLM fallback (optional) | tokens/title | ~$0.001 | Only unmatched titles |
Assumptions: blended $0.07/credit; ~40% of CRM contacts need title enrichment; average 1.5 gaps per account at first run, falling to ~0.5 steady state; 3 sourced candidates per gap at $0.15.
Worked scenarios (steady state, monthly)
| 50 target accounts | 250 target accounts | 1,000 target accounts | |
|---|---|---|---|
| Contacts on accounts (~6/account) | 300 | 1,500 | 6,000 |
| Title enrichment (40% Γ $0.07, 60-day cache β half monthly) | $4 | $21 | $84 |
| Sourcing (0.5 gaps Γ 3 candidates Γ $0.15) | $11 | $56 | $225 |
| Job-change checks ($0.03/mapped contact) | $9 | $45 | $180 |
| n8n share | $0β$25 | $25 | $25 |
| Total | ~$24β$49 | ~$147 | ~$514 |
First run costs more: full enrichment pass plus ~1.5 gaps/account sourcing. At 250 accounts expect ~$180β$250 for the initial map.
What this replaces
Manual committee mapping at ~1 hour per account. Mapping 250 accounts by hand is 250 operator hours nobody actually spends, which is the real point: the work simply doesn't happen, and deals stay single-threaded.
ROI calculator
ROI Calculator β Agent #002
Pack Version: 1.0.0
Fill the blanks with your numbers. This agent's value splits into time saved and deal-risk reduction; model them separately and only claim the second if you can measure it.
Time savings
| Input | Default | Yours |
|---|---|---|
| Accounts needing committee maps/month | 40 | ___ |
| Hours per manual map | 1 | ___ |
| Hours per map with agent (review only) | 0.1 | ___ |
| Loaded hourly cost | $75 | ___ |
| Monthly time saving | 40 Γ 0.9 Γ $75 = $2,700 | ___ |
Honest caveat: most teams don't do 40 manual maps a month, they do 5 and skip the rest. If that's you, count the 5 real maps ($338) as saved time and treat the other 35 as new capability, priced by the risk model below.
Running cost
| Input | Default | Yours |
|---|---|---|
| Monthly Clay/enrichment (cost-calculator.md) | $147 (250 accounts) | ___ |
| Review time: ABM lead, 1 hr/week at $90 | $360 | ___ |
| Build amortized (1 day Γ $600 / 12) | $50 | ___ |
| Monthly cost | $557 | ___ |
Deal-risk reduction (measure before claiming)
Pull your last 4 quarters: win rate and slip rate on deals with 3+ engaged contacts versus 1. If your data shows the multi-threaded lift most teams find, the model is:
| Input | Yours |
|---|---|
| Open pipeline on single-threaded Target accounts | ___ |
| Historical win-rate delta (multi vs single-threaded) | ___ % |
| Share of flagged accounts you expect to multi-thread | ___ % |
| Risk-adjusted pipeline recovered | = product of the three |
No default numbers here. If you can't pull the cohort comparison, run the agent one quarter, then build the case from your own before/after.
Governance notes
- Champion and Blocker are never confirmed by the workflow itself β only surfaced as candidates for a human to confirm in HubSpot.
- Sourced-but-unconfirmed contacts are never written to CRM contact records directly; they route to a review queue instead.
- Every classification stores a config version and a plain-language basis (which rule or behavioral signal fired) for auditability.
- The agent does not contact anyone directly β it surfaces coverage gaps and lets a human decide how to fill them.
Failure modes
- Sparse contact data: an account with only one or two known contacts canβt be meaningfully mapped β flagged for manual contact sourcing (Agent #005) rather than forced into a classification.
- Rule-only classification without engagement data produces lower-confidence results, written as such.
- Loose config symptom: if influencer classifications exceed roughly 40% of contacts, that indicates the role config is too broad and needs retuning.
- Role drift: classification re-runs on a schedule rather than treating a role as a permanent, one-time label.
QA checklist
QA Checklist β Agent #002
Pack Version: 1.0.0
Complete on a test portal or sandboxed property group. The reviewer must not be the builder.
Data validation
- β All five demo-data records pass `schemas/input.schema.json`
- β Workflow output on demo data matches `demo-data/sample-output.json` field for field
- β Output validates against `schemas/output.schema.json`
- β A non-target-account contact is gated out and logged
- β A record missing `job_title` classifies Unknown, does not fail the run
Classification
- β Dana Reyes (VP Revenue Operations) β economic_buyer, 0.9
- β Marcus Webb (3 replies + 1 meeting) β candidate_champion 0.7, review_required true, title-role noted in basis
- β Priya Nair (Senior Data Engineer) β technical_buyer, 0.8
- β Jordan Blake ("Growth Ninja", unsubscribed) β unknown 0.3, unreachable true
- β Elena Marsh (sourced) β user_buyer, review_required true
- β The rules engine cannot emit confirmed `champion` or `blocker`: attempt to force it via config; the output schema rejects the record
- β Every record carries `classification_basis` and `buying_role_model_version`
Coverage
- β Acme scores 75 with gaps [champion, user_buyer] (candidates weigh 0.5, confirmed 1.0)
- β Gaps list only roles with no *confirmed* contact
- β Single-threaded flag: account with 1 engaged contact flags true; Acme (2 engaged) flags false
- β Blocker candidates do not add coverage weight
CRM safety
- β Sourced contacts (`sourced:*` IDs) are never written to HubSpot; they appear only in the review queue
- β Contact writes touch only the three `buying_role*` properties; company writes only the four committee properties (diff before/after)
- β Unsubscribed contacts are classified but excluded from sourcing/outreach suggestions
- β Private app scopes match the setup guide
Operations
- β Review queue posts to the agreed channel with basis text per candidate
- β Single-threaded alert posts with gaps listed
- β Run log records role distribution; Influencer >40% of classified contacts raises the loose-config alert
- β Job-change path: departed contact resets to Unknown and alerts
- β Rollback tested: workflow disabled, test property values cleared
Sign-off
| Role | Name | Date |
|---|---|---|
| Builder | ||
| Reviewer (not the builder) | ||
| ABM lead (review queue owner) |