Back to blog
July 26, 2026

For the past four months I've been quietly obsessed with a single question: what actually makes a resume win? So I built a machine to answer it, and I've run more than 300 head-to-head A/B tests since. Most resume tools are a thesaurus with a subscription. They swap verbs, sprinkle keywords, and hand you something that reads well until an actual recruiter, or an actual applicant-tracking system, gets to it. I wanted the opposite: a system that is adversarial to itself. It writes a draft, then tries hard to reject it, and only ships when the draft survives a tournament and it self-learns on every run from previous rejections.

A/B testing your resume produces one of your best resumes. Not because any single rewrite is magic, but because scoring many variants against a consistent panel and keeping the winner is a search, and searching beats guessing.

01 - Architecture

The pipeline

The whole thing is a directed graph. Each node does one job, and the edges carry a typed state object from stage to stage. The back-edges are the interesting part. When a draft fails a gate (bullets too long, an identity field got mutated, the LaTeX won't compile, or the panel score is too low), the state routes back to the writer with structured notes about what to fix.

fail → revise (≤ 4 rounds)Parse resumeextract identity + work history01Analyze JDweighted skills · ATS keywords02Gap analysishonest reframing targets03Generate skillsJD-tailored skill buckets04Writerrewrite bullets · Claude Sonnet 505Length check195–210 chars / bullet06Renderinject bullets into LaTeX07Identity checklocked fields unchanged08CompileTectonic → 1-page PDF09Recruiter panel4 personas score in parallel10Aggregatorcomposite vs threshold · 7811EmitPDF · score report · vault note12
The resume pipeline as a LangGraph StateGraph. The dashed edge is the revision loop: the Aggregator sends failing drafts back to the Writer, up to four rounds. Nodes marked (length, identity, compile) have their own tighter retry budgets.

The front half is an extraction spine that runs once: parse the resume into a locked identity ledger plus editable work history, analyze the job description into a weighted skill vector, find honest gaps to reframe, and generate a JD-tailored skill block. Then the revision loop begins: the writer rewrites bullets, the draft is rendered into the original LaTeX template, compiled with Tectonic, and handed to the recruiter panel.

A design rule I kept coming back to: the writer never touches your name, contact details, employers, titles, or dates. Those are locked at parse time and injected verbatim by the renderer. An identity check verifies nothing leaked. The model gets to be creative with framing, never with facts about who you are.

FigStarting a run: paste a master LaTeX resume and a job description, pick a model preset, and go.

Models are chosen per role. By default the writer is Claude Sonnet 5 and the gap analyzer is Claude Opus 5 (both with medium reasoning effort), while parsing and scoring use a fast, cheap model. The whole pipeline streams its progress (parse, write, compile, score), so you can watch a draft climb toward the bar in real time.

FigLive run state: stage tracker, revision 01/04, and an activity log, including the vault reporting no proven examples yet for this job's tags.
02 - Evaluation

Four recruiters who disagree on purpose

A single scorer is easy to game. So the panel is four personas, run in parallel, each with a different lens and a different failure mode they're paranoid about:

  • ATS Matcher - keyword coverage and parseability. Does the resume mirror the JD's weighted vocabulary?
  • Hiring Manager - outcomes, quantification, seniority fit, narrative coherence.
  • Technical Screener - stack coherence and whether the claims would survive a phone screen.
  • Skeptic - the fabrication guard. It's the only persona that also sees your source resume, so it can call out claims the draft can't defend.

Each persona scores five rubric dimensions (0–100): keyword match, impact quality, coherence, plausibility, and formatting. Those collapse into a per-persona composite, and the four composites average into the aggregate. A draft passes only if the aggregate clears the threshold and the Skeptic's plausibility clears a floor, so you can't buy a pass with keyword stuffing the Skeptic would veto.

FigComposite 88, passed (min 78). ATS Matcher 86, Hiring Manager 91.
FigTechnical Screener 87. The Skeptic, always the harshest, lands at 83.
Per-persona composite · Vestwell backend run
Hiring Manager
91
Tech Screener
87
ATS Matcher
86
Skeptic
83
Aggregate
88
- - pass threshold · 78
The Skeptic is the binding constraint. It scores lowest almost every run, which is exactly what you want from a fabrication guard. All four clear the 78 bar; the aggregate is 88.

When the writer plateaus and produces a byte-identical draft, the panel is skipped and the previous scores are reused. Re-scoring an unchanged resume is four wasted model calls, and the loop is cheap where it can afford to be.

03 - Benchmarks

What 'good' looks like

88
Aggregate score (of 100)
78
Pass threshold
4
Recruiter personas
≤4
Revision rounds

The revision loop is the benchmark that matters. A first draft rarely clears the bar; each round folds the panel's ranked notes back into the writer, and the composite climbs. The curve below traces a representative run from a weak opener to a comfortable pass.

Composite score by revision round
5075100pass 7864R174R283R388R4
Illustrative curve, grounded in the Vestwell run (final aggregate 88). Only the final round's per-persona scores are captured verbatim; the intermediate points show the shape of a typical climb, not a logged transcript.

The rubric weights decide what the climb optimizes for. Keyword match carries the most weight because it's the gate an ATS actually enforces; plausibility and formatting are deliberately lighter so the writer isn't punished for confident, well-supported reframing.

Default rubric weights (sum = 100%)
Keyword match
30%
Impact quality
20%
Coherence
20%
Plausibility
15%
Formatting
15%
Every weight is tunable per run, and moving one rebalances the rest so they always total 100%.
04 - A/B testing

A tournament picks your best resume

Scoring one resume tells you whether it passes. Scoring many and keeping the winner tells you which one to send. So the A/B mode seeds a bracket of 4, 8, or 16 resumes and plays single-elimination matches judged by a configurable panel. It's fully deterministic. The same seed produces the same bracket and the same result, every time.

FigSetting up a bracket of 8: pick the roster, choose which judges sit on the panel, and set their weights.

Each match is scored per judge, per read. "Best of 3" takes three reads and averages them. More reads means less variance, the same reason you don't decide a chess match on one blitz game. An upset factor dials how much noise the judges tolerate: at zero it's pure chalk (the top seed always wins, which the tests verify); crank it up and lower seeds start pulling upsets.

FigThe knobs that shape a tournament: upset factor (chalk ↔ chaos), reads per match, target role affinity, strictness, and the seed that fixes every draw.

Here's a full 16-resume bracket. My actual resume (labeled "Vestwell") enters mid-seed and climbs the right half (82 → 85.9 → 89.5) to take the final over a strong platform-engineer variant. The tournament didn't just call my resume "good." It told me it was the best of sixteen, and exactly which matchups it won.

FigRound of 16 → Final. Every slot shows a seed, a label, and a match score; winners travel right toward the championship.
One tournament, 16 resumes → 1 champion
Round of 16
16
Quarterfinals
8
Semifinals
4
Final
2
Champion
1Vestwell · 89.5
16 resumes, four rounds, one champion. Each round halves the field; the winner is the variant that beat every resume it faced under a fixed, seeded judging panel.

Because it's deterministic, A/B mode is useful rather than theatrical. Change one bullet, keep the seed, and re-run: if the champion changes, that bullet mattered. It turns "does this edit help?" from a vibe into a measurement.

05 - Controls

Every knob, explained

Two forms drive everything. The first tunes how the writer loop behaves; the second tunes how tournaments are judged. Sensible defaults ship out of the box, but every value is exposed because different jobs want different trade-offs.

The upload form

Loop budgets and scoring thresholds for a single resume.

FigLoop & thresholds, with the two persona-scoring toggles below.
Pass threshold78
Aggregate composite a draft must reach to ship. Higher is stricter and burns more revision rounds.
Plausibility floor20
Minimum Skeptic plausibility, checked independently of the aggregate. The fabrication guard, kept low so honest reframing isn't blocked but non-zero so nonsense is.
Max revision rounds4
How many times the writer may rewrite and be re-scored before the best draft so far is emitted.
Compile retries2
Budget for fixing LaTeX that won't compile or overflows one page, per round.
Identity retries2
Global budget for rewrites when the identity check catches a mutated locked field.
Bullet-length retries3
Budget for nudging bullets into the 195–210 character band so every line sets cleanly.

When scoring is enabled, you also get the rubric weights and bullet shapes. Weights are live-balanced, so moving one rebalances the rest to keep the sum at 100%.

FigRubric weights, live-balanced to 100%.
FigBullet shapes. Leave unchecked to auto-rotate.
Bullet shapes
Lock the rhetorical shape of bullets, or leave all unchecked to rotate through them: PAR (problem → action → result), Result-first, Action + stack, and Context-PAR.
Per-role models
Override the model and reasoning effort for the writer, parser, and gap analyzer independently, or use the Fast / Balanced / Best presets.
FigAdvanced: per-role model and reasoning-effort selection.

The A/B form

How a tournament is seeded and judged.

Bracket size4 / 8 / 16
How many resumes enter. Powers of two only, which means no byes and clean rounds.
Judging panel2–5 judges
Which judges sit: ATS Scanner, Hiring Manager, Technical Lead, The Skeptic, Peer Reviewer. Pick at least two.
Panel weightsΣ 100%
Each judge's share of the verdict, live-balanced like the rubric weights.
Upset factor0–100%
Chalk ↔ chaos. Zero is deterministic favorites; higher injects the noise that lets underdogs win.
Reads per matchBest of 1 / 3 / 5
How many reads each match averages. More reads, less variance.
Target roleGeneralist…
Applies role affinity so, e.g., a backend-leaning judge weighs backend resumes accordingly.
Panel strictness0–100
Global harshness applied to every read.
Blind judging
Hides resume labels until the verdict lands, so names can't bias the read.
Seed
Fixes every random draw. Same seed → same bracket → same result. This is what makes A/B mode a measurement instead of a slot machine.
06 - Self-learning

A loop that makes the next one better

Every run so far is stateless: good resume in, good resume out, no memory. The last piece closes the loop. When a run finishes, it writes a note to an Obsidian vault: the winning bullets, the full score breakdown, and tags for the role and domains. The vault is just markdown files, so it's greppable, portable, and yours.

COMPOUNDINGLOOP ↻1 · Run the pipelinepanel-approved resume + final bullets2 · Write a vault notebullets · scores · role/domain tags3 · Next similar jobretrieve proven examples (Jaccard match)4 · Seed the Writerstart from what already scored well
Each finished run deposits what worked. The next similar job retrieves it and starts the writer from proven material, so quality compounds instead of resetting.
FigA run note's frontmatter: role, domains, outcome, internal score, and the exact settings used.
FigThe body records the aggregate (87.5625) and every persona's dimensions, including the lowest persona.

On the next job, before the writer runs, a retrieval step loads past notes, keeps only the ones that actually did well (outcome: interview or offer) for the same role, and ranks them by how much their domains overlap with the new job. The top few winning bullets are handed to the writer as proven examples: framing that already cleared a panel, now available as a starting point instead of a blank page.

There's also a tuning layer in the vault: per-tag overrides that can nudge thresholds and rubric weights for particular role/domain combinations, learned from what those applications needed. Fintech backend roles and ML research roles don't want the same resume, and over time the system stops pretending they do.

07 - Closing

Search, not magic

None of the individual pieces are exotic: a staged graph, a panel of judges, a bracket, a folder of markdown notes. What makes it work is the shape: draft, reject, revise, and keep only what survives, then remember it. A resume isn't a document you write once; it's the current best solution to a search problem, and this system just runs the search honestly.

A note on the numbers here: the scores, thresholds, and the bracket are from real runs. The revision-climb curve is illustrative, grounded in a real final score but drawn to show the shape of a typical run, not a logged transcript. I'd rather label that clearly than dress an estimate up as measurement. That, more than anything, is the point of the project.

Appendix - The output

The winning bullets

Enough about the machinery. Here are the five bullets from the resume that won its tournament, generated, scored, and shipped by the pipeline, shown verbatim. The highlighted figures are the quantified outcomes the panel rewards most.

Champion output · 89.5
The bullets that won
  1. 01Action + stack

    Built strongly-typed TypeScript and Node.js service layers for a real-time module serving 3,000 clients, using typed interfaces across WebSocket and GraphQL contracts to cut data delivery time 25%

  2. 02Result-first

    70% cut in SQL latency achieved by tuning SQL-based databases (PostgreSQL) - rewriting queries, adding composite indexes, and optimizing Hibernate/JPA persistence via Redis caching layer under high concurrency

  3. 03PAR

    Practiced test-driven development (TDD), writing failing unit tests before implementation across microservices with JUnit, Mockito and Jasmine/Jest suites gated in Jenkins CI/CD, sustaining 99% coverage

  4. 04Context-PAR

    Across a 3,000-client real-time module, built responsive interfaces in the React framework using TypeScript hooks, semantic HTML5 markup, and CSS3 Flexbox/Grid styling for consistent cross-device rendering

  5. 05Action + stack

    Owned CI/CD delivery by building automation pipelines using Jenkins with Groovy pipelines-as-code, running unit tests, SonarQube quality gates, and Docker/Kubernetes rollouts across 10+ services

Verbatim output from the champion resume (composite 89.5). Each bullet is tagged with the rhetorical shape the writer chose, the same PAR / Result-first / Action+stack / Context-PAR shapes from the controls above.