Job Board Scout
Discovery, not application. The script reads public job endpoints, applies filters
you declare in a config file, deduplicates against a state file, and writes a
markdown review queue. It has no credential, sends nothing, and cannot apply to a
job or message anyone.
Prerequisites
Python 3.9 or later, standard library only. No API key, no account, no secret.
Outbound HTTPS to the endpoints you list in the config.
Before the first run: write the config
There is no default search. filters.title_terms is required and the script exits
2 if it is missing, because a scout that guesses what you want is a scout that
wastes your review time. Start from examples/recruiting.json
and replace the lists with the user's own.
Ask the user for, and write into the config:
- Titles (
filters.title_terms). Substrings matched against the posting title,
casefolded. Prefer stems (talent sourc) over full words.
- Disqualifiers (
filters.exclude_terms). Matched across title, description,
tags and employment type. This is where hourly work, unpaid work, and
near-miss titles go.
- Remote requirement (
filters.require_remote, filters.non_remote_terms).
- Geography (
filters.geography). This filter reads the location restriction
the posting states. It is a filter over posting text, not a claim about anyone's
right to work anywhere. allow_unstated: false fails closed: a posting that says
nothing about location is dropped rather than assumed to be open.
- Sources. Four feed adapters (
arbeitnow, himalayas, jobicy, remotive)
plus a watchlist of public ATS boards. See
references/sources.md for how to find a board name and
how to confirm it before adding it.
- Scoring (
scoring). Ordering only, never filtering. Title tiers are
evaluated in config order and the first match wins; signal terms all accumulate.
Running it
python scripts/job_board_scout.py --config myconfig.json --state .scout/state.json --out out
--dry-run runs the whole pipeline without recording anything as seen, so you can
tune filters against real data and still get the same roles on the real run.
--json prints the run summary for a wrapper to read. --max-new N overrides the
config for one run.
Schedule it with cron or any scheduler. Nothing in the script needs a daemon.
Reading the result
out/queue.md carries the roles, then three sections that matter more than the
roles when you are tuning:
- Source status —
ok (N records) or failed (ExceptionName) per source. A
source that has been failing for a week is a dead board name, not a quiet market.
- Filter counts — how many postings each rejection reason removed. If
title does not match filters.title_terms is eating everything, your terms are
too narrow.
- Coverage warnings — a source returned records but zero title fits. Usually
the wrong board or the wrong vocabulary for that market.
Boundaries
- Read-only. Public GET requests to the configured endpoints and nothing else.
- If every configured source fails, the run exits 1 rather than writing an empty
queue. An empty queue must mean "nothing new", never "the network was down".
- Deduplication is by company + title + location, not by URL, so one posting
syndicated to three feeds is queued once. State is capped at 5000 keys.
- Never present a score as a judgement of the role. It is a review order, and
every point in it is printed with the rule that produced it.
- Do not add a board to the watchlist without fetching its endpoint once first.
1---2name: job-board-scout3description: Build a deduplicated, ranked queue of new job postings from public job feeds and public Ashby, Greenhouse and Lever boards, filtered by rules you write down. Use when asked to watch job boards, track new roles for a search, build a job review queue, or find remote postings matching specific titles and locations.4---56# Job Board Scout78Discovery, not application. The script reads public job endpoints, applies filters9you declare in a config file, deduplicates against a state file, and writes a10markdown review queue. It has no credential, sends nothing, and cannot apply to a11job or message anyone.1213## Prerequisites1415Python 3.9 or later, standard library only. No API key, no account, no secret.16Outbound HTTPS to the endpoints you list in the config.1718## Before the first run: write the config1920There is no default search. `filters.title_terms` is required and the script exits212 if it is missing, because a scout that guesses what you want is a scout that22wastes your review time. Start from [`examples/recruiting.json`](examples/recruiting.json)23and replace the lists with the user's own.2425Ask the user for, and write into the config:26271. **Titles** (`filters.title_terms`). Substrings matched against the posting title,28 casefolded. Prefer stems (`talent sourc`) over full words.292. **Disqualifiers** (`filters.exclude_terms`). Matched across title, description,30 tags and employment type. This is where hourly work, unpaid work, and31 near-miss titles go.323. **Remote requirement** (`filters.require_remote`, `filters.non_remote_terms`).334. **Geography** (`filters.geography`). This filter reads the location restriction34 the posting states. It is a filter over posting text, not a claim about anyone's35 right to work anywhere. `allow_unstated: false` fails closed: a posting that says36 nothing about location is dropped rather than assumed to be open.375. **Sources**. Four feed adapters (`arbeitnow`, `himalayas`, `jobicy`, `remotive`)38 plus a watchlist of public ATS boards. See39 [references/sources.md](references/sources.md) for how to find a board name and40 how to confirm it before adding it.416. **Scoring** (`scoring`). Ordering only, never filtering. Title tiers are42 evaluated in config order and the first match wins; signal terms all accumulate.4344## Running it4546```47python scripts/job_board_scout.py --config myconfig.json --state .scout/state.json --out out48```4950`--dry-run` runs the whole pipeline without recording anything as seen, so you can51tune filters against real data and still get the same roles on the real run.52`--json` prints the run summary for a wrapper to read. `--max-new N` overrides the53config for one run.5455Schedule it with cron or any scheduler. Nothing in the script needs a daemon.5657## Reading the result5859`out/queue.md` carries the roles, then three sections that matter more than the60roles when you are tuning:6162- **Source status** — `ok (N records)` or `failed (ExceptionName)` per source. A63 source that has been failing for a week is a dead board name, not a quiet market.64- **Filter counts** — how many postings each rejection reason removed. If65 `title does not match filters.title_terms` is eating everything, your terms are66 too narrow.67- **Coverage warnings** — a source returned records but zero title fits. Usually68 the wrong board or the wrong vocabulary for that market.6970## Boundaries7172- Read-only. Public GET requests to the configured endpoints and nothing else.73- If every configured source fails, the run exits 1 rather than writing an empty74 queue. An empty queue must mean "nothing new", never "the network was down".75- Deduplication is by company + title + location, not by URL, so one posting76 syndicated to three feeds is queued once. State is capped at 5000 keys.77- Never present a score as a judgement of the role. It is a review order, and78 every point in it is printed with the rule that produced it.79- Do not add a board to the watchlist without fetching its endpoint once first.