How I Built an AI Candidate Screener a Recruiter Can Overrule
By Ergini, Software & AI Developer
TL;DR
Xandidate, the applicant tracking system I built, began by ranking candidates with one match score, and nobody could say why a CV was a 71. I replaced it with the recruiter's rubric scored one criterion at a time, each level backed by a quote from the CV and 'unclear' as its own state, knockout rules in plain code, an override log that doubles as the test set, and proposed rejections a recruiter confirms in batches. It never rejects anyone by itself, in line with GDPR and the EU AI Act.
The question a number cannot answer
The first version of Xandidate gave every applicant a score out of a hundred. You opened a role, and there was the list, ranked: 91, 88, 84, down to the long tail in the forties. It looked like exactly what a busy recruiter wants. Four hundred applications, sorted, before lunch.
Then I did what I should have done first. I took a batch of CVs I had read and judged by hand, and compared my judgment with the list. The scores were not wildly wrong. They were something worse: unarguable. Why was this candidate a 71 and not an 80? The number had no answer. It was an average of things I could not see, weighed in a way I could not check.
And one question kept coming back, because it is the one every recruiter will eventually be asked: what would I tell the candidate who wants to know why they were not shortlisted? "The system gave you 62" is not an answer. In Europe, it is barely legal.
From one score to one quote per criterion
So the score went, and the rubric came in. For each role, the recruiter writes down what actually matters, in their own words: the must-haves, the things that count in a candidate's favor, and how much each weighs. The model's job changed completely. It no longer judges a person. It reads a CV against one criterion at a time, and for each one it returns a level (meets, partly meets, missing, unclear) and the exact line from the CV that supports it.
That last part is the whole design. A level without a quote is not accepted. If the CV says nothing about a requirement, the answer is "unclear", which is its own state, not a polite zero, and it goes to the recruiter as a question rather than a penalty. The hard knockout rules, such as a work permit or a required license, are not left to the model at all. They are plain code, readable by anyone.
Here is what one criterion looks like on a candidate's card:
| Field | What the recruiter sees |
|---|---|
| Criterion | Three or more years selling B2B software to mid-sized companies |
| Level | Partly meets |
| Evidence | "2022 to present: Account Manager, renewals and upsell for 40 SME customers" |
| Why partly | Account management with upsell, not new business; about three years |
| Recruiter override | Meets. Reason: "upsell to existing accounts counts for this role" |
The recruiter can disagree in two seconds, and the disagreement is not lost. That turned out to be the most valuable feature in the product.
The override log
Every time a recruiter changes a level, the change is written down: which criterion, from what to what, and a short reason. At first I built it as an audit trail, because a screener that touches hiring has to show that people really reviewed its output. It became much more than that.
Overrides cluster. When recruiters keep upgrading the same criterion, the rubric is written too narrowly, as in the card above, where "selling" was meant to include upsell. When they keep downgrading one, the model is reading something generously that the recruiter reads strictly. Either way the fix is visible, and it is usually a sentence in the rubric rather than a change to the model.
The log is also the test set. Whenever I change the prompt, the model or the rubric format, I replay past applications and check the new version against what recruiters actually decided. A change only ships if it agrees with the recruiters more often, not less. Nothing else I could have built would tell me that as honestly.
Why rejections go out in batches
Xandidate never rejects anyone by itself. That was never in question. The harder design problem was making human review real without making it miserable. A recruiter asked to confirm three hundred rejections one by one will, by the fortieth, be clicking without reading, and at that point the human in the loop is a formality.
So proposed rejections are grouped into a batch, and the batch screen is built to be read rather than clicked through. It leads with the candidates the screener was least sure about, shows the deciding criterion and its quote for each, and makes pulling one person out of the batch a single action. Only when the recruiter confirms the batch do any rejection emails leave. The review takes minutes, and it is still a review.
CVs that try to talk to the model
Any system that feeds documents to a model eventually meets documents written for the model. A CV with white text on a white background saying the candidate is an excellent fit. A footer in a one-point font with instructions to rank this application first.
The defense is structural, not a stern prompt. Before a CV reaches the model, the text layer is compared with what a person would actually see on the rendered page, and anything invisible is removed. The model can only fill in a fixed set of fields, so there is no instruction it could follow that would change the outcome. And a CV with hidden text is not rejected automatically either; it goes to the recruiter with the removed text shown, because some of it is just a badly exported file.
The rules I built it around
Screening is one of the areas where the law is clearest about AI. The EU AI Act lists systems that filter applications and evaluate candidates as high-risk, with those obligations now due from 2 December 2027. GDPR already restricts decisions based solely on automated processing. Xandidate was shaped by both from the start: candidates are told that AI assists the screening, names and photos can be hidden from reviewers, every score and decision is logged, and every rejection passes through a person.
If you hire at volume and want the same screening inside your own ATS, the design is in the blueprint for explainable candidate screening, and the engineering behind the rubric is in building a resume screener HR will trust. I build it as AI workflow automation on top of Greenhouse, Personio, Bullhorn or whatever your recruiters already live in.
Frequently asked questions
Why is a single AI match score a bad way to screen candidates?
Because nobody can argue with it. A recruiter who sees 71 cannot tell which requirement dragged it down, a candidate who asks why cannot be given an answer, and a model that weighed the wrong thing hides inside the average. Scoring each criterion separately, with the line from the CV that supports it, makes every judgment checkable and every mistake fixable.
Should an AI screener reject candidates automatically?
No. In the EU, GDPR restricts decisions based solely on automated processing, and the AI Act lists AI used to filter job applications as high-risk. A screener should propose, and a recruiter should confirm every rejection after seeing the evidence. Grouping proposed rejections into batches keeps that review real without turning it into three hundred separate clicks.
What is an override log in AI screening?
A record of every time a recruiter disagreed with the screener, what they changed and why. It shows that people genuinely reviewed the output, and it is the best test set you will ever have: when the rubric or the model changes, you replay past overrides and check that the new version agrees with the recruiters more often, not less.