💼 Job Hunt Agent
An end-to-end job search + tailored CV generation agent built with LangChain deepagents:
- builds your master profile (
profile/profile.md) from an uploaded CV, - searches postings on LinkedIn/Indeed and more (JobSpy scraper — no API key needed; optional JSearch API),
- ranks them against your profile with honest fit scores,
- for every job you pick, a cv-tailor subagent writes a job-specific CV +
cover letter (XYZ-formula bullets, ATS-friendly) into
applications/, - 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_KEYorOPENAI_API_KEY(priority in that order). Override the default model withJOB_AGENT_MODEL— e.g.openai/gpt-5.1on 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_KEYswitches 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 theremember_facttool, recalled at the start of every conversation withrecall_memory, removable withforget_fact. - Application history: every approved application
(
applications/applications_log.jsonl) and every generated CV package are surfaced throughrecall_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:
main.py/ui.pypasses the user message toagent.invoke(...); theUsageTrackercallback captures the tokens of every LLM call (main agent AND subagents).- The main agent (prompt:
prompts/system.md) callssearch_jobs—tools.pytries JSearch API → JobSpy scraper → sample data in order, normalizes results toschemas.Joband caches them in_JOB_CACHE. - For each selected job the main agent spawns the cv-tailor subagent via
the
tasktool (prompt:prompts/cv_tailor.md). The subagent reads the profile and the writing guides inreferences/, writes the CV/cover letter and saves them withsave_application_packageunderapplications/. submit_applicationpauses oninterrupt_on; y/n in the CLI or ✅/❌ in the UI resumes the flow withCommand(resume={"decisions": ...}).- Every step is logged to
logs/job_agent.log; every LLM call's tokens/cost go tologs/usage.jsonl(rates inconfig.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)
| File | Size | Uploaded | |
|---|---|---|---|
| job_hunt_agent-0.2.0.tar.gz | 182.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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