Skip to main content

💼 Job Hunt Agent

PyPI CI Python License: MIT

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, recall_memory, remember_fact, forget_fact
backend=FilesystemBackend(...) Built-in file tools read the real project dir (profile/, applications/), path-locked to the repo root
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

pip install job-hunt-agent   # or: uv tool install job-hunt-agent

or from source:

git clone https://github.com/EceDalpolat/job-hunt-agent && cd job-hunt-agent
uv sync
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. A structured profile/profile.json (typed via a Pydantic schema) is generated alongside it for reliable programmatic access. Review both before applying anywhere. See profile/profile.example.md for the expected shape. (profile/profile.md, profile/profile.json and profile/career_memory.json are gitignored — your personal data stays local.)

Career memory

The agent keeps long-term memory across chat sessions:

  • Facts (profile/career_memory.json): target companies, remote/salary preferences, decisions — saved via the remember_fact tool, recalled at the start of every conversation with recall_memory, removable with forget_fact.
  • Application history: every approved application (applications/applications_log.jsonl) and every generated CV package are surfaced through recall_memory, so the agent won't re-suggest a job you already applied to — even in a brand-new chat.

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/            # bring-your-own CV writing guides (gitignored; see its README)
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.

Metadata

Release files for job-hunt-agent 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for job-hunt-agent 0.2.0
File Size Uploaded
job_hunt_agent-0.2.0.tar.gz 182.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for job-hunt-agent 0.2.0
File Interpreter ABI Platform
job_hunt_agent-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 213.3 kB

Release files / job_hunt_agent-0.2.0.tar.gz

Download URL job_hunt_agent-0.2.0.tar.gz
Size 182.8 kB
Tags Source
SHA-256 checksum
How to use checksums
6df771b23a715adb74ff6599e4ccfd86a7cc493c7b6aa1cb2cdb406951469db2
BLAKE2b-256 checksum
How to use checksums
f39d33b318dc5a6510132a789f75fe82922e6bb59b4e24372e0c9fd6aea498c9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 10, 2026.

Transparency log

Release files / job_hunt_agent-0.2.0-py3-none-any.whl

Download URL job_hunt_agent-0.2.0-py3-none-any.whl
Size 30.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
809f7441bd1a59d173ca071fdf9998f0327a274fa51b752b694d543f57590095
BLAKE2b-256 checksum
How to use checksums
2acbdf0d90acb56b732ac7aae1b6647b797d005bf5ad01dc814256a95e12b8ef
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 10, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page