Deterministic Resumes: Teaching the Machine When to Shut Up

04 Sep 2026

Locking the LLM into strict Pydantic bounds and why upstream tools couldn't integrate a modular tailoring engine.

Once you accept that burning fifty dollars in tokens is stupid and hallucinating fake experience is fatal, the engineering problem becomes narrow and obvious.

How do you get an LLM to tailor a resume without letting it run wild?

The answer is simple: you treat the model like an unreliable intern in a sandbox. You never let it see the entire document as raw text, and you never give it permission to rewrite your career from memory. You force it to communicate strictly through structured schemas, validate every single output field through runtime type checks, and discard any response that violates the rules.

That was the foundation for resume-ops.

I built it around the JSON Resume standard, backed by FastAPI, Pydantic v2 schemas, and a LangGraph section pipeline running inside a Podman container.

The core architectural rule was the total separation of immutable facts from mutable alignment:

Your company names, job titles, start dates, end dates, educational degrees, and university credentials are hard immutable fields. In resume-ops, the LLM is not even provided with an API schema that would allow it to edit those fields. It cannot change your graduation year, it cannot rename your employer, and it cannot invent a promotion you never had.

The only surface area the model is allowed to touch is bullet point relevance.

When a job description asks for distributed systems and asynchronous message queues, the pipeline does not ask the LLM to write an essay. It takes your existing verified accomplishments, identifies which ones demonstrate relevant systems work, and re-orders or tightens the phrasing of those specific bullet points to highlight the overlap. If you never worked with Kafka, the model cannot invent Kafka out of thin air; it is constrained to highlight the closest adjacent concurrency or backend experience you actually documented in your master profile.

Once the tailored JSON is validated against the schema, the PDF compilation is entirely deterministic. No fragile Word templates, no headless browser screenshot hacks. It compiles directly into a clean, print-optimized document via the resumed CLI engine.

The whole thing ran fast, cost less than two cents per tailored role, and produced documents that were honest, sharp, and verifiable.

Once I had it working reliably, I did what any reasonable open-source user tries to do: I tried to push it upstream.

I didn't want to maintain a standalone resume service. Tools like career-ops and job-ops already had scraping communities and active user bases. I went into their issue trackers, opened technical discussions, and proposed integrating resume-ops as a pluggable, deterministic tailoring backend.

The response was polite, but it ran straight into the classic open-source monolith wall.

Upstream projects rarely want modular components. They want monolithic scripts where the scraper, the prompt strings, the LLM calls, and the output formatters are all glued together in a single Python file. Decoupling their systems to support a separate schema-validated microservice would have meant rewriting their internal pipelines and abandoning their custom prompt hacks. They were committed to their all-in-one architectures, even if those architectures were bleeding tokens or generating hallucinations.

So resume-ops stayed standalone.

It lived as a clean, modular service waiting for a real system to plug into. But solving tailoring was only half the battle. Because as soon as you have a clean resume engine, you run into the next wall in automated job hunting: the web scrapers that feed it are constantly getting blocked.


Image Credit: Giovanni Battista Moroni (c. 1520/1524-1579/1580) - The Tailor ('Il Tagliapanni') via National Gallery / Wikimedia Commons.

Series Roadmap: The Job Search Engine