A multi-agent service that scores a resume against a job description skill-by-skill (0–100 fit, covered/partial/missing with evidence) and rewrites bullets to fit the role — flagging genuinely missing skills as honest gaps instead of fabricating experience. A four-stage pipeline on Google ADK with Gemini uses sequential + parallel orchestration: a ParallelAgent parses JD and resume concurrently (gemini-2.5-flash-lite), a match analyzer scores fit, and a tailor agent rewrites (gemini-2.5-flash) — passing data through ADK session state with Pydantic schema validation at every handoff. Model routing uses cheap flash-lite for 3 of 4 stages and the stronger model only for the rewrite. Input handling is hardened with PDF magic-byte validation, a 10 MB cap, encryption/scanned-PDF rejection (HTTP 422 before any LLM call), and a zero-fabrication evaluation tripwire (eval.py over labeled cases) enforced as an acceptance gate.
What it is
A multi-agent service that scores a resume against a job description skill-by-skill (0–100 fit, covered/partial/missing with evidence) and rewrites bullets to match the role — while being structurally incapable of inventing experience. Skills the candidate lacks surface as honest gaps in their own UI panel instead of being papered over.
Pipeline
Four LLM agents composed under two orchestration agents on Google ADK. A ParallelAgent runs the JD parser and resume parser concurrently — they're independent — then a match analyzer scores fit and a tailor agent rewrites. Data moves through ADK session state: each agent reads exactly the slice it needs via placeholder templating and writes its result under a named output key, with a Pydantic output schema validating every handoff. Structured data between agents, never free text.
jd_parser = LlmAgent(
model="gemini-2.5-flash-lite",
instruction="Extract requirements from: {job_description}",
output_schema=JDParsed, # schema-enforced handoff
output_key="jd_parsed", # lands in session state
)Hardening & deploy
Input guards run before any model call: PDF magic-byte sniffing (415), a 10 MB cap (413), and rejection of scanned or encrypted PDFs (422) — because the no-fabrication guarantee can't survive garbage extraction. Both ingress paths (JSON API and multipart upload) run the identical pipeline. Model routing spends gemini-2.5-flash-lite on the three extraction stages and gemini-2.5-flash only on the rewrite. Deployed on Hugging Face Spaces (Docker) and Google Cloud Run, scale-to-zero. Full write-up: the blog post "Zero Fabrication by Construction."