Install
$ agentstack add skill-chenxi-bot21-daily-job-matcher-ai-job-search-pipeline ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Daily Job Matcher
An end-to-end job-search assistant: scrape → knockout-gate → score → curate → Notion, plus email → status updates. The mechanical steps run as a tested Python package (jobscreener); the judgement steps (reading full job descriptions, curating true fit, writing to Notion) are done by you with the Notion/Gmail MCP connectors.
When to use
Trigger for: "find me jobs that fit my resume", "screen these LinkedIn roles", "today's job matches", "which of these should I apply to", "update my application tracker from my email", "did I get any rejections", "build a daily job digest". Prefer this over ad-hoc searching whenever a résumé + real postings are involved.
One-time setup
- Install:
pip install -r requirements.txt(addpython-jobspyonly if
using the free JobSpy source; on Python 3.14 use pip install --only-binary=:all:).
- Secrets in
.env(copy.env.example; git-ignored — never commit or paste):
APIFY_TOKEN (required for the LinkedIn source), optional ANTHROPIC_API_KEY (LLM re-scoring), NOTION_TOKEN for the unattended path.
- Résumé: put the user's CV at
data/cv.md(copydata/cv.example.md). This
is the matching source of truth — level, skills, target roles, and hard constraints (visa, years, degree).
- Search config:
apify_input.json(copyapify_input.example.json) — the
Apify actor input: keyword[], locations[], publishedAt (r86400 = 24h), maxItems. Set JOBSCREENER_APIFY_ACTOR / JOBSCREENER_APIFY_TASK in .env.
- Candidate constraints: tune
CandidateProfileinsrc/jobscreener/config.py
(years_experience, max_years_experience, needs_visa_sponsorship, highest_degree, target_roles, exclude_title_keywords, …).
- Notion: connect the Notion MCP and note the target database's
data_source_id. Use one master table (see step 4 of the daily cycle).
Daily cycle
Run from the project directory. (If python is a broken Windows Store stub, use py or the full interpreter path.)
- Scrape the recent window (Apify actor; ~$0.1/run):
python -m jobscreener run --source apify --apify-input apify_input.json → writes output/apify_raw.json.
- Screen + dump full JDs. Apply the pipeline (knockout gate + score) to the
raw file and export the passing candidates with full descriptions so you can read them. The heuristic rank is a first pass, not the final word.
- Read the full JDs and curate by true fit. The knockout gate already drops
ineligible / over-experienced / wrong-degree roles; your job is judgement: down-rank roles that only keyword-match (e.g. trading/HFT, AI-research engineering, pure SWE) but don't fit the candidate, and flag caveats the gate can't see. Quality over quantity — a short honest list beats a padded one.
- Append to the ONE master Notion table (via the Notion MCP). Do not
create a new table each day. Columns: Name, Company, Location, Fit (Strong/Good/Moderate), Notes (why / caveats), Seniority, Status (new matches = To Apply), URL, Date, Source (LinkedIn). Prefix Name with a priority number. UPSERT rule — never insert before searching. Query the tracker for BOTH a distinctive company token AND a title fragment; use the shortest distinctive token so name variants still match ("Agricole" not "Crédit Agricole CIB HK" — accents, suffixes, and HK/SG tags have all defeated exact matching before). If any plausible match exists, UPDATE that row (or skip) — do not add a second.
- Report the ranked shortlist and which to apply to first.
Use --exclude-seen so the same job isn't surfaced on consecutive days.
Knockout gate (the core idea)
Standard job-matching separates hard non-negotiables (disqualify instantly) from soft weighted signals (rank). Implemented in filters.py:
- Eligibility — citizenship-only / no-sponsorship / clearance phrases → out.
- Experience — the JD's minimum required years >
max_years_experience→ out.
("Minimum 3 years" means ≥3; a new grad doesn't meet it.)
- Degree — PhD / postdoc required and the candidate is below → out.
Soft score (0–100) blends skill match (TF-IDF + skill overlap), title relevance, domain fit, seniority alignment, and location. Full rationale: METHODOLOGY.md.
Application tracking from email
When asked to "read email / update statuses": search Gmail (Gmail MCP) for job-application confirmations, rejections, and interview invites, then update the matching rows' Status in the same master table (Applied / Rejected / Interview / Offer / Started), or add rows for applications not yet tracked (Source = Email). One table for both matching and tracking. Backfills follow the same UPSERT rule as step 4: before adding a row from an email, search the tracker by a short company token + title fragment — the daily cycle may already have added the same role under a slightly different company name (this produced real duplicates on 2026-07-08: CACIB, Milliman). Match found → update its Status/Notes; only add when nothing plausibly matches.
References (read as needed)
RUNBOOK.md— full operating manual, gotchas, module map, how to extend.METHODOLOGY.md— how the funnel and scoring decide (the research).README.md— install, sources (Apify / JobSpy / sample), CLI flags.src/jobscreener/— the package; tests:python -m unittest discover -s tests -t ..
Notes
- No in-house LinkedIn scraper (ToS). Sources are pluggable behind
JobSource
in sources.py: Apify (managed, recommended), JobSpy (free), sample/CSV/JSON.
- The Notion/Gmail MCP connectors are verified to work in headless scheduled
runs (2026-07-03); the cycle runs as the nightly daily-job-feedback-sync scheduled task AND on command. If a headless run can't reach a connector, report-and-stop rather than fail silently.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: chenxi-bot21
- Source: chenxi-bot21/ai-job-search-pipeline
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.