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

38 KiB
Raw Blame History

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:

{
  "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)

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.

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.

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:

{
  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:

{
  "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.

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

// 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

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.

// 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

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:

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

// 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