vacancy-radar is a local-first vault for job applications — plain markdown files, hybrid keyword and semantic search, and an MCP server so an LLM can read and write it directly.
I've been keeping track of my job applications for over a decade, in progressively less bad ways. This is how that habit turned into a small open-source tool.
History
2012: Apply to Everything
Right after college, I applied to every vacancy that looked interesting. In 2012 there weren't many roles at big companies, but there were plenty of positions around me where I could put my skills to use. It felt like if you needed a job, you could get one — it just wouldn't be your dream job.
The Duplicate Application
Later I started applying only to jobs that would actually add something to my career. During one search around a specific technology stack, a recruiter broke from the script. The usual answer was some variation of:
"Sorry, but we don't have a position for you right now, we will contact you later."
This one said:
"You already applied for that position."
That was the moment it clicked. Without a record of what I'd already sent, I was re-applying to the same companies more than once — and a recruiter looking at a stack of applications from the same person probably moves on to someone else.
Trusting the Website's History
Almost everything I sent back then went through a single job board, so I could at least check the application history for each resume I had there. That was my tracking system: the platform remembered everything for me.
2019: The Spreadsheet
It worked until interesting vacancies started appearing on other sites. The same company could show up on several boards, and sometimes I wanted to apply directly through a company's own website. So in 2019 I created an .ods document (the LibreOffice equivalent of Excel) with four columns: the company name, a link to the posting, the date I sent the application, and a free-text Stage column for whatever was happening with it.
Color coding gave me the status of every row at a glance:
- White — just applied, no updates yet (default spreadsheet color)
- Yellow — waiting for a response
- Purple — not interested
- Blue — action required from me
- Green — looking good, but approvals are pending
- Red — rejected
- Dark olive — got an offer, then rejected after clarifications
2024: Adding an LLM
In 2024 I started using LLMs to review job postings and draft cover letters. They also took over filling in the endless custom application forms some companies insist on. That was the first step toward putting AI into my job search — and the point where the spreadsheet started to look like the weak link.
Where the Spreadsheet Broke Down
Diffs Nobody Can Read (2026)
In 2026 I wanted to integrate AI more tightly into the process, so I added a Claude Code skill to read and update the tracker directly. That's also when I switched the underlying file from .ods to .fods: flat XML diffs and delta-compresses in git far better than a zipped .ods binary, and the LibreOffice workflow doesn't change at all.
The trade wasn't free. Logging a single application can still turn into an 800-line diff, because opening and saving in LibreOffice rewrites style metadata across the whole document even when nothing visible changed.
No Room for Rich Data
After a while it became clear how much more I wanted to keep: the correspondence, the take-home tasks, who said what on a call. None of that fits in a free-text comment in the Stage column.
And once those details live somewhere, you can use them to judge up front whether a posting is worth answering at all — where I fall short on the stack, and where something in the description doesn't add up. I have never once been hired through an independent recruiter or an agency, and I'd rather have those flagged as they arrive than spot the catch from scratch every time.
Format Lock-in
And I didn't want to be stuck. Whatever I picked next, I wanted a clear path to migrate away from it if something better came along.
What I Wanted Instead
- Readable files — human-readable at any time, not locked in a proprietary database
- Searchability — the ability to find something specific across everything, like a recruiter by name even after that person changed companies
- Simplicity — no more machinery than the problem actually needs
- Not boring — I have to want to keep using it, so it shouldn't be just another database
- No vendor lock — swap technologies or LLM providers whenever I need to
Building It
One split I knew I wanted going in: keep the code separate from the data. The CLI, the eventual MCP server, the search and ranking logic — all of it should work against any vault, and a vault should stay nothing but files.
First Iteration: RAG, LangChain, and a Web UI
I didn't know exactly how I wanted to interact with the data, so I started from a shape rather than a need: ingest vacancies, put ChromaDB in the middle, expose a CLI over the files, and add a web UI on top. That iteration lives on the langchain-rag branch.
Partway through I migrated to LangChain so I could swap LLMs and algorithms more easily. I had plans for A2UI (Agent-to-User Interface), and I was leaning on spec-kit to generate most of it.
What the First Iteration Taught Me
It got more complex. Then more complex again. spec-kit pushed me through iteration after iteration of spec generation in places where I should have just made the change. And somewhere in there the real lesson surfaced: I didn't need an independent interface at all. I already had one — the LLM client I was talking to anyway.
Second Iteration: Remove the Interface
The architecture settled into a single line:
LLM → MCP → Files (source of truth) / DB
The client talks over the Model Context Protocol, the MCP server reads and writes plain markdown, and the database sits behind those files purely as a search index that every write refreshes. With no UI left to specify, I could drop spec-kit for openspec and iterate without multi-step specs in the way.
How to Try It
I used it on myself first: pointed a single agent at my old spreadsheet — years of companies and notes included — and had the whole thing converted into a vault in a few hours.
Full setup and configuration details live in the repo's README. Here's the shape of it:
- Install — needs only uv: uv tool install vacancy-radar. That puts vacancy-radar and vacancy-radar-mcp on your PATH; the first search then downloads a small embedding model.
- Init a vault — vacancy-radar init ~/job-search
- Connect an MCP client — point Claude Desktop or Claude Code at vacancy-radar-mcp --vault ~/job-search
- Talk to it — restart the client, then ask it to log an application or show you what's gone quiet. Or use the CLI directly: vacancy-radar list --status applied
Everything it writes lands as markdown in your vault: readable, searchable, and yours.
Is It Final?
No. What exactly will change I'm not sure yet — but it will keep growing out of the code that's already there.
The direction I keep circling back to is turning the common workflows — logging an application, chasing something that's gone quiet, flagging a posting that looks off — into reusable skills instead of describing them from scratch every time. For now I'm still experimenting with different models and prompts to see what actually holds up before committing to that.
If you have an idea or run into a bug, don't hesitate to open an issue on GitHub.
One last thing, about the photo behind the title. Eleven ironworkers eating lunch on a beam 850 feet over Manhattan, in the worst year of the Great Depression, when roughly one American in four was out of work. These men weren't. Someone still needed steel walked at that height, and they were the ones who could do it. If your search feels hopeless, it has been worse, and people were still hired. The hard part is finding the beam only a few will walk — and remembering where you've already looked.
Lunch atop a Skyscraper, 20 September 1932, on the sixty-ninth floor of the RCA Building at Rockefeller Center. Photographer unconfirmed — Charles C. Ebbets, Tom Kelley and William Leftwich were all on site that day. Public domain, via Wikimedia Commons.