JobPilot
Personal · Job search assistantSee 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.
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 / 6Explore the architectureSystem diagrams, design decisions and detailed flows
System overview
- SINGLE VM · SYSTEMDBackground workerhourly + spawned runsStep 1, public JSONATS boards + formsGreenhouse, Lever, Ashby
- Background workerStep 2, posting JSONSINGLE VM · SYSTEMDhiring-agentPython scorerevaluateClaude APIscoring, tailoring
- Background workerStep 3, tailorClaude API
- Background workerPDF, formsSINGLE VM · SYSTEMDHeadless ChromiumPlaywright: PDF, formsallowed formsATS 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
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
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.
Filter
Title and location patterns decide whether each posting matches. Only active, matching postings without a score are queued for scoring, newest first.
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.
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.
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.
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