Job Application Agent
A local-first LangGraph agent that turns a single job-post URL into copy-paste-ready application materials: a tailored resume (Markdown and PDF), a cover letter, and answers to the posting's custom questions. Saves about 10 minutes per application, with defense-in-depth safety gates around any form submission.
The problem
Applying to jobs has a tedious, repetitive core: re-reading a posting, retailoring a resume and cover letter, and answering the same “why this role” questions over and over. Job Application Agent automates that middle. Give it a job-post URL and your own profile, and it produces application-ready materials in one pass, saving roughly 10 minutes per application.
What it does
From a single URL, the agent:
- Reads the posting and scores how well your profile matches the role.
- Tailors your resume to the role, as both Markdown and PDF.
- Writes a cover letter grounded in your real facts, not generic filler.
- Answers the posting’s custom questions, pausing for your input when it is unsure.
- Optionally fills and submits the application form, behind explicit safety gates.
Architecture
The whole pipeline is a typed-state LangGraph StateGraph, and a few design choices stand out:
- Typed state throughout. Every node input and LLM output is a validated pydantic model, so bad output fails at the boundary instead of corrupting later steps.
- Fan-out where work is independent. Resume, cover letter, and questions run as parallel branches and rejoin before rendering.
- Checkpointing and resume. State is persisted at every step, so an interrupted run picks up where it stopped.
- Human-in-the-loop. When the agent cannot answer a question from your profile, it stops and asks, then continues with your answer.
Safety as a first-class concern
The agent reads attacker-influenced text (a job posting) and can act on the web, so it is built defense-in-depth from day one:
- A prompt-injection scanner runs before any LLM sees the posting, and aborts the run if it finds an attack.
- Auto-submission is off by default, gated behind an explicit flag, a domain allowlist, and a rate limit.
- A PII guard keeps addresses and other sensitive values out of anything submitted automatically.
- A one-submission-per-application guard prevents accidental double submits.
I wrote up the reasoning behind these guardrails here: the interesting part is what it refuses to do.
Local-first and provider-agnostic
It defaults to a local Ollama model, so a full run happens on-device with no data leaving the machine and no API cost. Pointing it at OpenAI, OpenRouter, or any other OpenAI-compatible provider is a one-line change.
My role
Sole designer and engineer across the LangGraph pipeline, the pydantic state model, the safety gates, the ATS adapters, and the Typer CLI. A personal tool I actively use and extend.