What it does
CVFit is a hiring agent for small recruiting teams. You give it a job description and a stack of CVs; it reads the JD the way a hiring manager would, screens every CV against that specific role, and returns a ranked shortlist where each score carries a written reason you can argue with.
The unit of work is a decision, not a search. Keyword filters tell you which CVs contain the word "Go". CVFit tells you that this candidate is a frontend engineer who has never shipped Go, that one refuses to relocate for a hybrid role, and that the third has run exactly the Kafka-plus-Postgres system you are hiring for — and it says so in sentences.
How Gemini runs the business
Gemini is not a feature bolted onto CVFit. It is the thing that makes the decisions.
Step 1 — Gemini reads the job. parseJdWithGemini() turns free-text JD prose into structured hiring criteria: required skills, seniority, location constraints, languages. On the live run below it extracted must_have: Go, PostgreSQL, Kafka, gRPC, Protobuf, Event-driven Architecture, seniority: senior, location: hybrid from an unstructured posting. Nobody wrote those rules; Gemini inferred them from the text.
Step 2 — Gemini scores each CV against those criteria. callGeminiScore() evaluates every candidate against the criteria from step 1 and emits a 0–100 score plus a verdict sentence. This is a per-candidate reasoning call, not a similarity metric.
Step 3 — every run is logged. Each screening writes to an append-only agent execution log (JD hash, CV count, which brain served it, latency, plan). GET /api/admin/agent-log exposes it. That log is how we can prove Gemini — not a fallback — actually served production traffic.
Live production evidence
Run on the deployed system at cvfit.theodoikenh.com on 2026-08-05, not a local demo:
POST /api/rank -> HTTP 200 in 22.0s
geminiUsed=true fallbackUsed=false failedCount=0
jdIntel (Gemini-extracted from free text):
title: Senior Backend Engineer (Go)
must_have: Go, PostgreSQL, Kafka, gRPC, Protobuf, Event-driven Architecture
seniority: senior location: hybrid
Ranked results:
Candidate A 98 Strongly recommend — direct logistics domain experience,
exact hands-on Go/Kafka/Postgres match
Candidate B 25 Reject — location mismatch, refuses to relocate for a
hybrid Hanoi role
Candidate C 20 Not recommended — frontend engineer, no Go experience
The two rejections are the point. Candidate B is a strong senior engineer with 9 years of backend and 4 in Go — a keyword scorer ranks him near the top. Gemini reads "seeking fully remote roles only; not willing to relocate" against "must be based in Vietnam" and drops him to 25 with that exact reason. Candidate C has "logistics", "gRPC", "PostgreSQL" and "TypeScript" all over her CV and is still a frontend engineer applying for a Go role. Both are the failure modes that waste a recruiter's afternoon.
How we built it
Node.js API on a VPS behind nginx, Gemini via generativelanguage.googleapis.com, JSON file store. Two things were load-bearing:
A key pool with 429 rotation. Google zeroed free-tier quota on our pinned gemini-2.0-flash mid-build, which took the product down. We moved to gemini-flash-latest, minted a second AI Studio key, and built a pool that rotates on 429 so per-key daily quota multiplies. geminiKeyCount is exposed on /api/health.
A scoring rubric corrected by a real recruiter. Our first version ranked a 30-years-experience executive assistant first for an ops role and marked a Spain-based candidate as a location match. A recruiter friend reviewed 16 real screenings and told us exactly what was wrong. That produced four scoring rules — hard cap for wrong-country, recency weighting on the last five years, an overqualification penalty, and role-family separation so an EA never competes with a Scrum Master — and a regression suite that pins her verdicts so we cannot silently regress them.
Challenges
The quota collapse was the sharp one: the model we had pinned went to limit: 0 with no warning and the product simply stopped working. The fix that stuck was not a bigger quota, it was removing the single point of failure — a key pool, a model alias instead of a pinned version, and a documented cascade to a second provider if Gemini is unreachable.
The subtler challenge was trusting our own output. Scores looked plausible long before they were correct. Only checking them against a working recruiter's judgment exposed that "plausible ranking" and "right ranking" are different products.
What's next
Ingest from the ATS side so screening happens where the CVs already live, and turn the agent log into a hiring audit trail — every rejection with its stated reason, retained, so a candidate can be told why.
Built With
- ai-agent
- express.js
- gemini
- google-cloud
- hr-tech
- llm
- nginx
- node.js
- recruiting
- rest-api
Log in or sign up for Devpost to join the conversation.