The scoring engine ranks companies already in your CRM. But what about companies you havenβt found yet? The Discovery Engine searches for new prospects that match your winning profile β and deliberately explores outside that profile to prevent tunnel vision.
The Explore/Exploit Dilemma
This is a fundamental problem in machine learning and decision theory: should you exploit what you know works, or explore something new?
Exploit only: You find more companies exactly like your past winners. Safe, predictable, but youβll never discover new markets. If your winning profile says β100-200 person SaaS companies in North America,β youβll never find that 500-person European fintech thatβs actually a great fit.
Explore only: You cast a wide net, testing every possible company type. Youβll discover new segments, but waste enormous time on bad-fit prospects. Most explorations fail.
The solution: 80/20 split. 80% of discovered companies match your winning profile (exploit). 20% are deliberate experiments outside your profile (explore). This is inspired by Googleβs β20% timeβ and the multi-armed bandit problem in statistics.
How Discovery Works
// src/domain/scoring/services/discovery/mlProspectDiscoveryService.ts (simplified)
async discoverProspectsForUser(options: DiscoverProspectsOptions): Promise<DiscoveryResult> {
// 1. Get/build winning profile from closed deals
const winningProfile = await mlFitScoreService.getOrBuildWinningProfile(options.userId);
if (!winningProfile || winningProfile.confidence === 'insufficient') {
return {
success: false,
error: 'Insufficient deal data. Need at least 5 closed deals for discovery.',
};
}
// 2. Get existing domains to exclude (don't discover companies already in CRM)
const existingDomains = await getExistingDomains(options.userId);
// 3. Build LLM prompt from winning profile
const promptContext = await this.buildPromptContextFromPatternAnalyzer(
options.userId, winningProfile, config, existingDomains, options
);
// 4. Run discovery strategies in parallel
// ... 7 strategies, each using Groq LLM
}The Safety Check: Minimum 5 Closed Deals
Discovery requires at least 5 closed deals. Why? The winning profile is built from statistical analysis (weighted means, standard deviations, embedding centroids). With fewer than 5 data points, the statistics are unreliable β your βwinning profileβ might just be random noise. Five deals is the minimum for a meaningful pattern.
Domain Deduplication
Before running discovery, we load every domain already in the userβs CRM. Discovered companies are filtered against this set to prevent duplicates. If βacme.comβ is already in your pipeline, the LLM wonβt suggest it again.
The Seven Discovery Strategies
Discovery runs seven strategies, each targeting a different dimension:
Exploit Strategies (80%)
1. Profile Matches β Companies that closely match your winning profile across all dimensions (industry, size, geography).
2. Goldilocks Matches β Companies that would score Composite β₯ 70 based on available signals. These are the βhigh probabilityβ prospects.
Explore Strategies (20%)
3. Smaller Companies β Companies 50-75% of your typical deal size. Tests whether your solution works at a lower price point.
4. Larger Companies β Companies 150-300% of your typical deal size. Tests whether you can sell upmarket.
5. Adjacent Industries β Companies in industries related to but not identical to your winners. If you win in βComputer Systems Design,β this explores βData Processingβ and βIT Consulting.β
6. New Geographies β Companies in regions where you havenβt closed deals. If all your wins are in North America, this explores European or APAC companies.
Each explore strategy has a minimum of 2 companies β enough to test the segment without over-investing:
const MIN_PER_CATEGORY = 2; // Minimum 2 per exploration category
const EXPLORE_OVERFETCH = 4; // Request 4, expect 2 after filteringWe request 4 and keep 2 because the LLM sometimes suggests duplicates or companies that fail domain verification.
The LLM Prompt
The discovery engine uses Groq (Llama 3.1/3.3) to generate prospect lists. The prompt is built from your winning profile:
You are a B2B sales intelligence expert. Find companies matching this profile:
WINNING PROFILE:
- Top industries: Computer Systems Design (45% of wins), Software Publishing (30%)
- Employee range: 50-300 (sweet spot: 150)
- Revenue range: $5M-$50M (sweet spot: $20M)
- Top regions: California (35%), New York (20%), Texas (15%)
- Average deal value: $85,000
- Win rate: 42%
EXCLUDE these domains (already in CRM):
acme.com, bigcorp.io, techstart.com, ...
Return 10 companies matching this profile. For each, provide:
- Company name, domain, industry, employee count, revenue range
- Why they match the winning profile
- A confidence score (0-100)
Respond in JSON format.The LLM returns structured JSON (using Groqβs JSON mode), which is parsed, validated, and scored before being stored.
Tracking Exploration Outcomes
The explore/exploit split isnβt static. It adapts based on results:
-- exploratory_segments table tracks each exploration
exploratory_segments (20 cols)
βββ segment_type -- e.g., 'industry', 'employee_size', 'geography'
βββ segment_value -- e.g., 'Fintech', '>500 employees', 'Europe'
βββ times_shown INT -- How many times this segment was presented
βββ times_acted_on INT -- How many times the user engaged
βββ deals_created INT -- How many deals came from this segment
βββ deals_won INT -- How many were won
βββ deals_lost INT -- How many were lost
βββ total_deal_value NUMERIC
βββ status -- 'exploring', 'absorbed', 'abandoned'
βββ confidence_score NUMERICEach exploration segment accumulates data over time:
- Exploring β The system is still testing this segment
- Absorbed β The segment proved successful and is now part of the winning profile (the ICP expands)
- Abandoned β After enough data, the segment clearly doesnβt work (too many losses or no engagement)
Bell Curve Shifts
When an exploratory segment is βabsorbed,β it shifts the winning profile:
-- bell_curve_shifts table records how the ICP changed
bell_curve_shifts (12 cols)
βββ shift_type -- e.g., 'expansion', 'contraction'
βββ dimension -- e.g., 'employee_size', 'industry'
βββ previous_range JSONB -- { min: 50, max: 300 }
βββ new_range JSONB -- { min: 50, max: 500 } (expanded!)
βββ trigger_reason -- e.g., "3 deals won in 300-500 employee segment"
βββ supporting_deals INT -- How many deals support this shiftIf you start exploring larger companies (300-500 employees) and win 3 deals there, the system proposes expanding your ICPβs employee range from 50-300 to 50-500. The βbell curveβ of your winning profile literally shifts to accommodate the new data.
This is the learning loop in action:
- Winning profile β Discovery finds companies
- Some are explored (outside the profile)
- User converts some explores to deals
- Deals close β Winning profile updates
- Updated profile β Better discovery
The Discovery Pipeline
Discovery results go through a pipeline before reaching the user:
LLM generates companies
β Domain verification (does the domain actually exist?)
β Dedup against existing CRM data
β Score each company (quick fit estimate)
β Filter by minimum confidence (>50)
β Store in discovery_results table
β Present in "Ready to Engage" queueThe discovery_results table tracks the lifecycle:
discovery_results (37 cols)
βββ status DEFAULT 'new' -- new β viewed β saved/dismissed/converted
βββ viewed_at -- When the user first saw it
βββ saved_at -- When saved for later
βββ dismissed_at -- When rejected
βββ dismiss_reason TEXT -- "Bad fit", "Already contacted", etc.
βββ converted_at -- When added to the CRM pipeline
βββ ml_fit_score NUMERIC -- Quick fit estimate at discovery time
βββ discovery_source -- 'groq_llm', 'web_search', etc.
βββ explored_segment -- Which explore category, if any
βββ explore_category -- 'smaller', 'larger', 'adjacent_industry', etc.
βββ deviation_reason TEXT -- Why this deviates from the profileThe Ready to Engage Queue
Discovered companies surface in the Command Center as a prioritized queue:
βββββββββββββββββββββββββββββββββββββββββββ
β Ready to Engage β
β β
β β
CloudTech Solutions Fit: 87 New β
β Computer Systems Design, 200 emp β
β "Strong profile match: industry, β
β size, and geography align" β
β [Save] [Dismiss] [Add to Pipeline] β
β β
β β FinanceAI Corp Fit: 72 Explore β
β Financial Software, 450 emp β
β "Testing larger company segment" β
β [Save] [Dismiss] [Add to Pipeline] β
β β
βββββββββββββββββββββββββββββββββββββββββββProfile matches (β ) are shown first. Explore picks (β) are labeled so the user knows theyβre experimental. The dismiss and save actions feed back into the segment tracking system.
Key Takeaways
-
80/20 explore/exploit balances known winners with deliberate experimentation. Without exploration, your pipeline ossifies. Without exploitation, itβs random.
-
Seven strategies cover profile matches, Goldilocks prospects, and four exploration dimensions (size up, size down, adjacent industries, new geographies).
-
Minimum 5 closed deals are required. The winning profile is statistical β it needs data.
-
Exploration outcomes are tracked. Successful experiments get absorbed into the ICP. Failed ones are abandoned. The system learns over time.
-
Bell curve shifts are the payoff β the winning profile literally expands when explorations prove successful. Discovery makes your scoring smarter over time.
Next chapter: we leave the scoring engine and enter the integrations layer β starting with how Astrelo connects to HubSpot through OAuth.