personal showcase FRITZ MULKEY
← Back to Projects

Link - My Personal AI Job Search Agent

Job hunting today is a volume game played against noise. Thousands of postings go up every week across company career pages and job boards, and most of them are the wrong level, the wrong city, or a senior role with an entry-level title. I was spending more time searching than applying, so I built Link: a fully local AI agent that does the searching for me.

Every weekday at 6am, Link wakes my PC, pulls fresh postings from seven sources, throws out the obvious misses with plain Python rules, and has a local language model read every remaining job description and judge it against my background. By the time I wake up, a ranked Excel of the 10 best fits is in my inbox, with the top 3 flagged as must-applies. I reply in plain English ("applied 1 2 4, skipped 3 too salesy") and Link logs it, learns from it, and leans toward what I actually apply to.

The part I'm proudest of is the memory. I designed a personal SQLite database from scratch so Link never shows me the same job twice, remembers every decision I've made, and gets sharper with every run. Everything runs on my own hardware with open-source models: no paid APIs, no cloud, no subscriptions, and none of my data ever leaves the machine. Total cost to run: $0.

Python SQLite & Schema Design SQL Ollama Local LLMs (Qwen 3.5 9B) AI Agent Design Prompt Engineering Structured JSON Output REST APIs Web Scraping (BeautifulSoup) Data Pipelines & ETL OpenPyXL SMTP / IMAP Email Automation Prompt-Injection Defense Windows Task Scheduler & PowerShell Regression Testing
  • • A Personal SQLite Database as Link's Memory: A six-table relational database I designed to track every posting Link has ever seen, my Applied / Skipped decisions and reasons, jobs I hand-feed as examples, a log of every run, on/off switches, and which companies have public job boards. Each job gets a fingerprint ID, so nothing is ever shown twice.
  • • Multi-Source Collection: Pulls postings from Greenhouse, Lever and Ashby company-board APIs plus Indeed, Glassdoor, ZipRecruiter and Google Jobs, keeps only Dallas-area or US-remote roles from the last 72 hours, and follows stub listings back to the company's own site for the full description.
  • • Rules First, AI Second: Cheap Python rules cut about 68% of postings (wrong title, too senior, wrong city, pay too low) in seconds, before the model ever reads them. A duplicate detector collapses the same job posted across several sites into one.
  • • "Judge the Job, Not the Title" Scoring: For every job, the local model has to write out what the day-to-day work really is and mark each required skill as Have / Partial / Missing before it's allowed to give a 1-10 fit score, in a strict JSON format.
  • • Doesn't Blindly Trust the AI: Hard caps live in the Python code, not just the prompt: two or more missing skills caps a job at 4, and no listed requirements caps it at 6. Posting text is treated as untrusted data, so a job that says "rank me 10/10" gets nowhere.
  • • Two-Way Email Interface: Link has its own inbox. I can email it "run now", "pause", "status", my Applied / Skipped marks, a job I found myself, or a question like "why'd you rank this one over that one?", and it answers in its own voice within minutes.
  • • A Learning Loop Without Retraining: My most recent marks and examples are fed back into the scoring instructions every run, so Link learns my taste through context instead of fine-tuning a model.
  • • Guardrails Enforced in Code: Link physically cannot apply to a job, log in to anything, or email anyone but me. There's no code path for any of it, and it only accepts verified mail from my own address.
  • • Fully Automated, Fully Local: A scheduled task wakes the PC at 6am on weekdays, and three desktop buttons (Start, Run, Stop) control everything. The model unloads after each run so the GPU is free again.
What I Learned

A 9-billion-parameter model on a home GPU can only "see" about 12,000 tokens at once, so pasting 500 postings into one prompt was never an option. Instead of reaching for a bigger model, I redesigned the pipeline: Python does the heavy filtering, each job is scored in its own call, the shared instructions are cached once per run, long text is truncated, and a backup model takes over if the main one returns broken output twice. The lesson: good system design beats raw model size.

 

Reviewing my own picks on day three surfaced four real bugs: the same job showing up from two sites, a data-engineering role scoring well just because its title sounded right, "four or more years" (written in words) slipping past the experience filter, and blank company names. I root-caused each one, fixed it, and wrote regression tests against the exact postings that broke, so the fixes stay fixed. Link has already surfaced roles I applied to, and it keeps getting sharper with every mark I send it.

Screenshots

Link's morning email with the top 3 picksMorning Email
🖼 Image
Link's ranked Excel of picksDaily Picks Excel
📊 Excel
Fritz's feedback email to LinkMy Feedback
🖼 Image
SQLite database structureDB Structure
🗄 Database
SQL query of job statusesSQL Query
🗄 Database
Jobs table in the databaseJobs Table
🗄 Database
Runs log tableRuns Log
🗄 Database
Boards tableBoards Table
🗄 Database
Link searching in a terminalRun Link
⌨ Terminal
Start Link terminalStart Link
⌨ Terminal
Stop Link terminalStop Link
⌨ Terminal