Inside the matching engine · 1 of 3

How a match happens.

A frozen snapshot of the candidate goes in at the top, a shortlist comes out at the bottom, and every job that falls out along the way is accounted for.

CONFIRMED PREFERENCES only what the candidate confirmed JOB CORPUS 141,897 real Djinni postings via Hugging Face MIT licence - verified by the build before ingest 1 · CONSTRAIN Hard rules checked against every job - out only on a proven violation the rule-keepers CANDIDATE EMBEDDING from the profile, at match time JOB EMBEDDINGS made once, at corpus ingest 2 · RECALL Meaning (60%) + keywords (40%), merged top 50 3 · SCORE Six weighted scores per job 4 · RANK Quality floor, two per company the honestly good 5 · EXPLAIN Reasons built from evidence THE SHORTLIST however many are truly good - even zero
The thing to notice: nothing slow happens at match time. I compute the job vectors long before, at ingest, and the candidate's own vector is one short call over the already-extracted profile - so a run mostly just compares numbers, and only the top 50 jobs ever get the full scoring pass. The corpus itself is real: 141,897 English postings from the Djinni recruitment platform, fetched through the Hugging Face API, and the build script verifies the MIT licence against the live dataset card before a single row is ingested. Ten hand-authored trap jobs sit alongside them, honestly tagged as synthetic, so the demo can show the system rejecting things for good reasons.

Inside the matching engine · 2 of 3

Where the vectors come from.

The dotted boxes from the first slide, zoomed in - two pipelines, one embedding model at the end of both. That shared model is the whole trick: it's what makes a job and a candidate comparable at all.

JOB CORPUS built once, offline - make corpus CANDIDATE CV captured once, at upload LICENCE GATE MIT, verified on the live dataset card - else it stops FETCH 500 real postings, drawn from 141,897 on Hugging Face TRAP CASES ten hand-authored jobs join in, tagged synthetic EXTRACT + CANONICALISE title, skills, comp, arrangement - one vocabulary CAPTURE original bytes kept and checksummed - never discarded TEXT the PDF becomes plain text EXTRACT roles, skills, education - each with an evidence span later, at match time QUERY TEXT titles and canonical skills only - identity stays out EMBED · ONE MODEL FOR BOTH SIDES bge-small-en-v1.5 turns each text into 384 numbers in the same space JOB EMBEDDINGS made at build time, shipped in the seed artifact, loaded into Postgres at boot - hash-verified CANDIDATE EMBEDDING made fresh each run from the extracted profile - one short text, so it costs almost nothing
Why the convergence matters: both pipelines end at the same model, so a job and a candidate land in the same 384-dimension space - and that's the only reason the distance between them means anything. Notice too that the candidate's vector is built from titles and canonical skills only, so a name, a school or a postcode can never drag a match around - fairness by construction, not by filter. Extraction uses the LLM when a key is present and falls back to heuristics when not, and both record an evidence span for every claim they make.

Inside the matching engine · 3 of 3

How we know it works.

Three layers of proof, and each answers a different question. Tests ask whether the code does what we claim. Evals ask whether the matching is actually any good. And the runtime record answers the question the other two can't: what actually happened on this run, for this candidate.

LAYER 1 · TESTS does the code do what we claim? 119 UNIT + PROPERTY pure engine - fast-check proves the invariants 22 INTEGRATION Testcontainers - fresh pgvector, migrated from empty 7 LLM CASSETTES recorded responses - injection fixture included 4 E2E JOURNEYS Playwright through the real wizard, no-key mode LOAD, MEASURED autocannon - cold and warm figures in the README LAYER 2 · EVALS is the matching actually good? 50 LABELLED CASES stratified, rationale committed, 20% double-labelled (κ 0.86) 9 METRICS precision, NDCG, false accepts, groundedness, unjudged@10… COMMITTED BASELINE the numbers live in git, next to the code they judge REGRESSION GATE make eval fails on a drop beyond tolerance - honest about noise its first run caught four real defects - the fixes took precision@3 from 0.17 to 0.40 LAYER 3 · THE RUNTIME RECORD what actually happened, provably? DURABLE - append-only by database trigger, a product feature TraceEvent why every job is in, or out SourceDocument original bytes - replay extraction OutcomeEvent labels accruing for the V2 ranker LlmCache the cache is the determinism EPHEMERAL - for operators, sampled and disposable STRUCTURED LOGS runId on every line - PII redacted, tested OTEL SPANS one per stage and LLM call - opt-in PROMETHEUS /metrics including dimension_unknown_total - honesty, counted
The layer worth dwelling on is the third, because it's the one users touch. Tests and evals run before anything ships - and the eval layer earned its keep immediately, catching four real defects on its first run, including a sponsorship trap that never triggered and a seniority score that compared bands across unrelated careers. But the runtime record is a product feature: TraceEvent means any candidate can ask why any job is or isn't in their list and get the actual decision path, SourceDocument means a bad extraction can be replayed from the original bytes, OutcomeEvent collects the training labels the V2 ranker will need, and LlmCache is what makes a repeated run deterministic. The dashed boxes are the only disposable part - and even there, a tested redaction formatter means a CV can never leak into a log line.