Skip to content
<Hyun/>
All projects

JobPilot

Personal · Job search assistant

See the match. Understand the evidence.

Compare a job requirement with the original resume evidence and the exact rewrite it can support.

Interactive explanation · not the live service

Loading interactive explanation…

Watch the original recordingsWeb · Recording notes included

Use the left and right arrow keys to switch slides.

Web

Recorded from a local build with fictional companies, postings and candidate; scores and evidence were seeded, not generated live.

Walkthrough with fictional data: companies by ATS source, score-ranked postings, per-requirement score evidence, a tailored resume and a form-fill report.

1 / 6
Explore the architectureSystem diagrams, design decisions and detailed flows

System overview

  1. SINGLE VM · SYSTEMDBackground workerhourly + spawned runs
    Step 1, public JSON
    ATS boards + formsGreenhouse, Lever, Ashby
  2. Background worker
    Step 2, posting JSON
    SINGLE VM · SYSTEMDhiring-agentPython scorer
    evaluate
    Claude APIscoring, tailoring
  3. Background worker
    Step 3, tailor
    Claude API
  4. Background worker
    PDF, forms
    SINGLE VM · SYSTEMDHeadless ChromiumPlaywright: PDF, forms
    allowed forms
    ATS boards + forms

Other connections

  • DashboardtoNext.js appvia Caddy
  • Next.js apptoBackground workerspawn
  • Next.js apptoSQLite (Drizzle)read / write
  • Background workertoSQLite (Drizzle)leases, events
  • Local assist tooltoSQLite (Drizzle)handoff
The dashboard reaches a Next.js app behind Caddy. A background worker and the jobs it spawns run the pipeline on one VM with SQLite: (1) pull postings from public ATS boards, (2) score them with the hiring-agent subprocess and Claude, (3) tailor with Claude, then render PDFs and fill allowed forms in headless Chromium.
View the diagram as text

Parts

  • Dashboard (jobs, resumes, runs): Client / UI
  • Local assist tool (person submits): Client / UI
  • Next.js app (pages, server actions): Owned service
  • SQLite (Drizzle) (jobs, resumes, applications): Data store / broker
  • hiring-agent (Python scorer): Owned service
  • Background worker (hourly + spawned runs): Background job
  • Headless Chromium (Playwright: PDF, forms): Owned service
  • Claude API (scoring, tailoring): External service
  • ATS boards + forms (Greenhouse, Lever, Ashby): External service

Connections

  • Step 1: Background worker → ATS boards + forms: public JSON
  • Step 2: Background worker → hiring-agent: posting JSON
  • hiring-agent → Claude API: evaluate
  • Step 3: Background worker → Claude API: tailor
  • Background worker → Headless Chromium: PDF, forms
  • Headless Chromium → ATS boards + forms: allowed forms
  • Dashboard → Next.js app: via Caddy
  • Next.js app → Background worker: spawn
  • Next.js app → SQLite (Drizzle): read / write
  • Background worker → SQLite (Drizzle): leases, events
  • Local assist tool → SQLite (Drizzle): handoff

From a new posting to a tailored application

  1. Sync postings

    Every hour, or on demand, the worker calls each enabled company's ATS adapter. One transaction inserts new postings, updates known ones and deactivates postings that have dropped off the board.

  2. Filter

    Title and location patterns decide whether each posting matches. Only active, matching postings without a score are queued for scoring, newest first.

  3. Score with hiring-agent

    Each posting is claimed with a lease and passed to the scorer, which builds a rubric from that posting and scores the resume on requirements, experience, skills and impact, quoting evidence for each. Failed postings are retried after six hours.

  4. Tailor the resume

    A background run asks Claude for a structured answer that picks and rewords master bullets by ID only. The result is validated, saved as a new version with rewrites shown side by side, rendered to a one-page PDF and scored again.

  5. Fill the form

    The person selects postings, and each selection is fingerprinted. Headless Chromium opens only allowed Greenhouse, Lever and Ashby hosts, fills fields from the resume and the answer bank and steps through multi-page forms; any unanswered question moves the application to review.

  6. Review or submit

    Before clicking Submit, the worker re-checks the stop switch, the fingerprint and the daily limit and writes a durable attempt record. An application is marked submitted only when a confirmation appears, so an unclear outcome is never resent; forms with a login or CAPTCHA go to the local assist tool for the person to submit.

The project & my contribution

Problem
A broad job search meant checking many career sites and tailoring a resume and form for every posting by hand.
Solution
A self-hosted dashboard that collects postings from five applicant-tracking systems, scores them against the resume, tailors resumes and fills in applications.
Result
Job discovery, resume matching and application tracking in one workflow; it submits on its own only in narrow, verified cases.

The source code is private. I'm happy to walk through it on request.

How it's built

  • Next.js, TypeScript, SQLite with Drizzle, Playwright and the Claude API on one small VM
  • Adapters for Greenhouse, Lever, Ashby, Workday and Jibe/iCIMS normalize postings to one shape
  • A background worker scores each posting with an open-source hiring-agent, quoting resume evidence

Highlights

  • The LLM may only select, reorder and reword existing resume bullets, checked against the master
  • Every tailored resume is kept as a diff, rendered to a one-page PDF and scored again
  • Posting text is treated as untrusted; a submission counts only when a confirmation appears
  • Forms behind a login or CAPTCHA are handed to the person to submit