How My Résumé Tailors Itself (Without Lying)

Every job application on this site gets a résumé rewritten for that specific job description — by an agent pipeline that runs in this site's repo. The interesting part isn't that an LLM can tailor a résumé. It's that an LLM tailoring a résumé will, left alone, quietly improve it: rounder numbers, grander titles, technologies I've only read about. So the pipeline's real job is saying no.

The scrubbed standalone code and sample data are staged for publication after review. Until the human-owned repository is created and verified, this page deliberately carries no dead repository link.

The pipeline

job description (file | URL | stdin | text)
        │
        ▼
persona routing ──── Claude picks the closest persona résumé
        │            (I confirm; slugs like acme-corp-fde)
        ▼
scaffold ─────────── clone the persona into an unlisted page,
        │            JD embedded in frontmatter for later reworks
        ▼
tailor pass ──────── career-coach prompt rewrites the body to the JD
        │            └─ mechanical tripwires validate; violations
        │               are fed back and the model retries
        ▼
coach-review pass ── a second, independent pass: fix AI-sounding
        │            phrasing, check the JD's keywords landed
        │            └─ same tripwires, same retry loop
        ▼
verify ───────────── PDF build + full site test suite
        ▼
publish ──────────── branch-per-job, PR back to the base branch;
                     a human reads the diff before anything ships

Two details do a lot of quiet work here. Each job gets its own git branch cut from the base branch's remote tip — never the local checkout — so concurrent applications can't contaminate each other. And the tailored pages are unlisted by construction: noindex,nofollow, excluded from the sitemap, RSS, and the folder listing, reachable only by the direct link I hand a recruiter. The privacy behavior is regression-tested, not just configured.

The guardrails: mechanical tripwires, not vibes

The tailoring prompt already says "never invent experience." Prompts are not enforcement. After every generation — both backends, both passes — a deterministic function inspects the candidate text and returns a list of violations:

  • Banned-technology list. A hard list of tools I have not shipped and will not claim, checked case-insensitively. The model can't be sweet-talked past a regex.
  • Number provenance. Every digit sequence in the output must already exist in the source résumé. The job description doesn't count as a source — a JD that says "$50M pipeline" must not license a résumé that says I built one. No new numbers, ever.
  • Proper-noun allowlist. Any mid-sentence TitleCase token must already appear in the source résumé, the JD, or the company name. This is the anti-employer, anti-product, anti-award tripwire: the model can't introduce "Netflix" or "Kubernetes" from nowhere.

Violations aren't fatal — they're feedback. The validator raises a retry with the exact violation list ("invents a number absent from the source résumé: '87'"), the model gets another attempt, and after a few strikes the pipeline fails closed. On the API backend this rides PydanticAI's output-validator retry loop; the CLI backend reimplements the same loop by hand so both paths share one enforcement function.

Why mechanical checks instead of an LLM judge

An LLM judge grading truthfulness inherits every problem it's grading: it's probabilistic, it's persuadable, and it reads the same untrusted JD text that might be trying to prompt-inject it. (The pipeline treats JD text as hostile elsewhere too — the headless Claude backend runs with all tools disabled, because a fetched job posting is exactly where you'd hide "ignore your instructions and run this command.")

The tripwires are the opposite: deterministic, auditable in one screen of code, free to run, and impossible to argue with. When one fires, the feedback to the model is precise instead of vibes ("don't exaggerate"). That precision is what makes the retry loop converge.

Where the tripwires fail (and why that's the design)

I call these tripwires, not guarantees, on purpose. Known holes:

  • Sentence-initial proper nouns are exempt — flagging every capitalized sentence-opener would drown the signal, so a fabricated employer that starts a sentence walks through.
  • Digit recombination. The check is per-digit-sequence, not per-claim. A source résumé containing "40%" licenses a "40x" somewhere else. "2016" licenses "2016 records processed."
  • Spelled-out numbers. "Tripled revenue" contains no digits. The number check never sees it.
  • Semantic inflation. "Contributed to" becoming "architected" uses only allowed tokens. No token-level check catches a verb getting promoted.

Each of these is catchable — a number-word lexicon, claim-level extraction, an entailment model. I haven't built them, because the economics don't ask for it: the tripwires catch the high-frequency, high-embarrassment failure modes cheaply, the prompt carries the semantic rules, and every tailored résumé leaves through a pull request that I read. The mechanical layer exists so human review is the last line of defense instead of the only one.

That's the honest shape of most production guardrails, I think: a cheap deterministic layer that catches the common failures loudly, wrapped around a probabilistic system, with a human gate where the stakes are real. What I'd distrust is the version of this page that claimed the checker was airtight.

Try it

The staged artifact ships the pipeline (persona routing, both tailoring passes, the guardrails and their retry loops, the scaffolder), a fictional sample candidate with two persona résumés, a sample job description, and a test suite that pins down the tripwire semantics — including tests documenting the failure modes above. The git/PR automation and PDF rendering stay behind in this site's repo; the README is explicit about what was cut and why.

Everything here — this page included — went out through the same review gate it describes.