Model or dataset
btseytlin/hr-breaker avatar
btseytlin/hr-breaker

hr-breaker: a resume rewriter that refuses to invent your experience

Optimize resume for jobs, now with AI

878 stars125 forksPythonMIT

At a glance

What is it?
hr-breaker takes a resume in any format and a job posting, and produces a single-page ATS-friendly PDF, with the unusual part being a chain of validation filters that include a hallucination checker and an AI-sounding text detector, and a regeneration loop that feeds filter feedback back into the model. It is MIT licensed, runs as a FastAPI web app or a Click CLI, and it needs an LLM API key.
Who is it for?
hr-breaker fits a developer or other applicant who wants a resume restructured for a specific posting, and who wants the output checked for invented claims rather than simply prettified. It is the wrong choice if you need a CV generator that produces a different document per role, because the design principle is minimal changes and no fabrication, and it is the wrong choice if you cannot use an LLM API, since the core of the tool is a model call.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The loop that rewrites until the checks pass

The How It Works section is six steps and the fifth is the interesting one.

You upload a resume in any text format, treated as a content source only. You provide a job posting URL or a text description. The LLM extracts the content and generates an optimized HTML resume. The system then runs internal filters, described as an ATS simulation, keyword matching and hallucination detection. If the filters reject the result, it regenerates using that feedback. When all checks pass, it renders the HTML to PDF via WeasyPrint.

So the design is a generate, validate, regenerate loop with the validation output fed back in as prompt context. That is the mechanism that distinguishes this from a prompt you paste into a chat window, because the feedback is structured and the regeneration is automatic rather than something you do by hand.

The feature list states the intent plainly: LLM-powered optimization tailors content to job requirements, minimal changes preserves your content and only restructures for fit, and no fabrication means hallucination detection prevents made-up claims. There is also opinionated formatting, following proven resume guidelines such as one page and no fluff, and multi-filter validation combining ATS simulation, keyword matching and structure checks.

The PDF output is described as single-page and professionally formatted, and the input side accepts LaTeX, plain text, markdown, HTML and PDF. Output is a single-page PDF, which is a hard constraint rather than a preference, and it is worth understanding what that means before you point it at a resume with fifteen years of experience.

Nine filters, run in priority order

The architecture section lists the filters with their priority numbers, and the ordering is the design.

Priority 0 is ContentLengthChecker, a size check. Priority 1 is DataValidator, HTML structure validation. Priority 3 is HallucinationChecker, which detects fabricated claims not supported by the original resume. Priority 4 is KeywordMatcher, described as TF-IDF matching. Priority 5 is LLMChecker, a visual formatting check and LLM-based ATS simulation. Priority 6 is VectorSimilarityMatcher, for semantic similarity. Priority 7 is AIGeneratedChecker, which detects AI-sounding text. Priority 8 is TranslationQualityChecker, checking translation quality, and it is skipped when the source and target languages are the same.

Three of those deserve comment.

The HallucinationChecker at priority 3 runs early, before the keyword and formatting checks, and it compares the generated claims against the original resume. That is the check that matters most for a tool whose stated principle is no fabrication, and its position in the order means a resume that invents a Kubernetes certification is rejected before anyone wastes a keyword-matching pass on it.

The AIGeneratedChecker at priority 7 is the most interesting one from a hiring perspective. If a hiring pipeline is already screening for machine-sounding cover letters, a tool that produces them is producing something that will be filtered out, so the tool checks its own output for that trait. It is a strange and rather clever constraint.

The TranslationQualityChecker at priority 8 sits last and is conditional, which suggests the multi-language support is a first-class feature rather than an afterthought. The language handling is visible in the configuration file, with PRO_MODEL and FLASH_MODEL having moonshot/kimi-k2-5 alternatives alongside the Gemini defaults, and an EMBEDDING_MODEL of gemini/text-embedding-004, which is what the vector similarity matcher would use.

Two models, one for quality and one for speed

The configuration file is where this project explains itself, and it explains a lot.

There is a shared mode, which is the default, and a separate mode. In shared mode one provider serves both models, and the file gives examples of the shared provider being gemini or openai_compatible, with a shared API key and a shared base URL that is used for openai_compatible only. The base URL example points at integrate.api.nvidia.com/v1, which is an OpenAI-compatible endpoint.

The models are configured in LiteLLM format. PRO_MODEL is the smart model, and the examples are gemini/gemini-3-pro-preview, moonshot/kimi-k2-5 as a Moonshot AI alternative, and openrouter/google/gemini-3-pro-preview for routing through OpenRouter. FLASH_MODEL is the fast model, with gemini/gemini-3-flash-preview and the same Moonshot alternative.

So the architecture is a two-tier model arrangement, a stronger model for the work that needs reasoning and a cheaper or faster one for the work that does not, with both behind one abstraction so the provider can be swapped without touching code. The separate mode, used only when LLM_SEPARATE_MODELS is true, lets you point the pro model and the flash model at different providers entirely, with their own keys and base URLs.

The cost control is explicit. There is a MAX_TOKENS setting, described as an output token cap for LiteLLM and OpenRouter requests, with an example value of 16384, and MAX_OUTPUT_TOKENS as an alias for it. The comment on that setting is that it exists to cap expensive responses, which tells you the author has watched a bill.

The key list is the other thing worth reading, because it is a good map of who the tool expects to be used with. GEMINI_API_KEY is the primary, GOOGLE_API_KEY is kept for backward compatibility, and MOONSHOT_API_KEY, OPENROUTER_API_KEY, OPENAI_API_KEY and ANTHROPIC_API_KEY are all recognised. Since LiteLLM reads the keys from the environment, adding a provider means setting a key, not writing an adapter.

A web app and a CLI over the same core

The project offers both interfaces, and the CLI is the better documented one.

The quick start is three commands. uv sync installs the environment, cp .env.example .env creates the configuration file, and you edit it to add your GEMINI_API_KEY or configure another LLM provider. Then uv run hr-breaker serve starts the web interface, which opens a browser at http://localhost:8899.

The CLI takes a resume and a job. From a URL:

bash
uv run hr-breaker optimize resume.txt https://example.com/job

From a job description file:

bash
uv run hr-breaker optimize resume.txt job.txt

And there are flags for the behaviours that matter. Debug mode, which saves iterations, is on by default and disabled with -D. User instructions let you guide the optimizer, with the example of focusing on Python and adding a Kubernetes certificate. Language mode takes from_job as the default, with from_resume and specific languages such as ru available, and there is a lenient mode called --no-shame that relaxes content constraints while still preventing fabricated experience, with the documentation telling you to use it with caution.

The web interface covers the same ground with a different shape. You drop a file or paste text to create a resume profile, or click an existing one. You enter a job URL or paste a description. You configure settings in the sidebar, which covers models, API keys and filter thresholds. You click optimize and get real-time progress over SSE. Then you preview and download the PDF.

The profile system is the piece that makes the web app more than a form. Profiles group multiple resume documents, so you can drop in a file to auto-create a profile, or create one manually and add documents. Clicking a profile synthesises it into a resume, and the edit icon manages the documents.

Output paths are predictable, which matters if you are running this repeatedly. Final PDFs go to output/ with a name of the month, day, hour, minute, then the name, company, role and language. Debug iterations go to a directory of the same prefix with debug in it, and the records go to output/index.json.

Where this is the wrong tool for the job

Three situations rule it out, and they are about the fit between the tool's philosophy and what you actually need.

The first is if you want a different document per role. The stated principle is minimal changes, preserving your content and only restructuring for fit, plus a single-page output. A CV that is genuinely rewritten for each application, with different framing, different emphasis and different achievements selected, is a different document, and the tool's design is pointed away from producing one. It will restructure and rephrase what you wrote, and it will refuse to add what you did not write, but it is not going to argue for a different career narrative.

The second is if you cannot use a hosted LLM. The whole design is a model call with a generate, validate, regenerate loop around it. There is no local model path described, and the configuration is entirely about choosing a hosted provider. The architecture listing does include a services directory with scrapers and a caching layer, and the dependency list includes playwright, so the job posting fetching is done locally, but the generation is not.

The third is if you need the output to survive a human reading it. The one-page constraint, the opinionated formatting, the no fluff rule, and the AI-sounding text check all push in one direction, towards a document optimised for an automated screening step. For a role where a human reads the document closely, the same choices can work against you, and the documentation does not describe a mode that relaxes the layout as it does for content.

There is also a question the project is honest about. The lenient mode is called --no-shame, which is a memorable name, and the documentation's own framing of it, relaxes content constraints but still prevents fabricating experience, use with caution, tells you what the tool's authors think of it. A tool that needs a flag whose name warns you about it is a tool with a known failure mode, and that is worth understanding before you turn it on for a job you care about.

Editorial conclusion

hr-breaker fits a developer or other applicant who wants a resume restructured for a specific posting, and who wants the output checked for invented claims rather than simply prettified. It is the wrong choice if you need a CV generator that produces a different document per role, because the design principle is minimal changes and no fabrication, and it is the wrong choice if you cannot use an LLM API, since the core of the tool is a model call. Before you use it, read what the hallucination and AI-sounding checks actually compare against, keep the debug iterations that the tool saves by default, and set the token cap, because a rewrite loop that regenerates on rejection is exactly the shape of workload where an uncapped call becomes an expensive one.

Frequently asked questions

What does hr-breaker do to a resume?

It takes a resume in any text format as a content source plus a job posting URL or text, has an LLM generate an optimized HTML resume, and renders it to a single-page PDF through WeasyPrint. The stated principles are minimal changes, so it restructures your content for the fit rather than rewriting your career.

How does hr-breaker prevent a model from inventing experience?

A chain of filters runs on the generated resume, and the HallucinationChecker at priority 3 detects fabricated claims not supported by the original resume. If a filter rejects the output, the tool regenerates using that feedback, and the project also has an AIGeneratedChecker that detects AI-sounding text.

How do I install and run hr-breaker?

Run uv sync, copy .env.example to .env and add your GEMINI_API_KEY, then run uv run hr-breaker serve for the web interface, which opens at http://localhost:8899. The CLI form is uv run hr-breaker optimize resume.txt job.txt, and it accepts a job URL in place of the file.

Which language models can hr-breaker use?

Models are configured in LiteLLM format, with a PRO_MODEL for the smart model and a FLASH_MODEL for the fast one, and any provider works including OpenAI, Anthropic and Moonshot, with examples given for Gemini, Kimi and OpenRouter. Separate mode, enabled with LLM_SEPARATE_MODELS=true, lets the two models use different providers, and MAX_TOKENS caps the output.

Is hr-breaker still being developed?

The last push to the repository was on 2026-04-17 and it is not archived, with v0.2.0 on 2026-03-13 described as FastAPI plus Alpine.js frontend, and v0.1.4 and v0.1.3 on 2026-02-08. The licence is MIT, it requires Python 3.10 to 3.13, and it is marked as a beta development status.

Official sources

  1. btseytlin/hr-breaker on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/btseytlin-hr-breaker.svg)](https://hysenlabs.com/projects/btseytlin-hr-breaker)

Was this useful?