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
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
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:
- 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.
- 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.
- 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.
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:
-
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) - Deduplicate results: The same job may appear in multiple search results. The system keeps only the highest-scoring instance of each unique job.
-
Exclude existing matches: Jobs that already have a
CandidateJobMatchrecord for this candidate are excluded. This prevents duplicate match cards in the pipeline. - 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.
- Validate company data: Each matched job must have a valid company name and company information. Jobs with missing company data are filtered out.
-
Create match records:
CandidateJobMatchrecords 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.
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:
The AI evaluates the match across four scoring dimensions:
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
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:
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
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:
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: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:
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:
All four weights must sum to 1.0. When you increase one weight, decrease others proportionally.
Fetch and Display Limits
The system has configurable limits for pipeline performance:Advanced
The Complete Pipeline Data Flow
Here is the full data flow from trigger to results, showing how data moves through each stage:How the Scoring Prompt Works
The AI scoring uses a carefully engineered prompt (CANDIDATE_JOB_SCORING_PROMPT) that instructs Gemini to:- Read the candidate’s CV text and confirmed skills
- Read the job description and requirements
- Evaluate four dimensions independently (role fit, skills fit, experience fit, secondary fit)
- Identify key matching points and potential concerns
- Produce a natural language explanation with evidence
- Return a structured JSON response
- 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: Whenrole_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 theEXCLUDED_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:
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
Related
- Understanding Scores — Learn how to interpret match scores and dimension breakdowns
- Candidate Pipeline — Track matches through your workflow
- Skills & Expertise — Improve match quality through accurate skills
- Location & Preferences — Configure geographic and preference filters

