Field Notes · Under the Hood · ~15 min read
the devil is in the details
Last time I gave the honest, plain-language version of what a human capital intelligence platform actually does. A bunch of you asked the obvious follow-up: okay, but how? What literally happens to my PDF? This is that post — one résumé, followed all the way down to the one screen it was all for.
In the last post I said a human capital intelligence platform reads every résumé and turns the messy flood of PDFs into a clean, structured thing you can ask questions of. Which is true. But it's also the kind of sentence that's easy to say and hard to believe, because "reads it" is doing an enormous amount of work in that sentence.
So let's actually open it up. I'm going to take one résumé and follow it from the moment it lands to the moment a recruiter opens the thing I think of as the whole point — the candidate dashboard. Twelve stages. I'll try to make each one concrete, and I'll tell you which ones are boring (most of them) and which one I'd put on a poster (one of them).
Before we start, here are the two ideas that run underneath every stage. If you only remember two things, remember these, because they're the difference between a real platform and a confident-sounding black box:
1. Every claim points at a sentence. Nothing the system asserts about a candidate is allowed to float free. "Knows Python" has to come with the exact line on the exact page it came from. No span, no claim.
2. When in doubt, it stops. Every gate defaults to blocking, not passing. Missing data means "we don't know," not "looks fine." A confident wrong answer about someone's career is far more expensive than an honest "we need more evidence here."
Okay. Here's the whole journey at a glance, grouped into four acts. We'll walk through each one.
act 1the boring stuff that holds everything up
Stages 1 through 4 are aggressively unexciting, and I want to spend a minute on them precisely because nobody markets them. They answer four dumb questions, in this exact order: what is this document, who does it belong to, have we seen it before, and what does the page actually say. Get any one of these wrong and every clever thing downstream is built on sand.
stage 1 — give it an identity
First thing that happens: the file gets a tenant_id (so candidate data can never leak between customers — this is a hard wall, not a filter), and the raw bytes of the PDF get hashed into a SHA256 fingerprint. That hash becomes the document's permanent identity. It follows the résumé through every later stage, and right at the end it's what lets an auditor prove the bytes we scored are the same bytes we kept. Provenance starts at byte one.
stage 2 — don't pay twice for the same person
Duplicates happen constantly at any real scale. Candidates apply twice because they never got a confirmation email. A recruiter uploads the same CV to two reqs. A LinkedIn pull overlaps a JobStreet pull. If you naively re-process every duplicate, two bad things happen: you pay Google twice for the same person, and that person lands in two recruiters' queues with two slightly different scores. So stage 2 checks at two levels — exact same PDF (byte match, short-circuit instantly) and same human, different file (email + name + phone, with a confidence score). A recruiter can force a re-score if they actually want one. But that override is logged, like everything else.
stage 3 — read the page, not just the text
Here's a thing that took me a while to properly appreciate. A PDF is technically just a set of instructions for where to put ink on a page. Generic OCR happily gives you the text back — as one giant undifferentiated blob. And for a résumé, the structure is half the meaning. "This is in the Experience section." "This is a sidebar." "This is the left column of a two-column layout." Lose that and you've thrown away the context that tells you what the words mean.
So this stage uses Google Document AI rather than plain OCR, and gets back text plus a map of where each block sits on each page. Later stages get to say precise things like "the candidate's degree appears in block 7 of page 2" — which is exactly the kind of pointer rule #1 needs.
stage 4 — strip the noise, keep every word the candidate wrote
Résumés are noisy. Footers repeat on every page. "Page 2 of 5" interrupts sentences. Confidentiality stamps reappear. Stage 4 removes that junk — but cautiously, on purpose. The rule here is blunt: the candidate's words are sacred; "Page 2 of 5" is not. Anything stripped is recorded, so "what did you remove?" has an answer too.
act 2where the AI actually earns it
Stages 5, 6, and 7 are where the model does the real work — and also where most résumé tools either stop or quietly fall over. The thing I got wrong for an embarrassingly long time was treating the model like a chatbot: asking nicely for structured output and hoping. The fix was treating it like a typed API instead.
stage 5 — turn prose into a structured object
This is the stage where the résumé stops being text and becomes a typed thing — positions, dates, employers, education, certs, languages, summary, all as structured fields. The detail that matters: we use Gemini's response_schema mode, which means the model literally cannot return free-form prose. It can only fill in the fields the schema defines. That one constraint kills an entire category of "the model said something weird" bugs.
And rule #1 lives here: every extracted claim has to carry an evidence_span pointing back at the source text. If the model wants to assert something it can't ground in a passage, that's treated as a hallucination and gated out. Concretely:
stage 6 — make "Spark" and "pyspark" the same thing
Here's a problem that quietly wrecks naïve scorers. One candidate writes "PySpark." Another writes "Apache Spark with Python bindings." A third writes "Spark / pyspark." All three mean the same thing. But to a literal string-matcher, none of them satisfies a job that asked for Spark — so three perfectly good candidates get dinged on a technicality.
"PySpark"exact"Apache Spark w/ python"semantic · 0.94"Spark / pyspark"aliasStage 6 maps every raw skill mention to one canonical taxonomy entry. Exact match first, then alias, then a Gemini-judged semantic match with a confidence score it writes down. That last part matters: a profile where every skill resolves cleanly is genuinely easier to trust than one where half the skills needed a fuzzy guess, and a later stage gets to use that distinction.
It also does two things I'm quietly proud of. Certs expand into their skills — an "OutSystems Reactive Web Developer" cert implies a whole basket (OutSystems, JavaScript, CSS…) so the candidate gets credit for what the credential actually means, not just the letters. And a three-pass verifier re-checks the JD's required skills the candidate didn't appear to claim — so a JD asking for "Cross-Cultural Communication" doesn't mark someone missing when their résumé describes five years operating across the NZ and PH markets. It catches the paraphrase before the score is locked.
stage 7 — the part where it infuses outside signal
A résumé is the candidate's self-description, and — politely — it's incomplete. People forget things, undersell things, run out of room. So stage 7 fills the gaps using sources the candidate themselves linked to: a LinkedIn URL, a GitHub handle, a portfolio site. We fetch them, read what's there, and merge it back into the structured profile — with the origin recorded on every single added claim.
📄 the résumé
- Python, FastAPI
- "led a 2019 migration"
- PostgreSQL
in LinkedIn
- same role, dated 2020
- team leadership
⌨ GitHub
- TypeScript
- Terraform
Two design choices here that I'd defend to anyone. First: not fetching is a failure, not a shrug. If a link is dead, that gets logged as a failure — the system never silently skips and pretends it tried. Second, and this is the one that matters: when sources disagree, nobody wins silently. If the résumé says the migration was 2019 and LinkedIn says 2020, both go into the evidence. Nothing gets quietly overwritten. A human can look at the conflict and decide. The machine's job is to surface it, not to paper over it.
Most "AI hiring" tools bolt one model onto one step — a parser, or a JD writer. Intelletto uses Gemini at roughly two dozen points across the whole loop: reading the résumé, understanding the candidate, defining the role, scoring the fit, drafting outreach, prepping the interviewer — and then checking its own output for biased language.
The reason that doesn't collapse into one giant black box is the two rules. Every one of those AI touch-points is grounded in your data and writes its evidence trail behind it. You can walk up to any single call and ask "why did you say that?" and get a specific, checkable answer. AI-native, but not faith-based.
act 3the part where we get paranoid on purpose
By the time we hit stage 8, the work of understanding the candidate is basically done. The next three stages do something different and, frankly, less fun: they make sure that understanding is safe enough to score, then they lock it down. This is exactly where most pipelines get cocky and ship. We try to do the opposite.
stage 8 — five gates, fail-closed
Five gates run before anything gets a score. Each writes a formal pass / fail / warn verdict, and — rule #2 — the default is to block. The reason this matters: an AI scorer will happily produce a confident-sounding number off a résumé where Document AI lost page 3. You'd never know. The gates are how the system finds out instead of you finding out later.
stage 9 — freeze the inputs forever
Small stage, big consequence. Everything the earlier stages produced — the extraction, the normalised skills, the enrichment, the specific JD version — gets written into one immutable snapshot, hashed and persisted. The scorer is then only allowed to read from that snapshot. It cannot reach past it.
Why bother? Because it means that if someone asks next year "why was this candidate scored 73.4?", you can re-run the exact same scoring against the exact same frozen inputs and get 73.4 again. Reproducibility isn't a nice-to-have in hiring; it's the thing that makes the audit trail mean anything.
stage 10 — close the chain
The source PDF moves to the archive bucket, and the move is only allowed to succeed if the moved file's SHA256 still matches the hash from stage 1. If it doesn't, the operation rejects and the run halts. The point isn't the storage — it's that the bytes we scored and the bytes we kept are provably identical. The provenance chain closes at the byte level, and there's no point anywhere in it where you have to just take someone's word.
act 4ascoring against the actual job
I went deep on the scoring model in the last post, so I'll keep this tight: nine buckets, weighted by seniority band, because the things that make a great grad (hands-on skills, recency, a relevant degree) are not the things that make a great COO (scope led, leadership tenure, P&L, board credibility). Same nine things measured every time; the emphasis slides with the level of the role. A grad at 87 earned it on skills; a COO at 87 earned it on leadership. Same number, two completely different questions, both answered correctly.
The detail I didn't cover last time is the five modifiers that run after the base score. The base score answers "does this profile match the role's requirements?" — useful, but incomplete. Two people can hit the same base and one is clearly the safer bet. The modifiers exist to surface why. They're bidirectional (they can lift or drop a score), each has a defined range, and — this is the important part — each one writes a reason onto the scorecard. The number never just arrives. It's itemised, like a receipt:
act 4bthe golden egg — the screen a recruiter opens
Everything above — all twelve stages, all the hashing and gating and evidence-binding — exists to feed one screen. If the rest of this post is the kitchen, this is the plate that comes out. It's the candidate dashboard, and it's the thing I'd actually put on a poster.
Here's roughly what it looks like when a recruiter opens a single candidate:
Let me walk the layout, top to bottom, because the order is deliberate:
1 · the human, before the score
The first thing you see isn't the number — it's four colour-coded career-trajectory chips: velocity, scope, recency, stability. They read out the shape of a person's career in one glance, and they come from a deterministic 10-signal engine — plain maths over the work history, no LLM round-trip, no opinion. Why above the score? Because a scorecard tells you whether someone fits one JD; the trajectory tells you who they are across every role they've held. Two people can both score 78 on the same job and be wildly different bets — one a steady three-years-per-role ladder climber, the other five roles in three years with the last one ending four years ago. Same score. You want to read the human first.
2 · the score — and you can pull on every thread
Then the score, with its seniority band stated right next to it (so you're never silently comparing a number computed under one rubric against one computed under another). Under it, the nine buckets, each as its own bar. And here's the move that makes the whole thing trustworthy: every bucket is a drawer you can open. Click "Hard skills: 88" and you get the actual sentences — which skills matched, where in the résumé, and which JD requirements went unmatched. The score isn't an opinion handed down; it's a claim with its homework attached.
3 · the narrative, for the human conversation
Below that, a multi-paragraph alignment note the model writes — not just a number, but a themed analysis of how this person fits, plus specific interview questions drawn from this candidate's scorecard. The recruiter walks into the interview informed instead of improvising from a one-line summary. The score does the reading; the human does the judging.
4 · the button that ends the argument
And one click away: the audit report. Fourteen pages — opens inline, or downloads as a cryptographically signed bundle. The cover sheet has the candidate, the JD, the final score and band, the seal timestamp, and a packet fingerprint. The body has all nine bucket breakdowns with their evidence, every modifier with its points and rationale, the full SHA chain, the twelve stage outcomes, and the re-score history.
Here's the part I think is genuinely cool. An external auditor doesn't have to trust our signature. They download the bundle, run our open verifier on their own machine, and it recomputes the hash chain and re-runs the scoring against that frozen snapshot from stage 9. The output is one word: VERIFIED — or VERIFICATION FAILED. That's the bar I wanted to hit. Not "trust me." Re-run it yourself and see you get the same number.
Every other stage is in service of this one screen being honest. The fingerprint, the page map, the evidence spans, the gates, the frozen snapshot, the hash chain — none of it is visible on the dashboard, and all of it is the reason the dashboard can survive the question "why was this candidate rejected?"
A black box that hands you a confident number is a liability you carry. This hands you a starting point with the receipts still stapled to it. That's the difference, and it's the entire difference.
so what's actually different here?
If you zoom back out, the uniqueness isn't any single clever stage. Lots of tools parse résumés. Lots score them. The difference is two things stacked together:
- AI-native, not AI-bolted-on. The intelligence isn't a feature on the side; it's the whole pipeline — Gemini at two dozen grounded touch-points — which is what lets it do role-normative scoring (crediting a CTO for skills a thin JD forgot to list), cert expansion, and paraphrase-aware skill verification.
- Every bit of it is checkable. Evidence on every claim, fail-closed gates, reproducible snapshots, a byte-level hash chain, and a one-click report an auditor can independently re-run. The cleverness is governed.
And — last point, because I promised honesty last time too — it's configurable, not opinionated. The default weights, thresholds, taxonomy, and gates are reasonable defaults, not truths. An EOR in Manila doesn't weight the same signals as a Singapore bank or a US SaaS company hiring its first VP of Eng. So almost every number in the system is a tenant setting, every change is logged, and the configuration surface is part of what you're buying. A platform that ships its defaults as if they were laws is quietly wrong for most of its customers, most of the time.
That's the devil, and that's the detail. The plain-language version from last time — "it reads every résumé and turns the pile into something you can query" — is still true. This is just what "reads it" actually costs, stage by stage, and the one screen the whole bill is paying for.
— scott