Skip to main content

Job hunting + tailored CV generation agent built with LangChain deepagents

Project description

๐Ÿ’ผ Job Hunt Agent

An end-to-end job search + tailored CV generation agent built with LangChain deepagents:

  1. builds your master profile (profile/profile.md) from an uploaded CV,
  2. searches postings on LinkedIn/Indeed and more (JobSpy scraper โ€” no API key needed; optional JSearch API),
  3. ranks them against your profile with honest fit scores,
  4. for every job you pick, a cv-tailor subagent writes a job-specific CV + cover letter (XYZ-formula bullets, ATS-friendly) into applications/,
  5. starts an application only with your approval (human-in-the-loop interrupt).

What is deepagents and how is it used here?

Deepagents is an agent harness that ships with planning (write_todos), a virtual file system, subagent delegation (task tool) and human approval (interrupts). The mapping in this project:

Deepagents feature Here
create_deep_agent(tools=...) search_jobs, get_job_details, read_profile, submit_application
subagents=[...] cv-tailor: writes the CV/cover letter in an isolated context
interrupt_on={...} + checkpointer submit_application never runs without human approval
write_todos (built-in) The agent plans multi-job tasks itself
Model Anthropic, OpenRouter or OpenAI โ€” whichever key you set

Setup

git clone https://github.com/EceDalpolat/job-hunt-agent && cd job-hunt-agent
uv sync                      # or: pip install -e .
cp .env.example .env         # fill in ONE of the API keys (see below)
  • For the model, set one of ANTHROPIC_API_KEY, OPENROUTER_API_KEY or OPENAI_API_KEY (priority in that order). Override the default model with JOB_AGENT_MODEL โ€” e.g. openai/gpt-5.1 on OpenRouter.
  • Job search needs no key: JobSpy scrapes LinkedIn + Indeed directly. LinkedIn rate-limits aggressively; if it blocks you, Indeed results keep coming. Setting RAPIDAPI_KEY switches to the JSearch API instead. If both fail, the agent falls back to 6 bundled sample postings.

Create your profile

uv run job-agent-profile path/to/your-cv.pdf --notes "linkedin.com/in/you, target: AI engineer roles"

This extracts the text from your CV (pdf/docx/md/txt), normalizes it with the LLM into profile/profile.md, and that file becomes the single source of truth โ€” the agent never puts anything on a CV that is not in it. Review it before applying anywhere. See profile/profile.example.md for the expected shape. (profile/profile.md is gitignored โ€” your personal data stays local.)

Run

uv run job-agent                          # terminal CLI
uv run streamlit run job_agent/ui.py     # web UI

In the Streamlit UI: upload your CV in the sidebar to build the profile, chat to search postings ("find AI/LLM engineer jobs in Istanbul, rank the top 5"), say "prepare CVs for 1 and 3", approve applications with โœ…/โŒ buttons, download the generated CV/cover letters from the sidebar, and watch live token/cost metrics.

Example CLI session:

๐Ÿ‘ค > find python backend jobs that fit me
๐Ÿค– (reads your profile, searches postings, returns a fit-scored list)

๐Ÿ‘ค > I want to apply to 1 and 3
๐Ÿค– (cv-tailor subagent writes a CV + cover letter per job into
    applications/2026-07-06-healthtech-labs-senior-backend.../)

โš ๏ธ  Pending approval:
   Tool : submit_application
   Args : {'job_id': 'mock-001', ...}
   Approve? [y/n]: y
๐Ÿค– Application logged, apply page opened in your browser.
๐Ÿ’ฐ 7 LLM calls โ€ข 41,203 in + 2,841 out tokens โ€ข ~$0.16

Architecture

                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 Entry points       โ”‚  main.py (CLI)         ui.py (Streamlit)    โ”‚
                    โ”‚  setup_logging() + UsageTracker + thread_id โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                     โ”‚ agent.invoke(message, callbacks=[tracker])
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 Orchestration      โ”‚  agent.py โ†’ build_agent()                   โ”‚
                    โ”‚  _resolve_model(): ANTHROPIC > OPENROUTER > โ”‚
                    โ”‚                    OPENAI (from config.py)  โ”‚
                    โ”‚  system prompt: prompts/system.md           โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                            โ”‚ tool calls           โ”‚ task (subagent)
                โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 Tools          โ”‚ tools.py             โ”‚  โ”‚ cv-tailor subagent     โ”‚
                โ”‚ search_jobs:         โ”‚  โ”‚ prompt: cv_tailor.md   โ”‚
                โ”‚  JSearch>JobSpy>mock โ”‚  โ”‚ reads profile + the    โ”‚
                โ”‚ get_job_details      โ”‚  โ”‚ references/ guides,    โ”‚
                โ”‚ read_profile         โ”‚  โ”‚ writes XYZ-format CV,  โ”‚
                โ”‚ submit_application โ†โ”€โ”ผโ”€โ”€โ”‚ save_application_pkg   โ”‚
                โ”‚  (human approval!)   โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 Data & output  โ”‚ schemas.py (Job, UsageRecord)   config.py (paths)โ”‚
                โ”‚ applications/  logs/job_agent.log  logs/usage.jsonlโ”‚
                โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Flow of one "apply to 1 and 3" turn:

  1. main.py/ui.py passes the user message to agent.invoke(...); the UsageTracker callback captures the tokens of every LLM call (main agent AND subagents).
  2. The main agent (prompt: prompts/system.md) calls search_jobs โ€” tools.py tries JSearch API โ†’ JobSpy scraper โ†’ sample data in order, normalizes results to schemas.Job and caches them in _JOB_CACHE.
  3. For each selected job the main agent spawns the cv-tailor subagent via the task tool (prompt: prompts/cv_tailor.md). The subagent reads the profile and the writing guides in references/, writes the CV/cover letter and saves them with save_application_package under applications/.
  4. submit_application pauses on interrupt_on; y/n in the CLI or โœ…/โŒ in the UI resumes the flow with Command(resume={"decisions": ...}).
  5. Every step is logged to logs/job_agent.log; every LLM call's tokens/cost go to logs/usage.jsonl (rates in config.PRICING โ€” approximate, keep updated).

File map

job_agent/
โ”œโ”€โ”€ config.py          # paths, model defaults, pricing table (single place)
โ”œโ”€โ”€ schemas.py         # Pydantic: Job, UsageRecord, ApplicationLogEntry
โ”œโ”€โ”€ prompts/           # system.md, cv_tailor.md, profile_builder.md (out of code)
โ”œโ”€โ”€ logging_conf.py    # logs/job_agent.log (rotating) + console warnings
โ”œโ”€โ”€ usage.py           # UsageTracker callback โ†’ tokens/cost โ†’ usage.jsonl
โ”œโ”€โ”€ agent.py           # deep agent wiring, model selection, subagent, HITL
โ”œโ”€โ”€ tools.py           # search (JSearch>JobSpy>sample), profile, references, save, apply
โ”œโ”€โ”€ profile_builder.py # CV file โ†’ LLM โ†’ profile/profile.md
โ”œโ”€โ”€ main.py            # interactive CLI
โ”œโ”€โ”€ ui.py              # Streamlit chat UI (approvals, downloads, cost metrics)
โ””โ”€โ”€ sample_jobs.py     # sample postings (last-resort fallback)
profile/               # profile.md (gitignored) + profile.example.md
references/            # CV writing guides (XYZ formula, Harvard guide, ...)
logs/                  # job_agent.log + usage.jsonl (gitignored)
applications/          # generated packages (gitignored)

Honesty notes

  • No automated form submission: neither the job boards' APIs nor their terms of service allow auto-filling application forms. The agent prepares the package, logs the application and opens the apply page; you press the final "Submit".
  • No fabrication on CVs: the subagent is instructed to use only the real facts in profile/profile.md โ€” still, read everything before sending it.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

job_hunt_agent-0.1.0.tar.gz (194.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

job_hunt_agent-0.1.0-py3-none-any.whl (25.9 kB view details)

Uploaded Python 3

File details

Details for the file job_hunt_agent-0.1.0.tar.gz.

File metadata

  • Download URL: job_hunt_agent-0.1.0.tar.gz
  • Upload date:
  • Size: 194.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for job_hunt_agent-0.1.0.tar.gz
Algorithm Hash digest
SHA256 0a618e1632dbb1f4a52b837bc2492016be6ceb822969a083d78709a40fcdcf3f
MD5 494e2ef2e296ccb86dc75afdcdb00117
BLAKE2b-256 c7369cf11c05e7039bde3b9b1bbf66d0ece5e753bee6e2f205cbfcf6fe305b66

See more details on using hashes here.

Provenance

The following attestation bundles were made for job_hunt_agent-0.1.0.tar.gz:

Publisher: publish.yml on EceDalpolat/job-hunt-agent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file job_hunt_agent-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: job_hunt_agent-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 25.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for job_hunt_agent-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fdba4be1794b7fa33d9900a41da6645b12a23f7cc458921d8fbf51de445af9b4
MD5 1a7a47ab7409dfcf844abe2efbb118f0
BLAKE2b-256 2bd311dcfaecce484fcd1e51225bf875a870415110f6e5e1fe3e35d259e6da95

See more details on using hashes here.

Provenance

The following attestation bundles were made for job_hunt_agent-0.1.0-py3-none-any.whl:

Publisher: publish.yml on EceDalpolat/job-hunt-agent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page