Job application companion

A job-search companion built for selective, hands-on use.

Live and in active use, deployed for one additional user beyond myself. Self-service onboarding for more users is next.
At a glance

Switching resume retrieval from flat to chunked semantic embedding nearly doubled measured retrieval spread (5.9 to 12.6) across 3 job types, with an honest caveat: the effect is vocabulary-dependent, not universal.

Five models across three providers, each assigned by documented task-fit reasoning.

Bugs found and fixed with direct, measured verification.

Why this exists

Twelve years without a resume

I used AI in various capacities for real estate and investing for three years, namely to help with marketing and sales. Eventually, I started preferring the AI implementation work over real estate and decided I wanted to find a job that focuses on AI. The problem was that after 12 years of total employment, I had never needed a resume or cover letter. Furthermore, I was doing a career pivot, which meant my work required translation.

After 6-8 weeks of using ChatGPT to help refine my resume, I decided that I needed to solve for scale. Around that time, I saw some unsettling data from LinkedIn about job application conversion rates, and at my current volume it could reasonably take 10+ months. If I could scale the repeat labor, I would recapture time that would allow me to focus on improving my position (certifications, projects, etc.) and leaning further into the human element, like networking or getting involved in the community. Slowly my personal use evolved to a live deployment for my friend Grace, who was also pursuing a job transition while working full time.

What I built

Designed to use only verified facts

I built a tool that generates tailored resumes and cover letters from a job posting, built so that everything it writes traces back to something I actually did. It reads a job posting, pulls relevant facts from my own verified history, drafts a resume shaped to that specific role, and enforces hard limits on what it's allowed to claim. I review and can override every selection before anything goes out.

System map

How it fits together

The following concepts are inspectable within the system map:

Job Pipeline top-level architecture and data flow Onboarding creates the SSOT record; the self-serve profile agent updates it ongoing; the per-application pipeline reads through it for every application; completed applications flow into portfolio tracking. Onboarding New user SSOT SSOT & guardrails Trust & validation Self-serve Profile agent creates updates reads Pipeline Per-app cycle Portfolio App tracking
Control boundaries

Where the model stops and I start

Model-controlled

  • Fit scoring against a job posting (fit_scorer.py)
  • Drafting and synthesis: resume bullets, cover letters (resume_writer.py, cover_letter_writer.py)
  • Gap reasoning (surfacing what's missing or thin, gap_analyzer.py)
  • Independent review (a second model critiquing the draft cold, independent_reviewer.py)
  • Rhetorical structure classification (classify_transition_shape)
  • Grounded research and interpretation (company_research.py)
These tasks need semantic judgment a rule can't encode. That same interpretive strength is a liability elsewhere, which is why it's paired with the tiers below rather than trusted alone.

Deterministic

  • Forbidden-tool/phrase checks against forbidden_claims.yaml, pure pattern matching, no model call (audit_resume.py)
  • Theme-collision dedup in retrieval (primary_theme = themes[0])
  • Schema/structure validation before a draft is accepted (run_with_regeneration_gate)
  • generation_status field validation (packet.py)
  • Strict binary claim verification, deliberately run on GPT-4o mini rather than Claude. Real documented reason: "GPT tends to be more literal and rule-following on structured checks than Claude, which is more interpretive. Interpretive is good for writing, not for audit."
Correctness-critical, binary checks where consistency has to beat nuance. The project's own config explicitly chose a less interpretive model for this specific reason.

Human-controlled

  • Resume: transparent override. I can see the actual reasoning behind every bullet/skill selection (real scoring shown via the Compare drawers/Skills tab) and manually override any selection via drag-and-drop, without breaking anything downstream. It's built to be inspected and corrected.
  • Cover letter: active co-authorship. I judge whether specific examples or transitions genuinely hold up for this application and add my own voice. It's the same reason I write my own cover letters by hand (see Results).
  • Corrections can durably change the source of truth. Answering a gap or adding a missing fact updates the real SSOT, so future applications benefit too.
  • My answer can be reused without re-asking (apply_to_similar flag). Judgment compounds across a batch instead of being spent once per instance.
  • Explicit reject-and-regenerate is its own control, distinct from editing. When something is wrong, the move is a fresh attempt.
  • Presentation/format choice (which visual preset renders) is a separate axis from content control entirely.
  • Submitting the actual application (entirely outside the system).
These are claims about my own career, or irreversible external actions. The system can draft and accelerate, but it doesn't have standing to represent my story or act on my behalf without my sign-off.
Key decisions

Decisions that shaped the build

Final claim acceptance is never automated. Every fact that reaches a live profile, and every application that gets submitted, requires a human sign-off (ties directly to Control Boundaries).
Model routing is config-driven. Five models across three providers, each assigned by task type with documented reasoning.
Resume and cover letter stayed two separate tracks. They need different research, different structure, and different points where I need to weigh in.
Self-service onboarding was deliberately deferred, prioritizing getting my one real user (Grace) live and useful during her actual job search over building generalized onboarding infrastructure she didn't need yet.
Visual presentation was tuned per real user. My own dense format and Grace's cleaner, label-free format come from the same underlying data, deliberately rendered differently because our actual preferences differ.
Built and positioned as a companion tool for selective use, not a mass-application pipeline. The technical capacity for bulk generation exists (see Results), but the tool is designed around deliberate, per-application judgment rather than volume.
Evaluation & reliability

What keeps it accurate and reliable

Run on every generation:

Deterministic checks: schema validation, forbidden-claim/phrase checks, and word-count/format rules run on every draft.
A judgment-based semantic audit: a second model checks paragraph-level rules a string match can't catch.
Independent review: a separate model, reading the draft cold, acting as a critical reviewer rather than the model grading its own work.
A regeneration gate: retries with targeted feedback up to a limit, only failing openly after attempts are exhausted.

1. Silent generation failure (found in live use)

Specify
A hard generation failure must be visible, never indistinguishable from "still processing"
Test
Cover-letter generation on my live account
Observe
A draft sat at "pending" with no letter text and no error
Fail
The hard-failure branch never set generation_status, and the success path never set it either
Repair
Both paths now set an explicit status; frontend surfaces a real error message
Verify
Reproduced the same underlying model failure on retry, confirmed it was now correctly recorded and shown instead of silently swallowed

2. Theme-collision retrieval bug

Specify
Two bullets making the same underlying point shouldn't block each other from selection when only one collides
Test
Retrieval run against job postings for both synthetic persona builds
Observe
A higher-scoring bullet failed to appear despite a strong score
Fail
primary_theme = themes[0] let a first-theme collision block a bullet regardless of its other themes
Repair
Reordered theme lists so the distinguishing theme leads
Verify
Reran retrieval, confirmed bullet count reached its true ceiling (e.g. 15/15)

3. PDF contact-column overflow

Specify
Contact info must never visually overlap the neighboring column
Test
Exported PDFs across different name/email lengths
Observe
One persona's email visually overlapped into the profile column
Fail
A fixed-width flexbox column had no overflow protection (min-width: auto default)
Repair
min-width: 0; overflow-wrap: break-word;
Verify
PyMuPDF-measured bounding box coordinates confirmed clear separation, not visual guessing
Results

What's real, what's estimated, what's next

Demonstrated

The full pipeline runs end-to-end, job posting in, tailored resume and cover letter PDF out, confirmed across multiple real and synthetic profiles with different backgrounds and formatting needs.

Self-use

I use the resume generator for my own job search, and I write my own cover letters by hand. Hand-tailoring each cover letter avoids losing my voice or over-generalizing across different applications.

External validation

Grace, a third-party user, adopted the tool during her active job search, addressing a genuine capacity and burnout constraint. This is one external user, not a broadly validated sample, and I onboarded her personally rather than through self-service.

Current estimate

The pipeline's technical capacity extends well beyond how I currently use it. A full application (resume plus cover letter) runs about 12 sequential model calls across both tracks: small structured-extraction calls taking a few seconds each, the larger constraint-heavy writing calls (the cover letter draft in particular) taking longer. Roughly, that's 2 to 4 minutes of API processing time per application, estimated from the call architecture. Paired with bulk import of saved job postings, the practical scale is bounded by how many postings I choose to bring in and how much I want to review or edit. I designed and use it as a selective job-application co-pilot. It doesn't replace hand-tailoring a cover letter to a specific opportunity, building relationships through networking, or the slower work of actually strengthening my position through certifications or projects.

Future goal

Two friends have expressed interest in using the tool. Finishing self-service onboarding, most of which is already reflected in the self-serve profile agent, would let them set up their own profiles directly. The intent is to offer the tool and support to people who need it. Commercializing it isn't the goal, which is part of why most of the code is public on GitHub.

Ownership & AI-assistance

What I did, what AI did, what I checked

I owned

  • The higher-level workflow/architecture (the actual system design)
  • Design decisions throughout
  • QA
  • Prompt engineering
  • Debug-pivot decisions: when something broke, deciding whether a durable fix or a quick fix was the right call
  • Being the first end user and tuning the system based on real output quality (e.g., recognizing cover-letter generation needed an extensive constraint set to produce a format and voice close enough to be usable)
  • Final decision authority on every consequential fork in the build
  • Protecting my own voice, declining to let AI draft my personal narrative and instead writing it myself, using AI only for structural feedback
  • Scope discipline: deciding what to build now, and what to deliberately leave out (self-onboarding, certain UI polish)

AI-assisted

  • Code generation and implementation
  • Refactoring and review
  • Documentation
  • Debugging suggestions
  • Infrastructure/deployment execution support (diagnosing DNS and HTTPS issues during the real cloud deployment)

I verified/accepted

  • Independently re-checked AI's own verification claims rather than accepting them at face value (caught a real gap in a security sweep this way)
  • Caught factual/terminology errors directly (a miscounted model, an invented term)
  • Reproduced and confirmed each bug fix live before accepting it as resolved
  • Accepted or rejected every proposed design at each fork, rather than deferring to whatever was proposed
Limitations

What this doesn't prove yet

Self-service onboarding is not yet built. The current biggest constraint is extending the tool use to new users without my direct involvement.

No formal external evaluation study yet. One external user so far, not a broader sample.

The tool won't do certain things, and isn't meant to. It doesn't build a user's network, improve their actual qualifications, or replace the judgment only they have about which opportunities are worth pursuing.

The quality of what it generates depends on the quality of what a user gives it. Thin or vague facts produce thin or vague resumes and cover letters. The self-serve agent exists partly to help sharpen facts, skills, bullets, and stories toward concrete, evidence-backed claims instead of vague trait statements, but it can only work with what's actually there. Users are encouraged to gather their own outside signal, direct feedback specific to their industry, to confirm or challenge whether this approach is actually working for them.

It's built to remove repetitive drafting labor. It was never meant to replace the parts of a job search that require the person doing it.