Skip to main content
← All Projects

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.

PythonLangGraphOpenAI SDKPlaywrightpydanticWeasyPrintTyperOllama

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.