Closed-loop account-intelligence system: harvest -> relevance filter vs. Capability Library -> synthesize -> score -> deliver -> feedback. - 40-capability Broadcom + VMware library - Live Exa.ai news connector with hard free-plan request ceilings - Provider-agnostic LLM layer (Gemini/Anthropic) with offline mock fallback - LLM confidence wired into composite scoring + low-confidence floor - 7-account pilot list Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
885 lines
38 KiB
Markdown
885 lines
38 KiB
Markdown
# Enterprise Sales Prospecting Agent Loop
|
||
## Account Intelligence System for Cross-Sell Opportunity Detection
|
||
|
||
---
|
||
|
||
## 1. Overview & Vision
|
||
|
||
The goal is a continuously running, closed-loop intelligence system that monitors a defined list of target accounts and surfaces high-confidence cross-sell opportunities to your sales team before they have to go looking. Rather than relying on reps to manually track news, LinkedIn, and account signals, the system does this automatically — ingesting raw signals, running them through an AI reasoning layer that understands your product capabilities, scoring them, and routing actionable briefs to the right person at the right time.
|
||
|
||
The system operates on three core principles:
|
||
|
||
- **Signal-first:** Every output is grounded in a real, verifiable business signal (a LinkedIn post, a job posting, a press release) — never speculation.
|
||
- **Capability-mapped:** Every signal is evaluated against a structured library of your product capabilities, so the output is always tied to something you can actually sell.
|
||
- **Closed-loop:** Sales rep feedback flows back into the system, continuously improving signal relevance, scoring weights, and messaging quality.
|
||
|
||
---
|
||
|
||
## 2. System Architecture
|
||
|
||
```
|
||
┌────────────────────────────────────────────────────────────────────────┐
|
||
│ AGENT ORCHESTRATOR │
|
||
│ (Scheduler + Job Queue + State) │
|
||
└────────────────────────┬───────────────────────────────────────────────┘
|
||
│ dispatches per-account jobs
|
||
┌──────────────┼──────────────────┐
|
||
▼ ▼ ▼
|
||
┌───────────────┐ ┌──────────────┐ ┌──────────────────┐
|
||
│ SIGNAL │ │ SIGNAL │ │ SIGNAL │
|
||
│ HARVESTER │ │ HARVESTER │ │ HARVESTER │
|
||
│ (LinkedIn) │ │ (News/Web) │ │ (Job Postings) │
|
||
└───────┬───────┘ └──────┬───────┘ └──────────┬───────┘
|
||
└─────────────────┼──────────────────────┘
|
||
│ raw signals
|
||
▼
|
||
┌─────────────────────┐
|
||
│ SIGNAL NORMALIZER │
|
||
│ (dedup + enrich) │
|
||
└──────────┬──────────┘
|
||
│ structured signal objects
|
||
▼
|
||
┌─────────────────────┐
|
||
│ RELEVANCE FILTER │◄─── Capability Library
|
||
│ (LLM reasoning) │
|
||
└──────────┬──────────┘
|
||
│ relevant signals only
|
||
▼
|
||
┌─────────────────────┐
|
||
│ OPPORTUNITY │◄─── Account Context (CRM)
|
||
│ SYNTHESIZER │◄─── Historical Feedback
|
||
│ (LLM + scoring) │
|
||
└──────────┬──────────┘
|
||
│ scored opportunity briefs
|
||
▼
|
||
┌─────────────────────┐
|
||
│ DEDUP & MEMORY │
|
||
│ (Redis + pgvector) │
|
||
└──────────┬──────────┘
|
||
│ new, non-duplicate opportunities
|
||
▼
|
||
┌─────────────────────┐
|
||
│ DELIVERY LAYER │
|
||
│ (Slack / CRM / UI) │
|
||
└──────────┬──────────┘
|
||
│ feedback
|
||
▼
|
||
┌─────────────────────┐
|
||
│ FEEDBACK ENGINE │
|
||
│ (scoring updates) │
|
||
└─────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Data Sources & Signal Harvesting
|
||
|
||
### 3.1 LinkedIn Intelligence
|
||
|
||
LinkedIn is the highest-signal source for executive intent, strategic direction, and organizational change.
|
||
|
||
**What to harvest:**
|
||
- Posts from C-suite, VP-level, and Director-level contacts at target accounts
|
||
- Company page updates and announcements
|
||
- Job postings (particularly in functions that correlate with buying decisions)
|
||
- Headcount change patterns (rapid hiring vs. contraction in specific departments)
|
||
- Executive profile changes (new roles, new certifications, new responsibilities)
|
||
|
||
**How to harvest:**
|
||
- **Proxycurl API** — enriches LinkedIn profiles and company pages programmatically, GDPR-compliant
|
||
- **Apify LinkedIn scrapers** — for post content and activity feeds at scale
|
||
- **LinkedIn Sales Navigator API** (if licensed) — provides official access to account activity feeds and alerts
|
||
- **RocketReach / Apollo.io API** — supplemental contact and org chart data
|
||
|
||
**Signal types extracted:**
|
||
```json
|
||
{
|
||
"type": "linkedin_post",
|
||
"account_id": "acct_123",
|
||
"author": { "name": "Jane Smith", "title": "CTO", "seniority": "C-suite" },
|
||
"content": "We're accelerating our digital transformation journey...",
|
||
"engagement": { "likes": 142, "comments": 38 },
|
||
"published_at": "2026-06-18T14:22:00Z",
|
||
"raw_url": "https://linkedin.com/posts/..."
|
||
}
|
||
```
|
||
|
||
### 3.2 News & Web Intelligence
|
||
|
||
**What to harvest:**
|
||
- Press releases (funding rounds, acquisitions, partnerships, new product launches)
|
||
- Earnings call transcripts (public companies) — rich in strategic intent signals
|
||
- Industry publication coverage
|
||
- Executive interviews and keynote summaries
|
||
- Regulatory filings (10-K, 10-Q for public companies via SEC EDGAR)
|
||
|
||
**How to harvest:**
|
||
- **Exa.ai** — semantic web search with date-filtering, excellent for "find me news about Acme Corp in the last 30 days"
|
||
- **NewsAPI / GDELT** — broad news aggregation with keyword and entity filtering
|
||
- **SEC EDGAR API** — free, official access to all public company filings
|
||
- **Perplexity API** — for summarizing and reasoning over web content
|
||
- **RSS feeds** — many company blogs and investor relations pages expose RSS
|
||
|
||
### 3.3 Job Posting Intelligence
|
||
|
||
Job postings are one of the most underutilized signals in B2B sales. A company posting for a "VP of Financial Operations" or "Director of Revenue Operations" is telling you they're scaling a function — and likely buying new tools for it.
|
||
|
||
**What to harvest:**
|
||
- Job titles in departments relevant to your product capabilities
|
||
- Technology stack mentions in job descriptions ("must have experience with Salesforce, looking to migrate from legacy ERP...")
|
||
- Volume of hiring in specific functions (trend data)
|
||
- Keywords like "build vs. buy," "digital transformation," "consolidate," "modernize"
|
||
|
||
**How to harvest:**
|
||
- **JSearch API (via RapidAPI)** — aggregates LinkedIn Jobs, Indeed, Glassdoor
|
||
- **Adzuna API** — free tier available, covers major job boards
|
||
- **Coresignal** — provides bulk job posting data with company mapping
|
||
|
||
### 3.4 CRM Context (Internal)
|
||
|
||
Every signal is enriched with internal CRM context to prevent surfacing noise:
|
||
|
||
- Current products the account has purchased
|
||
- Open opportunities and their stages
|
||
- Last activity date with the account
|
||
- Account tier / strategic priority
|
||
- Assigned rep and territory
|
||
- Known stakeholders and their roles
|
||
- Prior AI-surfaced signals and their outcomes
|
||
|
||
**Integration:** Salesforce REST API, HubSpot API, or direct DB query if self-hosted.
|
||
|
||
### 3.5 Product Usage Signals (If Available)
|
||
|
||
If your enterprise software has usage telemetry:
|
||
|
||
- Features actively used vs. features licensed but dormant
|
||
- Usage trends (increasing engagement in Module A could signal readiness for Module B)
|
||
- Login frequency and user adoption by department
|
||
- Support ticket patterns (frustration with a gap your cross-sell product fills)
|
||
|
||
---
|
||
|
||
## 4. The Agent Loop — Step-by-Step
|
||
|
||
### Phase 1: Signal Harvesting (Per Account, Per Run)
|
||
|
||
```typescript
|
||
interface AccountJob {
|
||
accountId: string;
|
||
accountName: string;
|
||
domain: string;
|
||
targetContacts: Contact[]; // from CRM
|
||
currentProducts: string[]; // what they already have
|
||
accountTier: 'strategic' | 'enterprise' | 'growth';
|
||
lastRunAt: Date;
|
||
}
|
||
|
||
async function runAccountHarvestCycle(job: AccountJob): Promise<RawSignal[]> {
|
||
const signals = await Promise.allSettled([
|
||
harvestLinkedInSignals(job),
|
||
harvestNewsSignals(job),
|
||
harvestJobPostingSignals(job),
|
||
harvestFilingsSignals(job),
|
||
]);
|
||
|
||
return signals
|
||
.filter(r => r.status === 'fulfilled')
|
||
.flatMap(r => (r as PromiseFulfilledResult<RawSignal[]>).value);
|
||
}
|
||
```
|
||
|
||
The harvester runs on a per-account cadence:
|
||
- **Strategic accounts:** Every 24 hours
|
||
- **Enterprise accounts:** Every 48–72 hours
|
||
- **Growth accounts:** Weekly
|
||
|
||
### Phase 2: Signal Normalization & Deduplication
|
||
|
||
All signals from different sources are normalized into a common schema, then deduplicated against the memory store.
|
||
|
||
```typescript
|
||
interface NormalizedSignal {
|
||
id: string; // hash of source + content fingerprint
|
||
accountId: string;
|
||
source: 'linkedin' | 'news' | 'jobs' | 'sec_filing' | 'usage';
|
||
signalType: string; // 'executive_post' | 'hiring_surge' | 'press_release' | etc.
|
||
content: string; // normalized text content
|
||
metadata: Record<string, unknown>;
|
||
publishedAt: Date;
|
||
discoveredAt: Date;
|
||
author?: { name: string; title: string; seniority: string };
|
||
embedding?: number[]; // vector embedding for semantic dedup
|
||
}
|
||
```
|
||
|
||
**Deduplication strategy:**
|
||
1. **Hash-based:** Exact match on `source + url` — catches literal duplicates
|
||
2. **Semantic dedup:** Cosine similarity on embeddings against signals seen in the last 90 days — catches rephrased versions of the same story
|
||
3. **Window-based suppression:** If a signal of type `executive_post` for account X with similar themes was surfaced < 14 days ago, suppress unless engagement is 3x higher
|
||
|
||
### Phase 3: Relevance Filtering (LLM Reasoning Layer)
|
||
|
||
This is where raw signals are evaluated against your **Capability Library** — a structured description of every product/module you sell, what business problems it solves, and what signals suggest a buying opportunity.
|
||
|
||
```typescript
|
||
interface Capability {
|
||
id: string;
|
||
name: string;
|
||
description: string; // plain language
|
||
businessProblems: string[]; // problems this solves
|
||
buyingSignals: string[]; // what to look for in the market
|
||
antiSignals: string[]; // signals that suggest they already have this
|
||
targetBuyer: { titles: string[]; departments: string[] };
|
||
}
|
||
```
|
||
|
||
**Example Capability entry:**
|
||
```typescript
|
||
{
|
||
id: "cap_financial_close",
|
||
name: "Automated Financial Close Module",
|
||
description: "Automates the month-end and quarter-end close process, reducing cycle time and manual reconciliation errors.",
|
||
businessProblems: [
|
||
"Manual financial close taking 10+ days",
|
||
"Audit findings related to reconciliation errors",
|
||
"Controller or CFO unable to scale without adding headcount",
|
||
"Recent ERP migration creating reporting gaps"
|
||
],
|
||
buyingSignals: [
|
||
"Hiring for financial reporting or accounting manager roles",
|
||
"CFO/Controller posts about process improvement or modernization",
|
||
"Press release about acquisition (new entity to consolidate)",
|
||
"Mention of 'close process' or 'reconciliation' challenges in earnings calls",
|
||
"Job descriptions mentioning 'reduce close cycle time'"
|
||
],
|
||
antiSignals: [
|
||
"Already purchased this module per CRM",
|
||
"Recent competitor win logged in CRM"
|
||
],
|
||
targetBuyer: {
|
||
titles: ["CFO", "Controller", "VP Finance", "Head of Accounting"],
|
||
departments: ["Finance", "Accounting"]
|
||
}
|
||
}
|
||
```
|
||
|
||
The LLM relevance filter receives:
|
||
- The normalized signal
|
||
- The account's current product footprint (to apply anti-signals)
|
||
- The full Capability Library
|
||
|
||
**System prompt pattern:**
|
||
```
|
||
You are an account intelligence analyst for an enterprise software company.
|
||
|
||
Your job: evaluate whether a business signal indicates a genuine cross-sell opportunity for one of our product capabilities.
|
||
|
||
ACCOUNT CONTEXT:
|
||
- Company: {{accountName}}
|
||
- Current Products: {{currentProducts}}
|
||
- Account Tier: {{accountTier}}
|
||
|
||
SIGNAL:
|
||
{{signal.content}}
|
||
Source: {{signal.source}} | Type: {{signal.signalType}} | Date: {{signal.publishedAt}}
|
||
|
||
CAPABILITY LIBRARY:
|
||
{{capabilityLibrary}}
|
||
|
||
INSTRUCTIONS:
|
||
1. Determine if this signal indicates a business need that one or more of our capabilities directly addresses.
|
||
2. If yes, identify the specific capability ID(s) and explain WHY this signal is relevant.
|
||
3. If the account already has that capability, mark it as an expansion signal instead.
|
||
4. Assign a signal strength: strong | moderate | weak
|
||
5. Identify the ideal contact to approach (by title/function) based on the signal.
|
||
6. If no relevance exists, return null.
|
||
|
||
Return JSON only. No preamble.
|
||
```
|
||
|
||
**Output:**
|
||
```json
|
||
{
|
||
"relevant": true,
|
||
"capabilityIds": ["cap_financial_close"],
|
||
"rationale": "The CFO's post explicitly mentions 'closing the books faster' as a Q3 priority following their recent acquisition of MidWest Holdings. The acquisition creates a new consolidation need our module directly addresses.",
|
||
"signalStrength": "strong",
|
||
"signalType": "executive_intent + strategic_event",
|
||
"idealContact": { "title": "CFO", "department": "Finance" },
|
||
"suggestedAngle": "Post-acquisition consolidation complexity as the entry point conversation"
|
||
}
|
||
```
|
||
|
||
### Phase 4: Opportunity Synthesis
|
||
|
||
Relevant signals are grouped per account and synthesized into a single **Opportunity Brief** using a second LLM pass. This aggregates multiple weak signals into a coherent narrative, or elevates a single strong signal into a full sales brief.
|
||
|
||
```typescript
|
||
interface OpportunityBrief {
|
||
id: string;
|
||
accountId: string;
|
||
accountName: string;
|
||
capabilityId: string;
|
||
capabilityName: string;
|
||
|
||
headline: string; // 1-sentence summary
|
||
executiveSummary: string; // 2-3 sentence brief for the rep
|
||
signals: EnrichedSignal[]; // all contributing signals
|
||
|
||
suggestedTalkingPoints: string[]; // 3-5 specific points grounded in signals
|
||
suggestedOutreachHook: string; // opening line for email or LinkedIn DM
|
||
|
||
targetContacts: Contact[]; // from CRM + signal author enrichment
|
||
urgencyLevel: 'high' | 'medium' | 'low';
|
||
confidenceScore: number; // 0.0–1.0
|
||
|
||
createdAt: Date;
|
||
expiresAt: Date; // opportunities decay if not acted on
|
||
assignedRep: string;
|
||
}
|
||
```
|
||
|
||
**Second-pass synthesis prompt** (abbreviated):
|
||
```
|
||
You are synthesizing multiple account intelligence signals into a single, actionable sales opportunity brief.
|
||
|
||
SIGNALS (already validated as relevant):
|
||
{{signals}}
|
||
|
||
ACCOUNT CONTEXT:
|
||
{{accountContext}}
|
||
|
||
TARGET CAPABILITY:
|
||
{{capability}}
|
||
|
||
Create a brief that:
|
||
- Opens with the single most compelling signal
|
||
- Builds a coherent narrative connecting all signals
|
||
- Proposes 3-5 specific talking points tied directly to signals
|
||
- Suggests an opening hook for outreach (LinkedIn DM or email opener)
|
||
- Does not over-promise — stay grounded in what the signals actually say
|
||
|
||
Return JSON matching the OpportunityBrief schema.
|
||
```
|
||
|
||
### Phase 5: Scoring & Prioritization
|
||
|
||
Each brief receives a composite score used to determine delivery urgency and rep queue position.
|
||
|
||
```
|
||
compositeScore =
|
||
(signalStrength × 0.35)
|
||
+ (accountTier × 0.25)
|
||
+ (buyerSeniority × 0.20)
|
||
+ (timingUrgency × 0.15)
|
||
+ (repFeedbackWeight × 0.05)
|
||
```
|
||
|
||
**Scoring dimensions:**
|
||
|
||
| Dimension | High | Medium | Low |
|
||
|---|---|---|---|
|
||
| Signal Strength | Multiple strong signals | 1 strong or 2 moderate | Weak signals only |
|
||
| Account Tier | Strategic | Enterprise | Growth |
|
||
| Buyer Seniority | C-suite or EVP | VP/Director | Manager |
|
||
| Timing Urgency | <7 days since signal | 7–21 days | 21+ days |
|
||
| Rep Feedback Weight | Rep has marked similar opps as useful | Neutral history | Rep has marked similar as noise |
|
||
|
||
Briefs scoring above **0.72** are routed as **Priority** (immediate Slack notification). Briefs between **0.45–0.72** are queued as **Standard** (daily digest). Below **0.45** are logged but not delivered unless the rep opts into low-confidence signals.
|
||
|
||
### Phase 6: Memory & State Management
|
||
|
||
```typescript
|
||
// What gets persisted per account
|
||
interface AccountMemory {
|
||
accountId: string;
|
||
|
||
// Signal fingerprints seen in last 90 days (for dedup)
|
||
signalHashes: string[];
|
||
signalEmbeddings: { hash: string; embedding: number[] }[];
|
||
|
||
// Opportunities surfaced (to avoid re-surfacing same opp)
|
||
surfacedOpportunities: {
|
||
briefId: string;
|
||
capabilityId: string;
|
||
surfacedAt: Date;
|
||
repAction: 'accepted' | 'rejected' | 'snoozed' | 'pending';
|
||
repFeedback?: string;
|
||
}[];
|
||
|
||
// Suppression rules (rep-defined)
|
||
suppressions: {
|
||
capabilityId?: string; // "don't show me fin-close opps for this account"
|
||
signalType?: string;
|
||
expiresAt?: Date;
|
||
}[];
|
||
|
||
// Rep-observed context (from CRM notes, enriched by AI parsing)
|
||
accountNarrative: string; // running AI-maintained summary of account status
|
||
|
||
lastUpdated: Date;
|
||
}
|
||
```
|
||
|
||
**Memory store implementation:**
|
||
- **Redis** — signal hash dedup (fast O(1) lookups, TTL-based expiry)
|
||
- **pgvector (PostgreSQL)** — embedding similarity search for semantic dedup and "find similar opportunities"
|
||
- **Primary DB** — full opportunity history, account memory, feedback records
|
||
|
||
### Phase 7: Delivery Layer
|
||
|
||
#### Slack Integration
|
||
Real-time delivery for high-priority opportunities:
|
||
|
||
```
|
||
🚨 *New Cross-Sell Signal — Acme Corp* | Priority: HIGH
|
||
|
||
*Opportunity:* Automated Financial Close Module
|
||
*Confidence:* 87% | *Urgency:* High
|
||
|
||
*Why Now:*
|
||
CFO Jane Smith posted yesterday about their post-acquisition integration
|
||
challenges, specifically calling out "reconciliation across three new
|
||
entities" as a Q3 blocker. They acquired MidWest Holdings 6 weeks ago.
|
||
|
||
*Supporting Signals:*
|
||
• LinkedIn post by CFO (Jun 18) — 142 likes, 38 comments
|
||
• 3 open Finance Manager roles (posted Jun 10–14) referencing
|
||
"multi-entity consolidation"
|
||
• Earnings call transcript (May 15) — CFO mentioned "streamlining
|
||
financial operations" as top priority
|
||
|
||
*Talking Points:*
|
||
1. Multi-entity consolidation is a known friction point post-acquisition
|
||
2. Our module reduces average close cycle by 60% in consolidation scenarios
|
||
3. Other [industry] clients scaled from 2 → 8 entities without adding headcount
|
||
|
||
*Suggested Opener:*
|
||
"Jane — saw your post about the MidWest integration. We've helped three
|
||
other [industry] CFOs close the multi-entity gap without adding staff.
|
||
Worth 20 minutes?"
|
||
|
||
*Best Contact:* Jane Smith (CFO) | Mike Torres (Controller — already in CRM)
|
||
|
||
[✅ Act on This] [⏰ Snooze 2 Weeks] [❌ Not Relevant]
|
||
```
|
||
|
||
#### CRM Auto-Logging
|
||
Every surfaced brief is auto-logged as an activity against the account record, with:
|
||
- Signal summary
|
||
- Full opportunity brief
|
||
- Links to source signals
|
||
- Suggested next step task created and assigned to rep
|
||
|
||
#### Daily Intelligence Digest (Email)
|
||
A consolidated morning email for each rep covering all active accounts, ordered by composite score, showing only standard-priority and above. Each entry is a 3-line summary with a link to the full brief in the web UI.
|
||
|
||
#### Web Dashboard (Optional)
|
||
A purpose-built UI showing:
|
||
- Active opportunity queue per rep / per account
|
||
- Signal feed with filtering by capability, signal type, account tier
|
||
- Account 360 view showing all signals + opportunity history
|
||
- Capability coverage heatmap (which accounts have white space for which products)
|
||
- System health (run logs, signal volume, delivery rates)
|
||
|
||
---
|
||
|
||
## 5. The Capability Mapping Engine
|
||
|
||
The capability library is the brain of the system. It needs to be maintained by a product or sales enablement team and treated as a living document.
|
||
|
||
### 5.1 Capability Library Structure
|
||
|
||
```typescript
|
||
interface CapabilityLibrary {
|
||
version: string;
|
||
lastUpdated: Date;
|
||
capabilities: Capability[];
|
||
|
||
// Cross-capability rules
|
||
bundleSignals: {
|
||
capabilityIds: string[];
|
||
description: string; // "When we see X + Y, pitch Z bundle"
|
||
}[];
|
||
|
||
// Account-level exclusion rules
|
||
globalAntiSignals: string[]; // "Competitor X win" → suppress all opps
|
||
}
|
||
```
|
||
|
||
### 5.2 Capability Embedding Index
|
||
|
||
Every `buyingSignal` string in the library is embedded and stored in a vector index. When a new signal comes in, a fast ANN (approximate nearest neighbor) search can pre-filter which capabilities are candidates before sending to the LLM. This reduces LLM calls by ~60% and dramatically improves latency on high-volume runs.
|
||
|
||
```typescript
|
||
// Pre-filter step before LLM evaluation
|
||
async function preFilterCapabilities(
|
||
signal: NormalizedSignal,
|
||
library: CapabilityLibrary
|
||
): Promise<Capability[]> {
|
||
const signalEmbedding = await embed(signal.content);
|
||
|
||
const similar = await vectorDB.query({
|
||
vector: signalEmbedding,
|
||
topK: 5,
|
||
threshold: 0.75,
|
||
index: 'capability_buying_signals'
|
||
});
|
||
|
||
// Return only capabilities that have at least one similar buying signal
|
||
const candidateCapabilityIds = [...new Set(similar.map(r => r.capabilityId))];
|
||
return library.capabilities.filter(c => candidateCapabilityIds.includes(c.id));
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Feedback Loop & Continuous Improvement
|
||
|
||
The closed-loop aspect is what separates this from a simple alert system. Every rep action is a training signal.
|
||
|
||
### 6.1 Feedback Actions
|
||
|
||
- **✅ Act on This** → High-quality signal; boosts weight for this signal type + capability combination for this account tier
|
||
- **⏰ Snooze 2 Weeks** → Timing is off; opportunity is real but surfaced too early
|
||
- **❌ Not Relevant** → Low-quality signal; penalizes weight; rep can optionally specify why
|
||
- **💬 Custom Feedback** → Free-text comment, parsed by LLM to extract structured feedback
|
||
- **🏆 Won Deal** → Maximum positive signal; retrospectively boosts all signals that contributed
|
||
- **❌ Lost Deal** → Retrospective analysis — what signals did we have, what did we miss?
|
||
|
||
### 6.2 Feedback Storage Schema
|
||
|
||
```sql
|
||
CREATE TABLE opportunity_feedback (
|
||
id UUID PRIMARY KEY,
|
||
brief_id UUID NOT NULL,
|
||
account_id TEXT NOT NULL,
|
||
capability_id TEXT NOT NULL,
|
||
rep_id TEXT NOT NULL,
|
||
action VARCHAR(20) NOT NULL, -- act_on | snooze | reject | won | lost
|
||
feedback_text TEXT,
|
||
feedback_parsed JSONB, -- structured extraction of free text
|
||
signal_types_present TEXT[], -- which signal types contributed
|
||
account_tier VARCHAR(20),
|
||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
### 6.3 Weight Recalibration (Weekly Job)
|
||
|
||
A weekly background job analyzes feedback patterns and adjusts scoring weights:
|
||
|
||
```typescript
|
||
async function recalibrateScoring(): Promise<void> {
|
||
// Calculate acceptance rate by signal_type × capability × account_tier
|
||
const acceptanceRates = await db.query(`
|
||
SELECT
|
||
capability_id,
|
||
account_tier,
|
||
unnest(signal_types_present) AS signal_type,
|
||
COUNT(*) FILTER (WHERE action IN ('act_on', 'won')) AS positive,
|
||
COUNT(*) AS total
|
||
FROM opportunity_feedback
|
||
WHERE created_at > NOW() - INTERVAL '90 days'
|
||
GROUP BY 1, 2, 3
|
||
HAVING COUNT(*) >= 5
|
||
`);
|
||
|
||
// Update scoring weights in config
|
||
for (const row of acceptanceRates.rows) {
|
||
const rate = row.positive / row.total;
|
||
await updateScoringWeight({
|
||
capabilityId: row.capability_id,
|
||
accountTier: row.account_tier,
|
||
signalType: row.signal_type,
|
||
weight: rate
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.4 System-Level Prompt Refinement
|
||
|
||
Monthly: run an LLM analysis over all rejected briefs to identify patterns in false positives, and use those to refine the relevance filter system prompt. This is a manual step reviewed by a product/sales ops owner before being deployed.
|
||
|
||
---
|
||
|
||
## 7. Technical Implementation
|
||
|
||
### 7.1 Technology Stack
|
||
|
||
| Layer | Technology | Rationale |
|
||
|---|---|---|
|
||
| Language | TypeScript / Node.js | Matches existing stack; excellent async primitives |
|
||
| Orchestration | BullMQ (Redis-backed job queue) | Reliable job scheduling, retries, rate limiting |
|
||
| LLM | Anthropic Claude API (claude-sonnet-4-6) | Best-in-class reasoning for signal evaluation |
|
||
| Embeddings | `text-embedding-3-large` (OpenAI) or `voyage-3` (Anthropic) | High-quality semantic similarity |
|
||
| Vector DB | pgvector (PostgreSQL extension) | Avoids a separate vector DB if you're already on Postgres |
|
||
| Primary DB | PostgreSQL | Opportunity history, feedback, account memory |
|
||
| Cache / Dedup | Redis (Upstash or self-hosted) | Signal hash dedup, rate limiting, BullMQ |
|
||
| LinkedIn Data | Proxycurl + Apify | Compliant enrichment + post scraping |
|
||
| News/Web | Exa.ai | Semantic search over web, excellent B2B signal quality |
|
||
| Job Postings | JSearch API | Aggregates all major boards |
|
||
| CRM Integration | Salesforce / HubSpot REST API | Read account context, write back activity logs |
|
||
| Delivery | Slack API (Bolt SDK) + SendGrid | Notifications + digests |
|
||
| Hosting | Railway / Render / EC2 | Depends on scale; start with Railway for simplicity |
|
||
|
||
### 7.2 Project Structure
|
||
|
||
```
|
||
/sales-agent
|
||
├── /src
|
||
│ ├── /agents
|
||
│ │ ├── harvester.ts # Signal collection per-source
|
||
│ │ ├── normalizer.ts # Signal standardization
|
||
│ │ ├── relevanceFilter.ts # LLM relevance evaluation
|
||
│ │ ├── synthesizer.ts # Opportunity brief generation
|
||
│ │ └── scorer.ts # Composite scoring
|
||
│ ├── /capabilities
|
||
│ │ ├── library.ts # Capability library loader
|
||
│ │ ├── embedder.ts # Build + query capability embeddings
|
||
│ │ └── capabilities.json # The actual capability definitions
|
||
│ ├── /connectors
|
||
│ │ ├── linkedin.ts # Proxycurl + Apify
|
||
│ │ ├── news.ts # Exa.ai + NewsAPI
|
||
│ │ ├── jobPostings.ts # JSearch
|
||
│ │ ├── crm.ts # Salesforce / HubSpot
|
||
│ │ └── secEdgar.ts # SEC filing fetcher
|
||
│ ├── /delivery
|
||
│ │ ├── slack.ts # Slack Bolt app + message formatting
|
||
│ │ ├── email.ts # Daily digest via SendGrid
|
||
│ │ └── crmLogger.ts # Write-back to CRM
|
||
│ ├── /memory
|
||
│ │ ├── dedup.ts # Hash + semantic dedup
|
||
│ │ ├── accountMemory.ts # Per-account state management
|
||
│ │ └── feedback.ts # Feedback ingestion + storage
|
||
│ ├── /jobs
|
||
│ │ ├── accountCycle.ts # Main per-account orchestration job
|
||
│ │ ├── digest.ts # Daily digest job
|
||
│ │ └── recalibrate.ts # Weekly scoring recalibration
|
||
│ ├── /prompts
|
||
│ │ ├── relevanceFilter.ts # Prompt templates
|
||
│ │ └── synthesizer.ts
|
||
│ └── /api
|
||
│ ├── webhooks.ts # Slack feedback webhooks
|
||
│ └── dashboard.ts # REST API for web UI (optional)
|
||
├── /migrations # DB migrations
|
||
├── docker-compose.yml # Local dev: Postgres + Redis
|
||
└── .env
|
||
```
|
||
|
||
### 7.3 Main Orchestration Job
|
||
|
||
```typescript
|
||
// jobs/accountCycle.ts
|
||
import { Queue, Worker } from 'bullmq';
|
||
import { redis } from '../lib/redis';
|
||
import { runHarvesters } from '../agents/harvester';
|
||
import { normalizeAndDedup } from '../agents/normalizer';
|
||
import { filterForRelevance } from '../agents/relevanceFilter';
|
||
import { synthesizeOpportunities } from '../agents/synthesizer';
|
||
import { scoreAndPrioritize } from '../agents/scorer';
|
||
import { deliverBriefs } from '../delivery';
|
||
import { updateAccountMemory } from '../memory/accountMemory';
|
||
|
||
const accountQueue = new Queue('account-cycle', { connection: redis });
|
||
|
||
// Schedule all accounts
|
||
export async function scheduleAllAccounts(accounts: Account[]): Promise<void> {
|
||
for (const account of accounts) {
|
||
const delay = getDelayForTier(account.tier); // 24h / 48h / 7d
|
||
await accountQueue.add(
|
||
'run',
|
||
{ accountId: account.id },
|
||
{ repeat: { every: delay }, jobId: `cycle-${account.id}` }
|
||
);
|
||
}
|
||
}
|
||
|
||
// Worker
|
||
const worker = new Worker('account-cycle', async (job) => {
|
||
const account = await loadAccountContext(job.data.accountId);
|
||
|
||
// 1. Harvest
|
||
const rawSignals = await runHarvesters(account);
|
||
|
||
// 2. Normalize + dedup
|
||
const signals = await normalizeAndDedup(rawSignals, account);
|
||
if (signals.length === 0) return; // Nothing new
|
||
|
||
// 3. Relevance filter
|
||
const relevantSignals = await filterForRelevance(signals, account);
|
||
if (relevantSignals.length === 0) return;
|
||
|
||
// 4. Synthesize into opportunity briefs
|
||
const briefs = await synthesizeOpportunities(relevantSignals, account);
|
||
|
||
// 5. Score + prioritize
|
||
const scoredBriefs = await scoreAndPrioritize(briefs, account);
|
||
|
||
// 6. Deliver
|
||
await deliverBriefs(scoredBriefs, account);
|
||
|
||
// 7. Update memory
|
||
await updateAccountMemory(account, signals, scoredBriefs);
|
||
|
||
}, { connection: redis, concurrency: 5 });
|
||
```
|
||
|
||
### 7.4 Rate Limiting & API Cost Management
|
||
|
||
LLM calls are the primary cost driver. Key mitigations:
|
||
|
||
- **Capability pre-filter (vector search):** Reduces LLM calls by ~60% by only sending signals to the LLM when vector similarity suggests relevance
|
||
- **Signal batching:** Send up to 5 related signals in a single LLM call rather than one per call
|
||
- **Caching:** Cache LLM responses for identical signal content (rare but happens with news reposts)
|
||
- **Tier-based depth:** Strategic accounts get full LLM analysis; growth accounts get lighter-weight summarization
|
||
- **Estimated cost at scale:** ~$0.08–0.15 per account per daily run at full depth with Claude Sonnet
|
||
|
||
---
|
||
|
||
## 8. Implementation Roadmap
|
||
|
||
### Phase 1 — Foundation (Weeks 1–3)
|
||
- [ ] Set up PostgreSQL + Redis + pgvector
|
||
- [ ] Build Capability Library (start with top 5–10 capabilities)
|
||
- [ ] Implement Exa.ai news harvester (lowest integration friction)
|
||
- [ ] Build basic LLM relevance filter + synthesizer
|
||
- [ ] Slack delivery for a single test account
|
||
- [ ] Manual run (no scheduler yet) — validate output quality
|
||
|
||
### Phase 2 — Core Loop (Weeks 4–6)
|
||
- [ ] Add BullMQ orchestrator + account scheduler
|
||
- [ ] Add LinkedIn harvester (Proxycurl for profile enrichment, Apify for posts)
|
||
- [ ] Add job posting harvester
|
||
- [ ] Build dedup layer (hash + semantic)
|
||
- [ ] Add CRM read integration (pull account context)
|
||
- [ ] Wire up feedback buttons in Slack
|
||
|
||
### Phase 3 — Intelligence Layer (Weeks 7–9)
|
||
- [ ] Capability embedding index + pre-filter
|
||
- [ ] Composite scoring engine
|
||
- [ ] Account memory + suppression rules
|
||
- [ ] CRM write-back (log opportunities as activities)
|
||
- [ ] Daily digest email via SendGrid
|
||
- [ ] Feedback storage + rep preference tracking
|
||
|
||
### Phase 4 — Closed Loop (Weeks 10–12)
|
||
- [ ] Weekly scoring recalibration job
|
||
- [ ] Feedback-driven prompt refinement workflow
|
||
- [ ] Account narrative maintenance (rolling AI-generated account summary)
|
||
- [ ] Web dashboard (opportunity queue UI, signal feed)
|
||
- [ ] System health monitoring + alerting
|
||
- [ ] Win/loss retrospective analysis
|
||
|
||
---
|
||
|
||
## 9. Full Capability Delivery Summary
|
||
|
||
| Capability | Description |
|
||
|---|---|
|
||
| **Signal-Grounded Intelligence** | Every opportunity is tied to a real, datable source — no speculation |
|
||
| **Multi-Source Signal Fusion** | Combines LinkedIn, news, job postings, filings, and CRM into a unified account view |
|
||
| **Continuous Monitoring** | Runs automatically on configurable cadences — reps don't have to go looking |
|
||
| **Capability-Mapped Relevance** | Signals are evaluated against your specific product capabilities, not generic firmographic data |
|
||
| **Anti-Signal Filtering** | Knows what the account already has and excludes opportunities they've closed or that don't apply |
|
||
| **Executive-Level Targeting** | Identifies the right contact based on who posted, what role is relevant, and who's in the CRM |
|
||
| **Urgency-Aware Scoring** | Prioritizes by signal recency, account tier, buyer seniority, and rep feedback history |
|
||
| **Semantic Deduplication** | Avoids re-surfacing the same story across different sources or with different wording |
|
||
| **CRM Bidirectional Sync** | Reads context from CRM; writes opportunities back as activities with tasks and owners |
|
||
| **Slack-Native Delivery** | One-click action buttons surface priority briefs in the rep's existing workflow |
|
||
| **Daily Intelligence Digest** | Morning email/Slack summary of the full account queue ranked by priority |
|
||
| **Rep Feedback Loop** | Every rep action refines scoring weights — the system gets smarter over time |
|
||
| **Suppression & Snooze** | Reps control the noise level; suppression rules prevent irrelevant signals from recurring |
|
||
| **Win/Loss Retrospective** | Closed deals are traced back to their contributing signals for model improvement |
|
||
| **Account Narrative** | AI-maintained running summary of each account's strategic context, updated with each run |
|
||
| **Capability Coverage Heatmap** | View-level insight into which accounts have white space for which products |
|
||
| **Prompt + Weight Versioning** | All changes to scoring and prompts are versioned, so output regressions can be diagnosed |
|
||
| **Cost-Controlled LLM Usage** | Vector pre-filtering + batching keeps per-account LLM cost under $0.15/day |
|
||
|
||
---
|
||
|
||
## 10. Example Output Brief
|
||
|
||
```
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
🎯 OPPORTUNITY BRIEF
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
Account: Meridian Health Systems
|
||
Tier: Strategic
|
||
Assigned: Sarah K. (sarah.k@yourco.com)
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
|
||
OPPORTUNITY
|
||
Workflow Automation Suite — Revenue Cycle Expansion
|
||
|
||
HEADLINE
|
||
CFO signaling process automation push following system merger;
|
||
3 new operations director hires suggest accelerated build-out.
|
||
|
||
CONFIDENCE: 91% URGENCY: HIGH SCORE: 0.87
|
||
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
CONTRIBUTING SIGNALS
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
|
||
[1] LinkedIn Post — CFO Robert Vasquez (Jun 19)
|
||
"Post-merger, our biggest unlock is eliminating the manual
|
||
handoffs between clinical and revenue cycle. We're building
|
||
toward a fully automated claim-to-cash process by Q4."
|
||
Engagement: 218 likes, 61 comments
|
||
|
||
[2] Job Postings — 3 postings (Jun 10–17)
|
||
"Director of Revenue Cycle Operations" × 2
|
||
"VP, Claims Automation" × 1
|
||
All three mention "reduce manual intervention" and
|
||
"automate prior authorization workflows"
|
||
|
||
[3] Press Release (Jun 5)
|
||
Meridian completed merger with NorthStar Medical Group,
|
||
adding 4 hospitals and ~$800M in annual revenue. Integration
|
||
expected to take 18 months.
|
||
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
TALKING POINTS
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
|
||
1. Post-merger claim-to-cash automation is exactly where our
|
||
Workflow Automation Suite drives the fastest ROI — clients
|
||
avg 40% reduction in manual touchpoints within 90 days.
|
||
|
||
2. The NorthStar merger likely doubled their prior auth volume
|
||
overnight. Our PA automation module has handled 10M+
|
||
authorizations monthly for comparable health systems.
|
||
|
||
3. They're hiring for roles that will own this decision —
|
||
now is the time to shape the requirements before vendors
|
||
are evaluated.
|
||
|
||
4. We already have an integration with their current EHR
|
||
(Epic) — no rip-and-replace risk.
|
||
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
SUGGESTED OUTREACH
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
|
||
Target: Robert Vasquez, CFO (LinkedIn; not yet in CRM)
|
||
Diana Torres, SVP Revenue Cycle (in CRM — last contact 4mo ago)
|
||
|
||
Opener:
|
||
"Robert — your post about claim-to-cash automation resonated.
|
||
We helped [Comparable Health System] cut manual handoffs by
|
||
half after their own merger. Given the NorthStar integration
|
||
timing, worth a 20-minute call before you finalize your
|
||
vendor evaluation?"
|
||
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
[✅ Act on This] [⏰ Snooze 2 Weeks] [❌ Not Relevant]
|
||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
```
|
||
|
||
---
|
||
|
||
*Document Version 1.0 — Enterprise Sales Prospecting Agent Loop*
|