Velto is a personal, local job application agent (Tsenta-style). Runs on your machine: monitor company boards, score fit, tailor résumés, auto-apply where possible, and track outcomes — without a hosted SaaS.
Stack: FastAPI · SQLite · Playwright · React · Vite · Tailwind · Ollama (or Gemini / rules)
| Area | Capabilities |
|---|---|
| Profile | Upload résumé (PDF/DOCX) — auto-fills name, contact, skills, target titles; automation toggles |
| Jobs | Paste URLs (Greenhouse, Lever, Ashby, Workday, gh_jid= links); match %, tailor, apply |
| Monitoring | 24/7 watchlist scans (tiered intervals), CSV import, scan logs, manual scan, repair boards |
| Applications | Status pipeline, error messages, apply receipts & screenshots |
| Inbox | IMAP sync, classify mail (confirmation / interview / rejection / offer), link to jobs |
| Dashboard | Open roles, match pipeline, applications, inbox stats, AI health |
- Polls Greenhouse, Lever, and Ashby job boards on a schedule (tier A ≈ 15 min, B ≈ 60 min, C ≈ 6 h).
- Adds new postings when they appear on a board.
- Closes roles that disappear from a board (
PRUNE_CLOSED_JOBS, default on) — list stays fresh even when nothing new is posted. - Re-opens a role if it returns to the board later.
- Skips closing jobs you are actively working (tailoring, ready, in-flight applications).
- LLM providers:
ollama(local, recommended) ·gemini·rules(instant keywords, no AI). - Monitor scans use fast rule-based matching by default (
MONITOR_RULES_ONLY=true) so boards do not block on Ollama. - Tailoring: per-job résumé rewrite with diff view + cover letter (PDF export).
- Auto-apply: Playwright (Greenhouse, Lever, Ashby, Workday best-effort). Set
AUTO_APPLY_ENABLED=trueonly when you trust submit behavior.
- Dark / light mode (system default, toggle in sidebar).
- Jobs list: newest first or best match, auto-refresh, show closed toggle, open vs closed counts.
- Monitoring: live scan stats including closed (24h).
- Python 3.13
- Node 18+
- Ollama (recommended) or a Gemini API key
- Playwright Chromium (for auto-apply):
cd backend && .venv/bin/playwright install chromiumcp .env.example .env
# Edit .env — quote passwords: IMAP_PASSWORD="your app password"
# Ollama (recommended)
brew install ollama && ollama serve
chmod +x scripts/setup-ollama.sh && ./scripts/setup-ollama.sh
# Backend
cd backend
python3.13 -m venv .venv
.venv/bin/pip install -r requirements.txt -r requirements-dev.txt
.venv/bin/playwright install chromium
# Frontend
cd ../frontend && npm install
# Run from project root
cd ..
chmod +x scripts/dev.sh scripts/setup-playwright.sh
./scripts/dev.sh| Service | URL |
|---|---|
| App | http://localhost:5173 |
| API | http://127.0.0.1:8000 |
| OpenAPI | http://127.0.0.1:8000/docs |
Health check:
curl http://127.0.0.1:8000/api/health- Profile — upload résumé, set skills & target titles; optional monitor auto-tailor / auto-apply toggles.
- Monitoring — Load starter watchlist or Import CSV (
docs/watchlist_template.csv). - Wait for scans (or Scan now on a source). New roles land under Jobs; removed board postings move to closed.
- Jobs — review matches, tailor, queue & apply. Paste one-off URLs (including Workday) here.
- Applications — track
submitted,needs_you,failed; open screenshots when present. - Inbox — set
EMAIL_ENABLED=trueand IMAP vars, then Sync inbox.
| Variable | Default | Description |
|---|---|---|
LLM_PROVIDER |
ollama |
ollama · gemini · rules |
OLLAMA_BASE_URL |
http://127.0.0.1:11434 |
Ollama API |
OLLAMA_MODEL |
llama3.2:3b |
Model for match/tailor |
LLM_FAST_MODE |
true |
Shorter prompts in dev |
GEMINI_API_KEY |
— | Required if LLM_PROVIDER=gemini |
GEMINI_MODEL |
gemini-2.5-flash |
Primary Gemini model |
AUTO_APPLY_ENABLED |
false |
Playwright clicks final submit |
MATCH_THRESHOLD |
60 |
Min score to treat as matched |
MAX_APPS_PER_DAY |
10 |
Daily submit cap |
MONITOR_ENABLED |
true |
Background watchlist worker |
MONITOR_TICK_SECONDS |
60 |
How often due scans are checked |
MONITOR_RULES_ONLY |
true |
Keyword match during board scans (no LLM per job) |
PRUNE_CLOSED_JOBS |
true |
Mark jobs missing from latest board scan as closed |
EMAIL_ENABLED |
false |
IMAP inbox sync |
IMAP_HOST / IMAP_PORT / IMAP_USER / IMAP_PASSWORD / IMAP_FOLDER |
Gmail defaults | Use App Password for Gmail |
EMAIL_SYNC_LIMIT |
50 |
Max messages per sync |
CORS_ORIGINS |
localhost:5173 | Frontend origins |
Tip: Always quote values with spaces or special characters in .env, e.g. IMAP_PASSWORD="xxxx xxxx".
company_name,ats_type,board_slug,tier
Stripe,greenhouse,stripe,A
Netflix,lever,netflix,CTemplate: docs/watchlist_template.csv
- Supported board types:
greenhouse,lever,ashby - Workday is not board-monitored — paste
myworkdayjobs.comURLs on the Jobs page
- API stores all jobs in SQLite; the UI lists up to 200 at a time (
GET /api/jobs/countshows open vs closed totals). - Default sort: newest first; switch to best match on the Jobs page.
- Closed roles (removed from boards) are hidden unless Show closed is on.
- Recompute match % refreshes scores only — it does not fetch boards (monitoring does that).
- If scans show
0 newbut the list changes, pruning may have closed stale postings — check Monitoring scan logs.
| Status | Meaning |
|---|---|
queued / tailoring / ready |
Pipeline in progress |
needs_you |
Form prefilled; finish CAPTCHA, custom questions, or submit manually |
submitted |
Playwright (or you) completed submit |
failed |
Error recorded on the application |
skipped |
Below threshold or skipped intentionally |
Job status closed = role no longer on the company board (still in DB; visible with Show closed).
./scripts/test.shCovers watchlist CSV parsing, LLM JSON extraction, email classification, board ingest/dedupe, closed-job pruning, and reopen logic.
job_applyer/
├── backend/
│ ├── app/
│ │ ├── routers/ # profile, jobs, applications, watchlist, inbox, dashboard
│ │ ├── services/ # matcher, tailor, apply, board_fetcher, board_prune, email_*
│ │ ├── workers/ # monitor (24/7 scans)
│ │ └── models/ # SQLAlchemy entities
│ ├── data/ # SQLite DB, résumés, receipts (gitignored)
│ └── tests/
├── frontend/ # React SPA
├── docs/
│ ├── watchlist_template.csv
│ └── AUDIT.md # architecture & gap notes
└── scripts/
├── dev.sh # start API + Vite (frees ports 8000/5173 on exit)
├── test.sh
├── setup-ollama.sh
└── setup-playwright.sh
- Built for single-user local use. There is no API authentication — keep the backend on
127.0.0.1only. - Do not expose port
8000to your LAN or the internet without adding auth. .envis gitignored; never commit API keys or app passwords.
- Workday forms vary widely; many runs end in
needs_you. - Email → job linking is heuristic (company + title); use manual link in Inbox when wrong.
- Large job DBs: list is capped at 200 rows; use filters/sort and closed pruning to keep the feed useful.
- Ollama is local with no API quota; slowness usually means model load or CPU, not rate limits.
For a deeper review (gaps, fixes, roadmap), see docs/AUDIT.md.
| Phase | Focus |
|---|---|
| 1 | Profile, job intake, match, tailor, apply, tracker, dashboard |
| 2 | 24/7 watchlist, Lever/Ashby, automation pipeline, monitoring UI |
| 3 | CSV import, Workday adapter, IMAP inbox |
| Recent | Closed-job pruning, jobs UX (sort/refresh/closed), light/dark UI, pytest suite, audit fixes |