Files
ProspectingAgentLoop/sales-prospecting-agent-loop.md
Chris Olson 39a338990f Initial commit: prospecting agent loop MVP
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>
2026-06-23 11:39:59 -04:00

885 lines
38 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 4872 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.01.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 | 721 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.450.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 1014) 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.080.15 per account per daily run at full depth with Claude Sonnet
---
## 8. Implementation Roadmap
### Phase 1 — Foundation (Weeks 13)
- [ ] Set up PostgreSQL + Redis + pgvector
- [ ] Build Capability Library (start with top 510 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 46)
- [ ] 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 79)
- [ ] 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 1012)
- [ ] 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 1017)
"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*