An AI-first job search platform that collects jobs from multiple sources, deduplicates and caches postings, performs semantic matching with embeddings, scores opportunities with AI, generates tailored resumes and cover letters, sends outreach emails, and tracks applications end-to-end.
Current status: MVP v1 is implemented and merged to main — live-backend frontend (no mock layer), Docker Compose stack, a configurable AI provider (local Ollama / Ollama Cloud / OpenAI, chosen independently for chat and embeddings), active-CV selection, documents + outreach, and basic hardening. After pulling, run alembic upgrade head to apply migrations. See TODO.md.
- Overview
- Documentation
- Current State
- Core Capabilities
- System Architecture
- Technology Stack
- Project Structure
- Team & Contributing
- Data Model
- Main Workflows
- Local Development Setup
- Environment Variables
- Database Migrations
- API Overview
- AI Layer
- Scraping and Ingestion
- Email Automation
- Frontend Overview
- Deployment
- Roadmap
- License
The platform helps users manage the full job-seeking workflow:
- Discover jobs from official APIs and supplementary scraping sources
- Normalize, deduplicate, and cache discovered postings in PostgreSQL
- Generate embeddings and index jobs in pgvector for semantic search
- Match uploaded CVs to relevant jobs using vector similarity and LLM re-ranking
- Score jobs, explain fit, and detect possible scams or low-quality postings
- Generate tailored resume snapshots and cover letters
- Compose and send outreach emails
- Surface direct apply links for the most relevant matches (user applies in their own browser)
- Track application status and outcomes
- Compute job-search statistics from stored records
The architecture is intentionally simple enough for an MVP, but structured enough to grow into a production-grade system.
| Document | Description |
|---|---|
| System Requirements | MVP feature checklist and business logic |
| Tech Stack | Approved technologies |
| Code Architecture | Layered backend design, AI layer, testing strategy |
| Data Layer | ORM models, pgvector, repositories, migrations |
| Docker Orchestration | Compose topology, healthchecks, volumes |
| Contributing Rules | Branch naming, commits, PR workflow |
| TODO | Active tasks by assignee |
| ADR 001: Queue Tool | ARQ + Redis for async workers |
| ADR 002: AI Layer | Embeddings, pgvector, local/API models |
| ADR 003: Apply Automation | Direct-apply links instead of browser automation |
| ADR 004: Jobs Scraping | Apify + official APIs, pluggable sources |
What exists today versus what the docs describe as the target:
| Area | Status |
|---|---|
| Documentation | Complete — requirements, architecture, data layer, Docker plan, ADRs |
| Backend | FastAPI app with auth, jobs, searches, CVs, applications APIs; ARQ workers; pytest in CI |
| Database / models | SQLAlchemy models + Alembic migrations (pgvector) |
| Auth | fastapi-users JWT with jobagent_auth cookie |
| ARQ workers | Ingestion, CV parse, embedding task stubs wired |
| Frontend | Next.js 16 dashboard with live API wiring, Vitest + ESLint in CI |
| Docker / infra | Compose stack in infra/docker/ |
| Tests | Backend pytest (Testcontainers); frontend Vitest |
Next steps: see TODO.md.
Target MVP capabilities (from system-requirements.md):
- User authentication and account management (JWT)
- CV upload, storage, and active-CV selection
- Job collection from official APIs and Apify-backed sources
- Normalization, deduplication, and caching of repeated postings
- Semantic matching via pgvector embeddings
- AI-based job scoring, explanations, and categorization
- Scam and risk detection with stored flags
- Tailored resume and cover letter generation
- Outreach email drafting and sending (Postmark or Gmail API)
- Application tracking with status pipeline
- Direct apply links for top matches (up to 10 relevant jobs)
- Background processing through ARQ workers
- Dashboard for jobs, applications, outreach, and statistics
flowchart LR
subgraph sources [JobSources]
APIs[OfficialAPIs]
Apify[ApifyActors]
end
subgraph backend [FastAPIBackend]
API[RESTAPI]
Workers[ARQWorkers]
end
subgraph storage [Storage]
PG[(PostgreSQL_pgvector)]
Redis[(Redis)]
S3[(S3_CVStorage)]
end
subgraph ai [AILayer]
Embed[Embeddings]
LLM[LanguageModel]
end
APIs --> Workers
Apify --> Workers
Workers --> PG
Workers --> Redis
API --> PG
Workers --> Embed
Workers --> LLM
API --> S3
Overview
This repository uses a single source-of-truth file for AI rules that automatically propagates to all supported AI tools on commit. Any contributor using a supported AI editor will receive the same rules without extra configuration, ensuring consistent guidance across the team.
How it works
ai-agents/*.md ──(pre-commit hook)──▶ .github/instructions/*.instructions.md (Copilot, Claude, Gemini, Hermes, Windsurf)
──(native @include)──▶ .cursor/rules/ (Cursor)
The repository pre-commit hook scans ai-agents/ for Markdown files, strips the leading frontmatter from each, and writes generated instruction files into .github/instructions/, staging them for commit. The Cursor client reads rules directly using a native @include directive that references files in ai-agents/, so Cursor consumers get the rules without creating copies.
Supported tools and where they read rules from
| Tool | Reads from | How |
|---|---|---|
| Cursor | .cursor/rules/global-rules.mdc |
@include directive, reads ai-agents/ directly, zero-copy |
| GitHub Copilot | .github/instructions/*.instructions.md |
native instructions folder, auto-applied to all files |
| Claude (claude.ai / Claude Code) | .github/instructions/*.instructions.md |
same as Copilot |
| Gemini | .github/instructions/*.instructions.md |
same as Copilot |
| Hermes | .github/instructions/*.instructions.md |
same as Copilot |
| Windsurf | .github/instructions/*.instructions.md |
same as Copilot |
How to add or update rules
- Edit or add a
.mdfile insideai-agents/— this is the only place you should ever edit rules - Run
git commitas normal — the pre-commit hook fires automatically - The hook syncs every
ai-agents/*.mdinto.github/instructions/*.instructions.mdand stages the results - Push — CI will verify the sync is correct on your PR
- Never edit
.github/instructions/files directly — they are generated and will be overwritten
Where to put rules for your specific tool
If you use Copilot / Claude / Gemini / Hermes / Windsurf — where do I put my rules?
Answer: edit ai-agents/global-rules.md or add a new ai-agents/<topic>.md file. Your tool picks it up automatically via .github/instructions/.
If you use Cursor — where do I put my rules?
Answer: same place — ai-agents/. Cursor reads it directly via @include, no copy needed.
Can I add a tool-specific file?
Answer: yes — add ai-agents/<toolname>.md and the hook will create .github/instructions/<toolname>.instructions.md automatically on next commit.
One-time setup (for new contributors)
git clone <repo-url>
cd <repo-name>
npm install
That's it — npm install activates the pre-commit hook and chmod via the prepare script. No other setup required.
CI enforcement
Every pull request that touches ai-agents/ or .github/instructions/ triggers a GitHub Actions workflow that diffs the source and generated files. If any generated instruction file is out of sync with its source, the workflow fails the PR and instructs contributors to run git commit locally so the pre-commit hook can update the generated files.
Bypassing the hook (not recommended)
If you use git commit --no-verify, the sync will not run. The CI check will catch this and fail your PR.
| Layer | Technologies |
|---|---|
| Frontend | Next.js, TypeScript, Tailwind CSS |
| Backend | FastAPI, Python, Pydantic, SQLAlchemy, Alembic, fastapi-users (JWT) |
| Database | PostgreSQL + pgvector |
| Queue / cache | ARQ, Redis |
| Scraping | Apify (Indeed, LinkedIn) + official APIs (Adzuna, Jooble, Careerjet, regional) |
| AI (local) | Ollama — nomic-embed-text, gemma3:4b |
| AI (cloud/BYOK) | Ollama Cloud (chat) and/or OpenAI (text-embedding-3-small, gpt-4o-mini) — chat and embeddings configured independently |
| Postmark or Gmail API | |
| CV storage | S3 |
| Infra | Docker |
Full details: tech-stack.md.
Monorepo layout (backend, frontend, and infra are all implemented):
job-agent/
├── backend/
│ ├── alembic/versions/ # migrations
│ ├── app/
│ │ ├── api/v1/routes/ # auth, cvs, jobs, searches, applications, documents, outreach
│ │ ├── core/ # config, db, security, logger
│ │ ├── integrations/ # S3, Apify, AI clients, Postmark, job sources
│ │ ├── middleware/ # auth rate limiting
│ │ ├── models/ # SQLAlchemy ORM
│ │ ├── repositories/ # data access layer
│ │ ├── schemas/ # Pydantic DTOs
│ │ ├── services/ # business logic (matching, ingestion, documents, outreach)
│ │ ├── workers/ # ARQ tasks
│ │ └── main.py # FastAPI entry + /health
│ ├── tests/ # pytest (Testcontainers + moto)
│ └── pyproject.toml
├── docs/ # requirements, architecture, data layer, ADRs
├── frontend/ # Next.js 16 App Router (TypeScript, Tailwind v4)
│ └── src/
│ ├── app/(auth)/ # login, register
│ ├── app/(dashboard)/ # dashboard, jobs, cvs, applications, documents, outreach, settings
│ ├── components/ # shared UI, layout, brand
│ ├── features/auth/ # auth context + hooks
│ ├── lib/api/ # typed API client (one module per resource)
│ └── test/ # Vitest setup
├── infra/
│ └── docker/ # Compose stack (Postgres, Redis, MinIO, Ollama, API, worker)
├── ai-agents/ # single-source AI rules (synced to .github/instructions/)
├── .env.example
└── README.md
Core developers: Pukakiii, Kyryll
Work is split in TODO.md:
Before opening a PR, read contributing-rules.md (branch naming, commit prefixes, rebase with main).
Guidelines:
- Keep services focused; business logic lives in
services/, not route handlers - Long-running work goes through ARQ workers, not request handlers
- Add Alembic migrations for every schema change
- Record significant architecture changes as ADRs in
docs/adr/ - Do not introduce technologies rejected in ADRs (e.g. Playwright for apply automation)
This project is for people who want real experience working on a team and building a production-shaped product — reading specs, following architecture, writing reviewable code, and shipping incrementally. If you are a vibecoder (copy-paste without understanding docs, skip conventions, or treat the repo as a playground for unrelated experiments), this is not the right fit. Please note that all contributor roles are voluntary. The project does not currently offer financial compensation, salaries, or contractor payments.
How to join
- Read the docs first. Work through docs/ — especially contributing-rules.md, system-requirements.md, code-architecture.md, and the ADRs. Skim the project structure and current state so you know what is implemented versus planned.
- Pick a task. Choose one open item from your role section in TODO.md (see team roles). Tasks are scoped for contributors and align with the MVP foundation.
- Open a pull request. Follow branch naming, commit prefixes, and the PR workflow in contributing-rules.md. Rebase on
main, keep the change focused, and explain what you did and why. - Team review. Core developers review your PR. We check that the work matches the docs (architecture, ADRs, conventions) and that the feature, fix, or contribution is effective — correct, maintainable, and useful to the project.
- Join the team. If the review passes, you are welcomed as a contributor with an ongoing role. If not, you are welcome to address feedback and try again with the same or another task.
Questions before you start? Open an issue or note your intent on the task you plan to take.
Core tables: users, cvs, jobs, searches, search_results, job_applications, generated_documents, outreach_emails. Jobs are shared corpus entities; per-user relevance lives on the search-result join. Full ERD, indexes, and repository patterns: data-layer.md.
Official APIs / Apify → ARQ workers → normalize → deduplicate → jobs
New job → embed → pgvector → AI analysis → persisted results
CV upload → parse profile → embed → similarity search → LLM re-rank → ranked jobs
Job + CV → AI generation → stored snapshot
Top matches → direct apply links → user applies → application record → status pipeline
Application statuses: saved, applied, interview, offer, rejected.
- Python 3.11+
- Node.js 20+
- PostgreSQL 15+ with pgvector (or Docker Compose — recommended)
- Redis
- Docker (recommended for full stack)
- Git
cp infra/secret/.env.backend.example infra/secret/.env.backend # set SECRET_KEY; choose AI provider
cd infra/docker
# Fully-local AI (also runs Ollama and pulls models):
docker compose --profile local-ai up -d
# ...or cloud AI (Ollama Cloud / OpenAI) — lighter, no local model container:
# docker compose up -d
# Create the tables once (after first boot):
docker compose run --rm api alembic upgrade headServices: Postgres (pgvector), Redis, MinIO, API (:8000), worker — plus Ollama when --profile local-ai is used.
cd backend
python -m venv venv
source venv/bin/activate
pip install -e ".[dev]"
# Ensure root .env exists (copy from .env.example) and Postgres/Redis/MinIO are reachable
uvicorn app.main:app --reloadHealth check: GET http://localhost:8000/health
cd frontend
cp .env.example .env.local
npm ci
npm run devDev server: http://localhost:3000 — requires the backend at http://localhost:8000.
Two env files serve different contexts:
| File | Used by | Purpose |
|---|---|---|
.env.example → .env at repo root |
Local uvicorn / pytest via config.py |
Pydantic Settings — DB, Redis, S3, AI keys |
infra/secret/.env.backend.example → infra/secret/.env.backend |
Docker Compose api and worker services |
Injected into containers; overrides hostnames (postgres, minio, ollama) |
OS environment variables take precedence over file values. Compose environment: block overrides env_file for service-specific hostnames.
Copy and adjust:
cp .env.example .env
cp infra/secret/.env.backend.example infra/secret/.env.backendKey variables (see .env.example for the full list):
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_DB=job_agent
SECRET_KEY=change-me
REDIS_URL=redis://localhost:6379
CHAT_PROVIDER=ollama # ollama | openai
EMBED_PROVIDER=ollama # ollama | openai
OLLAMA_BASE_URL=http://localhost:11434 # https://ollama.com for Ollama Cloud
OLLAMA_API_KEY= # required for Ollama Cloud
OPENAI_API_KEY= # required if CHAT_PROVIDER/EMBED_PROVIDER = openai
S3_ENDPOINT_URL=http://localhost:9000
FRONTEND_URL=http://localhost:3000Frontend: copy frontend/.env.example to frontend/.env.local with NEXT_PUBLIC_API_URL=http://localhost:8000.
Use Alembic for all schema changes. Migrations run as a one-off command — not on app startup.
cd backend
alembic revision --autogenerate -m "describe change"
alembic upgrade headRules: never edit production schema directly; keep migrations small; test locally before deploy.
REST surface under /api/v1. Implemented: auth, CVs (incl. active-CV), jobs, searches, applications, documents, outreach (see OpenAPI at /docs when the API is running).
| Domain | Endpoints |
|---|---|
| Auth | register, login, logout (fastapi-users JWT) |
| CVs | upload, list, presigned download, set active |
| Searches | trigger match, list, detail (embedded results) |
| Jobs | list, detail (with direct apply URL), ingest |
| Applications | CRUD + status transitions |
| Documents | generate resume / cover letter, list |
| Outreach | draft, send, list |
Scam/risk scoring is on the roadmap and not yet exposed as an endpoint.
Conventions: plural resource nouns, paginated lists, consistent error envelope — code-architecture.md.
Two phases per ADR 002:
Ingestion — embed job corpus (nomic-embed-text / text-embedding-3-small), store in pgvector, run analysis workers.
Query — parse CV → embed with search_query: prefix → cosine similarity → LLM re-rank → fit explanations.
Chat and embeddings are configured independently (CHAT_PROVIDER / EMBED_PROVIDER): fully-local Ollama by default, or mix Ollama Cloud chat with local/OpenAI embeddings, or OpenAI for both. 768-dim vectors throughout (Ollama Cloud has no embedding models, so cloud chat pairs with local or OpenAI embeddings). See infra/secret/.env.backend.example.
Pluggable JobSource interface. Official APIs are primary; Apify supplements boards without sanctioned APIs. API source wins on duplicate. All ingestion runs via ARQ — ADR 004.
AI-assisted drafting via Postmark or Gmail API. Emails are separate from application records (one job can have multiple outreach messages). Statuses: draft, sent, failed.
Next.js 16 App Router (src/ directory), TypeScript, Tailwind CSS v4. The dashboard is built and wired to the live backend, with route groups for unauthenticated ((auth)) and authenticated ((dashboard)) areas. API calls go through src/lib/api/ (one typed module per backend resource) and hit /api/v1/* same-origin via a Next.js rewrite — there is no mock layer at runtime. Pages are thin: they compose feature components and call typed API helpers.
Routes: login, register, dashboard, jobs, CVs, applications, documents, outreach, settings.
Conventions: job matches with scores and explanations, CV management, document generation, applications Kanban, outreach. Fetches from FastAPI only — no direct DB access. Surfaces direct apply links; no server-side browser automation — ADR 003.
Full folder layout: code-architecture.md.
Target topology (Docker): API + worker containers, managed PostgreSQL/pgvector, Redis, Next.js frontend, S3 for CVs, Ollama or BYOK AI, external Apify/Postmark. Full plan: docker-orchestration.md.
Recommended order: database + Redis → migrations → API → workers → frontend → S3 → AI/email/scraping credentials.
- Backend skeleton, config, layered structure
- Core tables and Alembic migrations
- Docker Compose local stack
- User authentication (JWT)
Next.js init— done; app shell (route groups, layouts, auth pages)
- Pluggable job sources
- ARQ workers for async ingestion
- pgvector index and embedding pipeline
- CV upload and S3 storage
- Semantic search and LLM re-ranking
- Scoring, explanations, scam checks
- Resume and cover letter generation
- Email generation and sending
- Dashboard, job detail, Kanban board
- Application pipeline and statistics
- Validation, logging, rate limiting, test coverage, production deploy polish
Job Agent is source-available, licensed under the PolyForm Noncommercial License 1.0.0. Free to use, modify, and share for any noncommercial purpose (personal, study, research, nonprofit/educational/government). Commercial use is reserved and requires a separate license from the copyright holder. Copyright © 2026 Igor Pukaki. All commercial rights reserved. See the LICENSE file.