Skip to main content

Overview

Recruitier’s job matching engine is a multi-stage AI pipeline that takes a candidate’s complete profile — confirmed skills, experience, title, location, and preferences — and finds the most relevant jobs from a database of thousands of opportunities. It then scores and ranks every match using AI evaluation across four dimensions, producing detailed explanations that tell you exactly why each job is a good fit. The entire process runs in the background. You start it and continue working while the system handles searching, filtering, and scoring. Real-time progress updates are delivered to your browser via Server-Sent Events (SSE) as each stage completes. A typical matching run processes 50-200 jobs in under 2 minutes.

The 5-Stage Matching Pipeline

When matching is triggered for a candidate, the system executes five distinct stages in sequence. Each stage builds on the output of the previous one.
1

Stage 1: Embedding (Analyzing)

What happens: The system reads the candidate’s full profile and generates vector embeddings — mathematical representations of the candidate’s expertise that enable semantic search.The matching engine loads the candidate’s data from the database, including:
  • CV text (original from upload or synthetic from LinkedIn import)
  • Confirmed skills (only confirmed skills are used)
  • Job title and experience level
  • Location coordinates and search radius
  • Salary expectations and job type/flexibility preferences
It then generates three separate vector embeddings for the candidate’s profile:
VectorSource DataWhat It CapturesWeight in Search
Title vectorJob title and headlineRole alignment, seniority level, job function35%
Skills vectorConfirmed skill listTechnical capabilities and tool proficiency45%
Experience vectorSummary and CV textBroader professional context, domain knowledge, work complexity20%
These three vectors are stored in the Qdrant vector database for fast similarity comparison. If the candidate has been embedded before (is_embedded = true), existing vectors are used unless the profile has changed. If this is the first embedding or the profile has been updated, fresh vectors are generated by calling the recommendation service.Why three vectors instead of one? A single embedding would blur the signal. By separating title, skills, and experience, the system can independently evaluate different aspects of fit. A candidate might have a perfect skills match (Python, FastAPI, PostgreSQL) for a job that has a very different title (“Platform Engineer” vs. “Backend Developer”). The three-vector approach catches these nuanced matches that a single-vector system would miss.
Vector embeddings are high-dimensional mathematical representations (typically 384-768 dimensions) where similar meanings are close together in vector space. “Python Developer” and “Python Engineer” produce vectors that are very close together, even though the exact words are different. “Python Developer” and “Marketing Manager” produce vectors that are far apart.
2

Stage 2: Generating Search Queries

What happens: The AI generates multiple diverse search strategies based on the candidate’s profile.Rather than running a single search, the system asks the AI (Gemini) to create up to 15 different search query sets. Each query set targets a different angle of the candidate’s capabilities:
  • Direct title matches — Jobs with the same or similar title
  • Skill-based searches — Jobs that require the candidate’s key skills in combination
  • Alternative roles — Related positions the candidate could fill based on their skill set
  • Industry variations — The same role in different sectors or company types
  • Seniority variations — Similar roles at slightly different levels
Example: A “Senior Python Developer” with skills in FastAPI, PostgreSQL, and Docker might generate:
Query #Search StrategyExample Query
1Direct title”senior python developer”
2Skill combination”python backend engineer fastapi”
3Framework focus”python developer django fastapi”
4Alternative title”software engineer python”
5Infrastructure angle”python developer docker kubernetes”
6Data angle”python developer postgresql data”
7Lead variation”lead python developer”
8Full-stack angle”full-stack python developer”
Why multiple queries matter: A single search query can miss jobs that use different terminology for the same role. “Backend Engineer”, “Software Developer”, “Platform Engineer”, and “Application Developer” might all describe positions that a Senior Python Developer would be perfect for. Multiple search strategies cast a wider net and catch opportunities that keyword-only or single-query searching would miss.
3

Stage 3: Searching (Vector Search)

What happens: The system executes parallel vector (semantic) searches across the entire job database, applying preference-based filters.This is where the three-vector matching architecture comes into play. For the candidate’s profile, the system runs three parallel vector searches against the job database:
  1. Title-to-Title search (35% weight) — The candidate’s title vector is compared against every job’s title vector using cosine similarity. This finds jobs with similar role descriptions regardless of exact wording. “Senior Python Developer” matches “Python Backend Engineer” because they are semantically similar.
  2. Skills-to-Skills search (45% weight) — The candidate’s skills vector is compared against every job’s skills vector. This finds jobs requiring similar technical capabilities. A candidate with [Python, FastAPI, PostgreSQL] matches a job requiring [Python, Django, MySQL] because the skill sets overlap significantly in the vector space.
  3. Experience-to-Description search (20% weight) — The candidate’s experience vector is compared against every job’s full description vector. This captures broader context like domain knowledge, industry experience, work complexity, and organizational expectations.
Each search also applies filters based on the candidate’s preferences and data quality requirements:
FilterPurposeApplied When
is_activeOnly active, current job listingsAlways
Recency (6 months)Only jobs posted within the last 6 monthsAlways
Company nameExclude jobs without a valid company nameAlways
Industry exclusionExclude staffing and recruiting agenciesAlways
Experience levelMatch candidate’s level (junior/medior/senior/lead)When set on candidate
Location + radiusExclude jobs beyond 2x the preferred radiusWhen candidate has coordinates
Job typeOnly matching types (full-time, part-time, contract)When preferences set
FlexibilityOnly matching arrangements (on-site, hybrid, remote)When preferences set
The search uses a fetch limit of 400 — it retrieves up to 400 candidate jobs from the vector database. This is intentionally larger than the display limit of 200 to provide a buffer for post-filtering (deduplication, existing match exclusion, etc.).Why semantic search outperforms keyword search: A job posting for a “Python Backend Engineer” will match a candidate titled “Senior Python Developer” even though the exact phrase differs. A job requiring “cloud infrastructure experience” will match a candidate whose CV describes “deploying applications on AWS EC2 and ECS” because the embedding model understands these are semantically related. This is dramatically better than keyword matching, which would miss these connections.
4

Stage 4: Processing (Match Creation)

What happens: The system collects results from all searches, removes duplicates, and creates match records in the database.Since multiple search queries and three parallel vector searches may find the same job, the system performs several processing steps:
  1. Merge vector scores: For each job found, the three vector similarity scores are combined using the weighted formula: combined_score = (title_score * 0.35) + (skills_score * 0.45) + (experience_score * 0.20)
  2. Deduplicate results: The same job may appear in multiple search results. The system keeps only the highest-scoring instance of each unique job.
  3. Exclude existing matches: Jobs that already have a CandidateJobMatch record for this candidate are excluded. This prevents duplicate match cards in the pipeline.
  4. Exclude protected matches: Jobs that are in protected statuses (favorited, applied, contacted, interviewing, offer stage, placed) are identified and their IDs are passed to the exclude list. These matches are NEVER deleted or replaced during re-matching.
  5. Validate company data: Each matched job must have a valid company name and company information. Jobs with missing company data are filtered out.
  6. Create match records: CandidateJobMatch records are created in the database with the initial vector similarity scores and a “pending” status. These records link the candidate to specific jobs and will be updated with AI scores in the next stage.
Why deduplication matters: Without it, a job found by 3 different search queries would appear 3 times in your pipeline. The deduplication ensures each opportunity appears exactly once, with the best available vector similarity score.
Protected matches are sacrosanct. If you have favorited a job or started interviewing for it, that match will never be removed during re-matching. This is a core design principle — your pipeline progress is always preserved.
5

Stage 5: Scoring (AI Evaluation)

What happens: Every matched job is individually evaluated by Gemini, which produces a detailed score breakdown and natural language explanation.For each job match, the AI receives a carefully constructed prompt containing:
InputSourceCharacter Limit
Candidate CV textcv_text field (original or synthetic)Up to 8,000 characters
Candidate confirmed skillsskills_with_confidence (confirmed only)Full list
Candidate experience levelexperience_level fieldSingle value
Job titleFrom the matched jobFull text
Company nameFrom the matched job’s companyFull text
Job locationFrom the matched jobFull text
Job descriptionFull description from the job listingUp to 6,000 characters
The AI evaluates the match across four scoring dimensions:
DimensionDefault WeightWhat It Evaluates
Role Fit30% (weight_role)How well the candidate’s background aligns with the role requirements. Career trajectory, seniority match, role type alignment.
Skills Fit35% (weight_skills)How many required skills the candidate possesses. Identifies matching and missing skills.
Experience Fit20% (weight_experience)Whether the candidate’s years and type of experience match expectations. Industry relevance, project complexity.
Secondary Fit15% (weight_secondary)Additional factors: education alignment, certifications, industry knowledge, language requirements, culture fit indicators.
Each dimension receives a score from 0 to 1. The AI also produces:
  • Key matching points — The strongest reasons the candidate fits this role (displayed as green badges on match cards)
  • Potential concerns — Issues to consider, such as missing skills or experience gaps (displayed as orange badges)
  • Natural language explanation — A summary of why this match is good or not, with specific evidence from both the CV and job description
  • Recommendation — “apply”, “consider”, or “skip” based on the final score
After AI scoring, the location penalty is applied as a multiplicative factor (see Location & Preferences for the formula). Jobs are then ranked by their final score, and ranks are assigned (1 = best match).Concurrent scoring for speed: To keep matching fast even with hundreds of potential jobs, the AI scoring runs concurrently — up to 50 jobs are scored simultaneously using parallel API calls. This concurrency level is tuned to stay within Gemini’s rate limit of 500 requests per minute. Each individual scoring call has a 90-second timeout to prevent any single slow evaluation from blocking the entire batch.A typical matching run scores 50-200 jobs in under 2 minutes.
Critical mismatch caps: If a scoring dimension is extremely low, the overall score is capped to prevent misleadingly high scores:
  • Role Fit below 0.30: Overall score capped at 35% — even great skills cannot compensate for a completely wrong role
  • Skills Fit below 0.30: Overall score capped at 45% — a strong role match does not help if the candidate lacks the core skills
These caps ensure that fundamental mismatches are always surfaced, regardless of how well other dimensions score.

The Technology Behind Matching

Vector Embeddings Explained

At the core of Recruitier’s matching is vector embedding technology. Text (job titles, skill lists, descriptions) is converted into high-dimensional mathematical vectors where similar meanings are close together in vector space. This means “Python Developer” and “Python Engineer” produce vectors that are very close together, even though the words are different. “Python Developer” and “Java Developer” are further apart but still closer than “Python Developer” and “Marketing Manager”. The distance between vectors represents semantic similarity.

Three-Vector Architecture

Rather than using a single embedding for the whole profile, Recruitier uses three separate vectors with different weights:
VectorWeightRationale
Title35%The job title is the most direct signal for role alignment. It captures seniority (Senior, Junior, Lead), domain (Backend, Frontend, Full-Stack), and primary function (Developer, Engineer, Architect).
Skills45%Skills are the strongest predictor of technical fit. They carry the most weight because a great title match is meaningless if the skills do not align. A “Senior Developer” without the right technology stack is not a fit.
Experience20%The full experience description provides “soft signals” — domain knowledge, company types, project complexity, industry context. It is noisier than skills or title but adds useful context that the other vectors miss.
This weighted approach outperforms single-vector matching because it allows the system to separately evaluate different aspects of fit and combine them intelligently. Research shows that multi-vector approaches improve recall by 15-25% compared to single-vector methods.

Concurrent AI Scoring Architecture

The scoring stage uses a concurrent execution model to maximize throughput:
  • Batch size: Up to 50 concurrent AI scoring calls
  • Rate limit: Stays within Gemini’s 500 RPM (requests per minute) limit
  • Timeout: Each individual scoring call has a 90-second timeout
  • Error handling: Failed scoring calls are logged but do not block other scores. A job that fails to score receives a default handling rather than crashing the entire batch.
  • Typical throughput: 50-200 jobs scored in under 2 minutes
This means that even for candidates with very broad skill sets or large search radii that produce many vector matches, the scoring completes quickly.

Real-Time Progress Updates

During matching, you receive live progress updates directly on the candidate detail page. A progress panel shows each stage with real-time status indicators:
StageLabelDescription
AnalyzingAnalyzing ProfileReading the candidate’s skills and experience
GeneratingCreating QueriesBuilding search parameters for the job database
SearchingFinding JobsScanning databases for matching opportunities
ProcessingProcessingDeduplicating and preparing match records
ScoringAI ScoringEvaluating each match for quality and fit
Each stage shows one of three states: completed (checkmark), currently running (spinner), or pending (circle). The panel also shows a description of what the current stage is doing and a note that matching usually takes less than a minute. The progress updates are delivered via Server-Sent Events (SSE) using Redis pub/sub. You do not need to refresh the page. You can watch the matching progress in real time, navigate away and come back later, or work on other candidates while matching runs in the background.

When Matching Runs

Matching is triggered automatically in several scenarios:
TriggerTypeWhat Happens
After CV uploadAutomaticFull 5-stage pipeline runs after the candidate is created and skills are confirmed
After LinkedIn importAutomaticFull 5-stage pipeline runs after the profile is imported
After skill changeAutomaticIf confirmed skills changed (added/removed), re-matching is triggered
After location changeAutomaticChanges to location, coordinates, or radius trigger re-matching
On login (incremental)AutomaticTop 5 candidates matched against 50 new jobs each, with 6-hour cooldown
Manual triggerOn demandYou can manually start matching from the candidate profile at any time

Re-Matching Behavior

When matching runs again for a candidate who already has matches:
  • Protected matches are ALWAYS preserved: Jobs in these statuses are never removed:
    • Favorited
    • Applied
    • Contacted
    • Interviewing
    • Offer Stage
    • Placed
  • Pending matches may be replaced: Unscored or unacted-upon matches (in “New Matches” status) may be replaced with fresh results
  • New matches are added: Jobs that were not in the previous results are added as new matches
  • Exclude list: Protected match job IDs are passed to the matching pipeline so it does not waste resources re-scoring jobs you have already engaged with
Re-matching is designed to be safe. It will never erase your pipeline progress. Only “pending” matches that you have not interacted with are affected. This means you can confidently update a candidate’s skills or location knowing that your favorited, contacted, and interviewing matches are safe.

Incremental Matching on Login

To keep match results fresh without manual intervention, Recruitier runs incremental matching when you log in:
ParameterValue
TriggerUser login
Cooldown6 hours (does not run if last run was less than 6 hours ago)
Candidates processedTop 5 candidates (by activity or priority)
Jobs per candidateUp to 50 new jobs
SSE eventslogin_matching_started, incremental_matching_success, login_matching_completed
This means that every morning when you log in, your most active candidates automatically get matched against jobs that were posted overnight or recently added to the database.

Matching Configuration

Scoring Weights

The default scoring weights can be customized per candidate through the candidate profile:
WeightDefaultConfigurableUse Case for Adjustment
weight_role0.30YesIncrease for roles where industry/domain alignment matters most
weight_skills0.35YesIncrease for highly technical roles (ML Engineer, DevOps)
weight_experience0.20YesIncrease for leadership or management positions
weight_secondary0.15YesIncrease for roles where education/certifications are critical
All four weights must sum to 1.0. When you increase one weight, decrease others proportionally.
For most technical roles, the default weights work well. Only adjust weights when you notice that the standard scoring is not capturing what matters most for a specific type of role. Common adjustments:
  • Career changers: Decrease role weight (0.15), increase skills weight (0.50)
  • Leadership positions: Increase experience weight (0.30), decrease skills weight (0.25)
  • Regulated industries: Increase secondary weight (0.25) to emphasize certifications and compliance

Fetch and Display Limits

The system has configurable limits for pipeline performance:
LimitDefaultPurpose
Fetch limit400Maximum jobs retrieved from vector search (buffer for filtering)
Display limit200Maximum matches shown to the user after all processing
Minimum score0.50Jobs below this threshold receive a “skip” recommendation

Advanced

The Complete Pipeline Data Flow

Here is the full data flow from trigger to results, showing how data moves through each stage:
Trigger (CV upload, skill change, location change, manual, login)
    |
    v
Stage 1: Embedding
    Input: candidate.title, candidate.skills (confirmed), candidate.cv_text
    Process: Generate 3 vectors via recommendation service
    Output: Title vector, Skills vector, Experience vector -> stored in Qdrant
    |
    v
Stage 2: Query Generation
    Input: Candidate profile (title, skills, summary, experience level)
    Process: Gemini generates up to 15 diverse search query sets
    Output: Array of search query strings
    |
    v
Stage 3: Vector Search
    Input: 3 candidate vectors + query strings + filter criteria
    Process: Parallel searches in Qdrant with filters
    Output: Up to 400 job candidates with vector similarity scores
    Filters applied: is_active, recency, company name, industry exclusion,
                     experience level, location/radius, job type, flexibility
    |
    v
Stage 4: Match Creation
    Input: Raw search results (potentially duplicated across queries)
    Process: Merge scores (35%/45%/20%), deduplicate, exclude existing matches,
             exclude protected matches, validate company data
    Output: CandidateJobMatch records in database (pending status)
    |
    v
Stage 5: AI Scoring
    Input: Each match's candidate CV (8K chars) + confirmed skills +
           job description (6K chars) + job metadata
    Process: 50 concurrent Gemini calls, 90s timeout each
    Output: Per-match scores (role_fit, skills_fit, experience_fit, secondary_fit),
            key matching points, potential concerns, explanation, recommendation
    Post-process: Apply location penalty, calculate final score, assign ranks
    |
    v
Result: Ranked list of up to 200 scored matches with full explanations
    Delivered via: SSE notification to browser

How the Scoring Prompt Works

The AI scoring uses a carefully engineered prompt (CANDIDATE_JOB_SCORING_PROMPT) that instructs Gemini to:
  1. Read the candidate’s CV text and confirmed skills
  2. Read the job description and requirements
  3. Evaluate four dimensions independently (role fit, skills fit, experience fit, secondary fit)
  4. Identify key matching points and potential concerns
  5. Produce a natural language explanation with evidence
  6. Return a structured JSON response
The prompt is designed to produce consistent, calibrated scores. The AI is instructed to:
  • Use the full 0-1 range (not cluster around 0.5)
  • Provide specific evidence from both the CV and job description
  • Flag fundamental mismatches explicitly
  • Consider both required and preferred qualifications separately

Critical Penalty System

The critical mismatch caps serve as a safety net against score inflation: Role Fit cap: When role_fit < 0.30, the overall score is capped at 0.35 (35%). This prevents a situation where a Marketing Manager with Python hobby projects gets an 80% match for a Senior Python Developer role just because their skills overlap. Skills Fit cap: When skills_fit < 0.30, the overall score is capped at 0.45 (45%). This prevents a situation where a Senior Java Developer gets a high score for a Python/FastAPI role just because their seniority and experience align. These caps ensure that fundamental mismatches in either role or skills always result in a low overall score, regardless of how well other dimensions score.

Staffing Agency Exclusion

The matching pipeline automatically excludes jobs posted by staffing and recruiting agencies. This is a deliberate business rule: recruiters using Recruitier do not want to match their candidates to competitor agencies’ job listings. The exclusion is based on the EXCLUDED_CLIENT_INDUSTRIES list, which currently includes “Staffing and Recruiting”. Companies with this industry classification in the global_company table are excluded from search results. Companies without industry data (NULL) are still included — only explicitly excluded industries are filtered out.

SSE Event Flow During Matching

The matching pipeline publishes the following SSE events through Redis pub/sub:
EventWhenData
matching_startedPipeline beginscandidate_id, candidate_name
matching_stage_updateEach stage transitionstage_name, stage_number, total_stages, status_message
matching_completedPipeline finishescandidate_id, total_matches, top_score
login_matching_startedIncremental matching begins on loginuser_id
incremental_matching_successOne candidate’s incremental match finishescandidate_id, new_matches_count
login_matching_completedAll incremental matching finishestotal_candidates_processed
The frontend receives these via the SSEConnectionManager and updates the UI in real time — progress indicators, match counts, and notifications are all driven by these events.

Power-User Tips

Trigger manual re-matching after weight adjustments. If you change a candidate’s scoring weights (e.g., increasing skills weight for a technical role), the existing match scores were calculated with the old weights. Trigger a manual re-match to recalculate all scores with the new weight configuration.
Use the stage-by-stage progress to diagnose issues. If matching is slow at the “Searching” stage, the candidate may have a very broad profile generating too many vector matches. If it is slow at “Scoring”, there are many jobs to evaluate. If it completes quickly but produces few matches, the candidate’s preferences may be too restrictive.
For candidates with unusual profiles, consider adjusting weights before running the first match. A career changer from finance to tech with strong Python skills should have their role weight decreased and skills weight increased BEFORE the first matching run. This saves a re-match and produces better initial results.