Job Search Knowledge Bundle
Bundle OKF 0.1 · 22 conceitos · GregOratOr/job-search
Open source Repository Open in the app JSON README (API)
About
# Job Search Knowledge Bundle
OKF v0.1 knowledge base for the harness-agnostic job-search toolbox. Start here for
agent-friendly structured reference; pair with [CONTEXT.md](/CONTEXT.md) for the canonical
glossary and [AGENTS.md](/AGENTS.md) for operational workflows.
## Architecture
* [Harness-agnostic design](/okf/architecture/harness-agnostic-design.md) - toolbox + skills, not a single agent
* [Web access policy](/okf/architecture/web-access-policy.md) - harness-native web first; scripts/web.py fallback
* [Autonomy ceiling](/okf/architecture/autonomy-ceiling.md) - Saved ceiling; never submit or send
* [Private overlay](/okf/architecture/private-overlay.md) - how private/ submodule routes data
## Glossary (OKF concepts)
* [Harness](/okf/glossary/harness.md)
* [Skill](/okf/glossary/skill.md)
* [Tool](/okf/glossary/tool.md)
* [Bundle](/okf/glossary/bundle.md)
* [Profile](/okf/glossary/profile.md)
* [Shortlist](/okf/glossary/shortlist.md)
* [Platform playbook](/okf/glossary/platfor
Details
- Kind
- OKF bundles
- Topic
- AI, RAG & memory
- Publisher
- gregorator
- Origin
- okf_github
- Category
- dados
- Version
- 0.1
- Last push
- 2026-08-11T09:24:41Z
- Repository state
- ativo
- Language
- Python
- License
- MIT
- Added
- 2026-09-09 05:03:59
- Updated
- 2026-09-09 05:03:59
- Origin id
GregOratOr/job-search:okf/index.md
README
# Job Search
A **harness-agnostic toolbox** for automating a tech job search end to end: discover roles,
tailor a resume + cover letter, draft networking outreach, research companies, track
applications, and follow up — fully autonomously or one step at a time.
The project is deliberately **not** a single hard-wired agent. It is a set of portable
**skills** (markdown instructions) plus callable **tools** (CLI scripts). Whatever
model/harness you plug in becomes the "brain":
- **Local & free first:** a tool-use Ollama model (e.g. `qwen3.6:27b`) under a harness
like Hermes, VS Code chat, Claude Code, or Cursor.
- **Cloud when quota allows:** Anthropic (`claude-opus-4-7`) / OpenAI / OpenRouter via the same scripts.
This public repo ships with **template/example data** so you can explore the tooling without
exposing personal information. Your real data lives in a **private git submodule** at
`private/` and is picked up automatically (see [Private data & the overlay](#private-data--the-overlay)).
> New to the design? Read [`CONTEXT.md`](CONTEXT.md) (glossary),
> [`okf/index.md`](okf/index.md) (OKF knowledge bundle), and
> [`docs/adr/`](docs/adr) (why key decisions were made).
---
## How it works
```mermaid
flowchart TD
Harness["Harness + model (Ollama+Hermes / Cursor / Claude Code / OpenRouter)"]
Skills["skills/*/SKILL.md (portable when/how)"]
Orchestrator["scripts/pipeline.py (batch / autonomy_level)"]
Tools["CLI tools: web, ai_tailor, build, bundle, find_contacts, research, track, followup"]
Bundle["applications/jobs/{id}/ (jd, job_info, {id}_resume.pdf, {id}_cover_letter.pdf, networking.md)"]
Harness --> Skills
Skills --> Tools
Skills -.batch.-> Orchestrator
Orchestrator --> Tools
Tools --> Bundle
```
- **Skills** (`skills/<name>/SKILL.md`) tell the harness *when* and *how* to do something
(preferred path in an agent session).
- **Tools** (`scripts/*.py`) do the actual work and can be invoked from any shell.
- **Batch pipeline** (`pipeline.py`) chains tools for unattended runs — optional when a
harness can follow a skill step-by-step.
- **Bundle**: every application gets one folder, `applications/jobs/<id>/`, holding everything
you upload — see [The application bundle](#the-application-bundle).
### The Saved ceiling (safety)
No matter how autonomous a run is, the automation **never submits an application** and
**never sends a message**. The maximum action it takes is preparing the bundle and logging the
application as `Saved`. You upload the files and send outreach yourself. (See
[`docs/adr/0002-autonomy-ceiling.md`](docs/adr/0002-autonomy-ceiling.md).)
### Web access (harness-native first)
When your harness already provides web search and page extraction (Hermes `web_search` /
`web_extract`, Cursor browser MCP, Anthropic `web_search`), **use those tools directly**.
Do not invoke `scripts/web.py` from an agent session.
`scripts/web.py` is the **fallback** for standalone terminal runs and scripted pipelines.
Set `WEB_BACKEND` explicitly in `.env` (`searxng` | `tavily` | `brave` | `serper` | `harness`)
plus that backend's URL/key or harness adapters — there is no implicit default. See
[`okf/architecture/web-access-policy.md`](okf/architecture/web-access-policy.md).
---
## Setup (one time)
```bash
# 1. Install uv (recommended runner / dependency manager)
# macOS / Linux:
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows (PowerShell):
# powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
# or: pip install uv
# 2. Clone and sync dependencies
git clone https://github.com/yourusername/job-search
cd job-search
uv sync
# 3. Configure settings (LLM + web). Copy the example and edit:
cp .env.example .env
# For scripted web: set WEB_BACKEND=… (required — no default)
# 4. (Optional) Add your private data submodule — see below
git submodule add git@github.com:YOUR_USER/job-search-private.git private
git submodule update --init private
# NOTE: if you cloned/forked this repo, .gitmodules points at the original
# author's private repo, which you cannot access. Either edit .gitmodules to
# point at your own private repo (then `git submodule sync`) or remove the
# submodule entry entirely (`git rm --cached private` + delete the
# .gitmodules entry) and run without the overlay.
# 5. (Optional) Install LaTeX for PDF compilation (TeX Live or MiKTeX)
# Needed for --pdf / --bundle. Without it, scripts still generate .tex files.
```
Run any tool with `uv run`, e.g. `uv run scripts/pipeline.py --help`.
---
## Settings
There are two places to configure the project: **`.env`** (secrets + runtime providers) and
**`config/job_search_config.yaml`** (search preferences + automation behavior). When the
private overlay is present, the versions under `private/` win.
### `.env` — providers and keys
| Variable | Purpose |
|----------|---------|
| `LLM_PROVIDER` | `ollama` (local) \| `anthropic` \| `openai` (also covers OpenRouter/LM Studio/vLLM) |
| `LLM_MODEL` | Global default model for the active provider (e.g. `qwen3.6:27b`, `claude-opus-4-7`) |
| `LLM_MODEL_TAILOR` / `LLM_MODEL_AUDIT` / `LLM_MODEL_RESEARCH` / `LLM_MODEL_DISCOVERY` / `LLM_MODEL_FOLLOWUP` | Optional per-task pins — used only with `--use-agent` |
| `OLLAMA_BASE_URL` / `OLLAMA_MODEL` | Local Ollama endpoint + model (see `.env.example`) |
| `ANTHROPIC_MODEL` | Anthropic model when no `LLM_MODEL*` is set |
| `ANTHROPIC_API_KEY` | Needed when `LLM_PROVIDER=anthropic` (default model: `claude-opus-4-7`) |
| `OPENAI_BASE_URL` / `OPENAI_API_KEY` / `OPENAI_MODEL` | Any OpenAI-compatible endpoint (OpenRouter, LM Studio, vLLM; default model: `gpt-4o`) |
| `WEB_BACKEND` | Required for scripted web I/O: `searxng` \| `tavily` \| `brave` \| `serper` \| `harness` |
| `SEARXNG_URL` | SearXNG instance when `WEB_BACKEND=searxng` (default `http://127.0.0.1:8888`) |
| `HARNESS_WEB_SEARCH_CMD` / `HARNESS_WEB_FETCH_CMD` | Adapters when `WEB_BACKEND=harness` |
| `TAVILY_API_KEY` / `BRAVE_API_KEY` / `SERPER_API_KEY` | Keys for the matching `WEB_BACKEND` |
| `ENRICHMENT_PROVIDER=hunter` + `HUNTER_API_KEY` + `--domain` | Optional paid contact lookup (off by default; Hunter only) |
> **Web access is required** for discovery, research, and contact-finding when running
> scripts standalone, and `WEB_BACKEND` must be set explicitly (scripts exit with a clear
> message when it isn't). Agent harnesses with built-in web tools do not need `WEB_BACKEND`.
> A free option is a self-hosted [SearXNG](https://docs.searxng.org/) — set
> `WEB_BACKEND=searxng` and point `SEARXNG_URL` at it. Or pick a hosted API and provide its key.
### `config/job_search_config.yaml` — preferences + automation
Tune `target_roles`, `target_companies`, `search_terms`, `profile.preferred_locations`,
`networking` (including `follow_up_delay_days` and `alumni_networks`), and automation:
```yaml
automation:
# discover_only : find + shortlist jobs only
# tailor : also tailor resume / cover letter / outreach drafts
# full_bundle : also research, build PDFs, audit, finalize the bundle, log "Saved"
autonomy_level: full_bundle
max_jobs: 5
build_pdf: true # compile PDFs (needs pdflatex)
# web_backend: searxng # optional; env WEB_BACKEND wins. One of them must be set
# explicitly for scripted web I/O — there is no default.
contact_enrichment: false # reminder only — enrichment is controlled by .env + CLI
```
`config/platforms.yaml` is an **agent playbook** (board filters, career URLs, apply checklists).
It is not loaded by Python scripts today — see
[`okf/glossary/platform-playbook.md`](okf/glossary/platform-playbook.md).
---
## Private data & the overlay
The public repo holds **example** profile/config so the tooling runs out of the box. Your real,
sensitive data lives in a **separate private repo** mounted as a submodule at `private/`.
**How routing works** (`scripts/data_paths.py`): when `private/profile/` exists, every script
reads and writes your data under `private/` instead of the public templates, and `.env` is read
from `private/.env` first. Nothing else needs to change — the same commands operate on your real
data automatically.
Your private repo should mirror this structure:
```
private/
├── .env # your API keys / endpoints (never on a public remote)
├── config/job_search_config.yaml
├── profile/ # real contact info, experience, projects, skills, summaries
├── resume/outputs/ # per-job resume sources + builds ({id}.py/.tex/.pdf)
├── coverletter/outputs/ # per-job cover letter sources + builds ({id}_cl.py/.tex/.pdf)
├── applications/
│ ├── tracker.csv # your master application log
│ ├── shortlists.md # optional agent discovery shortlist log
│ └── jobs/<id>/ # one bundle folder per application
└── networking/ # connections.csv, message templates, strategy
```
> The `profile/` directory is **read-only** to agents unless you explicitly ask to update it.
> Tailoring never edits `profile/` — job-specific changes go into `resume/outputs/<id>.py`
> and `coverletter/outputs/<id>_cl.py` via `dataclasses.replace()`.
---
## Usage
### Agent discovery (preferred in a harness)
Follow [`skills/discover-jobs/SKILL.md`](skills/discover-jobs/SKILL.md): shortlist open roles
in chat, append a session to `applications/shortlists.md`, then hand accepted jobs to
`new-application` / `ai_tailor`. Use harness-native web — do not call `scripts/web.py`.
### Run the whole pipeline (scripted / unattended)
`scripts/pipeline.py` chains **discover → tailor → research → build → audit → bundle →
track(Saved)**, stopping at your configured `autonomy_level`.
```bash
# Use config defaults (autonomy_level + max_jobs); opt in to project web
uv run scripts/pipeline.py --use-project-web
# Override how far it goes / how many jobs
uv run scripts/pipeline.py --level tailor --max 3 --use-project-web
uv run scripts/pipeline.py --query "LLM inference engineer remote" --max 5 --use-project-web
# A single known posting, end to end
uv run scripts/pipeline.py --url "https://careers.example.com/123" --id acme_ml_2026 --use-project-web
uv run scripts/pipeline.py --jd jd.txt --id acme_ml_2026 # local JD — no web flag
# Piecewise on an existing application (skip discovery)
uv run scripts/pipeline.py --id acme_ml_2026 --steps build,bundle,track
uv run scripts/pipeline.py --id acme_ml_2026 --steps audit # audit only
# Pause for confirmation before each per-job step
uv run scripts/pipeline.py --gate --use-project-web
```
`--use-project-web` is required for unattended discovery/fetch/research/contacts (ADR 0004).
`WEB_BACKEND` must be set in `.env` (`searxng` | `tavily` | `brave` | `serper` | `harness`).
`--search-mode` is retired. Discovery helpers are in `job_discovery.py` (library only).
The `audit` step runs at `full_bundle` and writes advisory `audit.md`; it never edits
profile or per-job outputs sources.
### Run steps individually (manual / piecewise)
```bash
# Discover jobs (scripted preview / batch) — agents prefer the discover-jobs skill
uv run scripts/pipeline.py --max 5 --dry-run --use-project-web
uv run scripts/pipeline.py --max 5 --build --use-project-web
# AI-tailor straight from a JD (writes the whole bundle scaffold)
uv run scripts/ai_tailor.py --url "https://..." --id acme_ml_2026 --use-project-web
uv run scripts/ai_tailor.py --jd jd.txt --id acme_ml_2026
# Manual scaffold without AI
uv run scripts/new_application.py --id acme_ml_2026 --company Acme --role "ML Engineer"
# ...edit resume/outputs/acme_ml_2026.py + coverletter/outputs/acme_ml_2026_cl.py...
# Audit the tailored documents before submitting (also a pipeline step)
uv run scripts/audit.py --id acme_ml_2026
# Build + finalize the upload bundle (compile PDFs, move into the app folder, clean temp)
uv run scripts/build.py --id acme_ml_2026 --bundle
uv run scripts/bundle.py --id acme_ml_2026 # finalize only
# Web search / page fetch (requires WEB_BACKEND)
uv run scripts/web.py search "ML engineer remote 2026" --max 8
uv run scripts/web.py fetch "https://example.com/job/123"
# Research a company/topic into a sourced brief (unattended runs need the web opt-in)
uv run scripts/research.py "Anthropic interview process" --id acme_ml_2026 --focus "interview prep" --use-project-web
# Find networking contacts (queries + public pages; no LinkedIn scraping).
# Without --use-project-web it emits search queries only (no page scan).
uv run scripts/find_contacts.py --company Acme --role "ML Engineer" --id acme_ml_2026 --use-project-web
# Track applications
uv run scripts/track.py log --id acme_ml_2026 --platform "Company Website" --url https://...
uv run scripts/track.py update --id acme_ml_2026 --status "Phone Screen"
uv run scripts/track.py list
# Follow up on stale applications (drafts only — never sends)
uv run scripts/followup.py --list-only
uv run scripts/followup.py
# Validate profile imports / inventory (read-only)
uv run scripts/validate_profile.py --inventory
uv run scripts/validate_profile.py
```
---
## The application bundle
Every application ends up as one self-contained folder you can upload from:
```
applications/jobs/<id>/
├── jd.txt # saved copy of the job description
├── job_info.py # company, role, url, keywords, networking targets
├── <id>_resume.py # resume source (after --bundle)
├── <id>_resume.tex # kept for future manual recompiles
├── <id>_resume.pdf # ← upload this
├── <id>_cover_letter.py
├── <id>_cover_letter.tex
├── <id>_cover_letter.pdf # ← upload this
├── networking.md # drafted outreach + contacts + follow-ups (never auto-sent)
├── research.md # optional company/role research brief
└── audit.md # optional pre-submit critique
```
Before bundling, sources live under:
| Kind | Path |
|------|------|
| Resume | `resume/outputs/<id>.{py,tex,pdf}` |
| Cover letter | `coverletter/outputs/<id>_cl.{py,tex,pdf}` |
`bundle.py` (or `build.py --bundle`) **moves** `.py` / `.tex` / `.pdf` into the job folder as
`{id}_resume.*` / `{id}_cover_letter.*` and deletes LaTeX temp files:
`.aux .log .out .toc .lof .lot .fls .fdb_latexmk .synctex.gz .nav .snm .vrb .bbl .blg`
---
## Skills
Portable instructions in `skills/<name>/SKILL.md`, each paired with a CLI tool or templates:
| Skill | Tool | Use when |
|-------|------|----------|
| `run-pipeline` | `pipeline.py` | Run the whole thing E2E or piecewise (batch) |
| `discover-jobs` | harness web + optional `pipeline.py` | Shortlist open roles; hand off accepted jobs |
| `new-application` | `new_application.py` / `ai_tailor.py` | Scaffold or fully prep one application |
| `tailor-resume` | agent or `ai_tailor.py` | Tailor the resume from a JD |
| `tailor-coverletter` | agent or `ai_tailor.py` | Tailor the cover letter from a JD |
| `build-documents` | `build.py` / `bundle.py` | Render `.tex`, compile PDFs, finalize the bundle |
| `research` | harness web or `research.py` | Research a company/topic into a brief |
| `find-contacts` | harness web or `find_contacts.py` | Find recruiters/hiring managers |
| `networking-outreach` | (templates) | Draft LinkedIn/email outreach |
| `follow-up` | `followup.py` | Surface + draft follow-ups for stale apps |
| `audit-application` | agent or `audit.py` | Critique resume/CL before submitting |
| `track-application` | `track.py` | Log/update applications in `tracker.csv` |
| `update-profile` | (edit `profile/` +) `validate_profile.py` | Edit `profile/` — ONLY on explicit command; then validate |
---
## Project structure
```
job-search/
├── CONTEXT.md ← glossary (harness, skill, tool, bundle, shortlist, ...)
├── okf/ ← OKF v0.1 knowledge bundle for agents and humans
├── docs/adr/ ← architecture decision records
├── LICENSE ← MIT
├── private/ ← your private git submodule (real data) — overlays the templates
├── profile/ ← template/example data (public; read-only to agents)
├── resume/ ← LaTeX engine + outputs/ (templates in tailoring/)
│ ├── outputs/ ← {id}.py / .tex / .pdf
│ └── tailoring/_template.py
├── coverletter/ ← LaTeX engine + outputs/ (`{id}_cl.*`)
│ ├── outputs/
│ └── tailoring/_template.py
├── applications/ ← tracker + shortlists + per-job bundles
│ ├── tracker.csv
│ ├── shortlists.md ← agent discover-jobs sessions (create if absent)
│ └── jobs/
│ ├── _template/
│ └── <id>/
├── networking/ ← strategy, message templates, connections
├── config/ ← job_search_config.yaml + platforms.yaml (playbook; agent-only)
├── skills/ ← SKILL.md files (portable instructions)
│ ├── audit-application/
│ ├── build-documents/
│ ├── discover-jobs/
│ ├── find-contacts/
│ ├── follow-up/
│ ├── networking-outreach/
│ ├── new-application/
│ ├── research/
│ ├── run-pipeline/
│ ├── tailor-coverletter/
│ ├── tailor-resume/
│ ├── track-application/
│ └── update-profile/
├── tests/ ← pytest suite (`uv run pytest`)
└── scripts/
├── pipeline.py ← E2E orchestrator (autonomy_level; --use-project-web)
├── job_discovery.py ← discovery library (no CLI; used by pipeline)
├── ai_tailor.py ← JD → tailored resume/CL/outreach
├── audit.py ← hiring-manager critique → audit.md
├── new_application.py ← scaffold a bundle without AI tailoring
├── build.py / bundle.py ← compile + finalize the upload bundle
├── web.py ← search + fetch (pluggable backend; explicit WEB_BACKEND)
├── research.py ← company/topic research briefs
├── find_contacts.py ← contacts via queries + public pages
├── followup.py ← follow-up surfacing + drafting
├── track.py ← application tracker
├── validate_profile.py ← profile validation + inventory
├── llm_provider.py ← provider-agnostic LLM access
├── json_llm.py ← JSON-mode LLM helper (shared by AI tools)
├── text_utils.py ← shared text helpers
├── job_info_io.py ← safe job_info.py reads/writes
├── bootstrap.py ← shared CLI bootstrap (paths + env)
└── data_paths.py ← private overlay + {id} / {id}_cl document paths
```
---
## Requirements
- Python 3.10+ and [uv](https://docs.astral.sh/uv/)
- An LLM configured in `.env` (local Ollama, or a cloud key)
- Web backend for **scripted** discovery/research/contacts when no harness web tools exist
(set `WEB_BACKEND` in `.env` — e.g. a self-hosted SearXNG or a hosted API key);
harness-native web needs no `WEB_BACKEND`
- LaTeX (TeX Live or MiKTeX) for PDF compilation (`--pdf` / `--bundle`)
---
## License & contributions
Licensed under the [MIT License](LICENSE) — free to use, modify, and fork.
This is a personal project shared as-is. Contributions are not actively solicited,
but pull requests are welcome; all merges require the maintainer's review and
approval. Please open an issue first for anything larger than a small fix.