Job Board Search
SkillSearchSearch a named company's careers board, or resolve a natural-language query into a company list and search each. Extract job descriptions, score pool-anchored and channel-conditional fit against the user's profile, run a contrarian gate before tiers are finalized, and create role folders with assessments. Updates the job search tracker.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Job Board Search skill
What this skill tells your AI
The instructions your AI receives, as published by vonterraproject501c3/slushpile in skills/job-board-search/SKILL.md and read by ahel’s review.
Search a careers board, extract the postings, and score each one against the realistic applicant pool rather than against the posting's own keywords.
Two ways in. Name a company and this skill searches that company. Describe the work instead — a function, a place, a market — and Phase 0 resolves the description into a list of companies and searches each one. Everything after Phase 0 is identical either way, because the second mode's only job is producing the company list the first mode is handed directly.
Announce at start. Company mode: "Searching $COMPANY for roles matching the profile. Focus: $KEYWORDS. Scoring: pool-anchored, channel-conditional, contrarian-gated." Query mode: the same sentence with the resolved company count in place of $COMPANY, announced only after Phase 0e, so the user is never told a number that a confirmation step might still change.
Every templates/... path in this file is relative to the plugin, not to the workspace. The working directory is the user's job-search directory and does not contain them, so a bare templates/role_analysis.md resolves to nothing. Resolve them against the directory this skill file was itself loaded from — that works on every harness, where a harness-specific plugin-root variable does not.
Arguments: one required argument, read as either a company or a query. See Phase 0a for how to tell them apart and what to do when it is genuinely unclear.
- Company mode —
$1is a company name,$2+are role keywords. Keywords default totargeting.functionsinpreferences.yaml. - Query mode — the whole argument describes the work, the place, or the market. Constraints in the query are read on top of
preferences.yaml, never instead of it.
Examples:
/slushpile:job-board-search Anthropic applied AI
/slushpile:job-board-search Rivian manufacturing NPI
/slushpile:job-board-search Stripe
/slushpile:job-board-search applied AI roles within 50 miles of Martinsville, VA that fit my profile
/slushpile:job-board-search remote staff platform engineering at Series B infra companies
/slushpile:job-board-search who is hiring manufacturing engineers near me
Scoring Philosophy
Three properties, and skipping any one of them collapses the scoring back into keyword matching:
Pool-anchored. Fit is where the candidate sits in the realistic applicant pool for this specific role. Not their match against the job description. A 75% keyword match against a pool whose 75th-percentile applicant matches at 90% is a weak application, and grading it against the posting says the opposite.
Channel-conditional. Cold submission and warm referral are different gates with different pass rates. A role that is marginal cold and strong with a referral gets both numbers, not their average.
Calibration-aware. The user's own application history sets the priors. A pipeline blind to its own track record produces optimism rather than signal, indefinitely, because nothing ever contradicts it.
The contrarian pass is a gate, not an appendix. It runs before tier tables are written and its findings change them.
Prerequisites
Read in full before assessing anything. Skipping these produces assessments that are confidently wrong about relocation, blockers, and domain overlap — the three things a user notices immediately and then stops trusting the tool over.
profile.md— complete background. The primary reference for every fit judgment.preferences.yaml— constraints, compensation method, work authorization, targeting, claimed differentiators, application posture. Every hard kill in Phase 3j comes from this file. Nothing here is hardcoded.job_search.md— the tracker. Referrals, cooldowns, and the calibration table.companies.md— whether this company has already been searched, and whether it is excluded. In query mode this file is load-bearing rather than advisory: it is the only thing standing between a market query and a list that re-searches everything the user already looked at last month.
If preferences.yaml does not exist, stop and run /slushpile:onboard.
Phase 0: Resolve the Target Set
Skip this phase in company mode. It exists for a query that describes the work rather than the employer, and its only output is a list of companies to hand to Phase 1. Nothing downstream knows which mode it was reached from, deliberately: a query mode that changed how roles are scored would produce assessments not comparable against a company-mode run's, and the calibration table cannot tell the two apart.
0a. Decide which mode you are in
A bare company name, with or without trailing role keywords, is company mode. An argument describing roles, a place, a market, or a hiring situation is query mode.
When the argument reads both ways, ask. Some words are both a company and a category. Guessing wrong does not fail loudly — it produces a complete, expensive, internally consistent search of the wrong target set, and the user finds out when they read a report about companies they never asked about.
State the mode you chose and why, in one line, before anything else. A mode decision that is never stated is one the user cannot correct until the run is over.
0b. Extract the constraints
Read out of the query: function and seniority, falling back to targeting.functions and targeting.levels; geography, a named place with or without a radius, a region, or remote, resolving anything relative like "near me" against identity.location; and anything else stated — industry, company stage, size, ownership.
Query constraints are additional to preferences.yaml, never a replacement for it. A user asking for roles near one city has not withdrawn their compensation floor, their excluded regions, or their excluded companies. Reading the query as an override is the failure mode of this phase, and what it produces looks responsive: a tidy on-topic list containing roles the user already ruled out and now has to rule out again by hand.
Geography is the one exception, and only when the query names a place relocation would otherwise exclude. Asking about a specific city is evidence about that city postdating the preferences file. Surface the conflict and ask; do not silently apply either one.
Write the resolved constraint set out before searching. A radius the user meant loosely and you applied strictly is the difference between thirty candidates and three, and nothing in the output shows which happened.
0c. Generate candidate companies
Search for employers matching the constraint set, varying the search the way 1b does — one query returns one query's blind spot, and here that costs a whole company rather than one posting.
- Employers by industry and place:
{industry} companies in {metro} - Aggregator boards, read for employer names rather than for postings
targeting.target_companiesinpreferences.yaml, filtered to the query- The Watchlist table in
companies.md, filtered the same way
Prefer employers over postings here. A posting on an aggregator is often stale, duplicated, or a staffing agency reposting someone else's requisition, and Phase 1 reads the company's own board regardless. The aggregator is worth the name it reveals, not the listing it shows.
Aim for fifteen to thirty candidates before filtering. Below that the filter has nothing to work with, and a thin candidate list is indistinguishable in the output from a thorough search of a thin market.
0d. Filter, and count what each step removed
- In
targeting.excluded_companies, or in the Excluded table ofcompanies.md. - Searched inside its recheck window per
companies.md, unless the user asked to refresh. - Ruled out by
relocation— checkwillingandtarget_regionsbefore dropping anything on location, and honorremote_preferencebefore dropping a company for being in the wrong place at all. - No plausible opening for the target functions. Weak filter, applied last: this is a guess from outside the company's board, and Phase 1 is what finds out.
Report what each step removed, not only the total that survived. "Twenty-two candidates: four searched recently, two excluded, three out of region, thirteen to search" is checkable. "Searching thirteen companies" is not, and it hides the case where one bad filter ate the list.
0e. Confirm before spending the budget
Show the user the final list and the counts, and get a yes before Phase 1. Phases 1 through 5 run per company and are where this skill spends nearly everything — browser navigation, full posting capture, a per-role assessment, a share of the contrarian batch. A target list wrong in a way only the user can see is worth exactly one question, asked before the spend.
Cap the run at ten companies unless the user says otherwise, and name the ones you cut rather than reporting a truncated list as the result. A silent cap reads as "this is the market" when it is "this is the first ten."
Then run Phases 1 through 4 per company, and Phase 5 once across all of them, per 5c.
Phase 1: Discovery
1a. Find the careers URL
Careers sites fall into two categories, and getting this wrong wastes the most time of any error in this skill.
| Type | Behavior | Tool |
|---|---|---|
| Server-rendered | Full HTML in the response | WebFetch |
| JavaScript SPA | Response is a JS bundle, no listings | Playwright or the browser tools |
Most large-company career sites are SPAs. If WebFetch returns a page with no job titles in it, that is the signal — switch to browser automation rather than retrying.
Common platforms and their patterns: Greenhouse (boards.greenhouse.io/{company}), Lever (jobs.lever.co/{company}), Ashby (jobs.ashbyhq.com/{company}), Workday ({company}.wd1.myworkdayjobs.com), SmartRecruiters, and in-house boards at the largest companies. Greenhouse and Lever are usually server-rendered and cheap to search. Workday is always an SPA.
Find the board first by searching the web for {company} careers rather than guessing a URL.
1b. Run several queries
One query misses roles. Run three to five, varying:
- The function title and its synonyms — many roles that fit are titled something else entirely
- Seniority terms from
targeting.levels - Domain keywords from
targeting.industries - Location filters, and one unfiltered pass
1c. Extract the listing data
For each result: title, requisition ID, location, team or department, posted date, and URL.
1d. Triage on titles
Do not fetch every posting. From the first twenty to forty results, keep anything where the title plausibly maps to the target functions, and read the responsibilities before concluding a title is wrong-function. Adjacent titles are frequently the best fits — the posting's title reflects the company's internal taxonomy, not the work.
Discard on title alone only for unambiguous mismatches: a different profession entirely, or a seniority level several steps away.
Phase 2: Extract the Postings
2a. Navigate to each posting
Use browser automation for SPA boards.
Never run browser navigation in parallel agents. The browser is a shared singleton and concurrent agents clobber each other's pages. Sequential navigation, always.
The singleton is shared across sessions, not just across this run's agents. Another session driving the same browser will move tabs out from under this one, and the failure is silent: the read succeeds, it just returns a page that is not the one navigated to. Before parsing any posting, confirm the page is the URL that was requested — check the title or the requisition ID against what the listing said. On a mismatch, re-navigate rather than parsing what is there. A posting captured from the wrong tab produces a role folder for a job that was never assessed, and nothing downstream can detect it.
2b. Capture verbatim
Write each posting to job_description.md using templates/job_description.md.
Capture verbatim. Do not summarize. Three agents parse this text directly — the ATS simulator counts keyword occurrences, the requirements analyst checks qualifications one at a time, and the pool analyst infers the pool from how the posting is written. All three degrade against a summary, and quietly.
Capture the compensation band exactly as posted. Note the application form's structure if visible: years-of-experience dropdowns, screening questions, whether a cover letter field exists. A hard "8+ years of X" dropdown is a completely different obstacle from the same sentence in prose, and only the pool analyst's filter-exposure estimate distinguishes them.
Phase 3: Fit Assessment
Skipping 3a, 3d, 3h, 3i, or 3j reverts the scoring to absolute grading. Those five are the method.
3a. Calibration ingest — required
Read the Calibration section of job_search.md and the calibration_priors block in preferences.yaml before scoring anything.
Capture: this company's historical conversion rate, which verdict tiers are over- or under-converting, and any sharp mismatch — an INTERVIEW verdict that auto-rejected, or a pass that would have converted.
calibration_priors holds the same findings in the form the agents consume: observed per-channel rates with a sample size, free-text drift notes, and per-company overrides. Where it has a by_company entry for this company, that rate replaces the company-type prior in 3d. Where drift_notes describes a pattern this role fits, apply it and say you did.
How to use it:
- Historical conversion at this company below 5% → treat any Tier 1 score with suspicion. The pool likely outclasses the candidate regardless of the keyword match.
- No history at this company → use the closest peer in the table. Say which peer, and that the prior is borrowed.
- No history at all, first search → say so. An uncalibrated estimate labelled as uncalibrated is useful. One presented as calibrated is not.
3b. Role reality research — required
The posting describes the role the company wants to fill. It does not always describe what the team does.
Run two or three targeted searches: "{Company} {Team}" what they do, other postings from the same team (they leak org structure), and any engineering blog or talk from that team.
Answer four questions:
- Who are the customers? External clients, internal teams, or both?
- Where in the value chain? Research, product development, production, or post-sale delivery?
- What is the operating environment? Offices, labs, data centers, production floors, customer sites?
- What creates the pressure? Contract deadlines, launch dates, compliance, sales cycles?
This changes scores materially. "Data center operations" can mean customer-facing production management or internal research lab infrastructure. Those are different jobs with different pools, and the posting frequently does not distinguish them.
Budget five to ten minutes. If the search reveals nothing beyond the posting, write "no additional context found" and move on.
3c. Week in the life — required
Write three to five sentences describing what this person actually does in a normal week. Cadence, stakeholders, deliverables, and what breaks when they do the job badly.
This has teeth. If the week describes operating rhythms the candidate has never lived, that divergence reduces the pool position in 3h. It is an input to the score, not a section of the report.
If you cannot write it, you do not understand the role well enough to score it, and a score built on a misunderstanding is worse than no score because it looks the same as a good one.
3d. Pool estimation — required
This replaces keyword match as the primary signal.
Estimate, inline — a full pool-analyst agent dispatch is reserved for /slushpile:adversarial-review on finished materials:
- Volume. Roughly how many applications per week?
- Median (p50). Title history, years, recent employers, signature artifacts, credentials.
- Strong (p75). Same dimensions, one tier up.
- Rare-strong (p90). The candidates a hiring manager would be embarrassed to pass on.
Priors by company type. Starting points. Adjust for the specific role and say what you adjusted.
| Company type | Volume/wk | Pool character |
|---|---|---|
| Frontier AI lab | 80-200 | Deep, and self-selected. p50 is already ex-big-tech. Public technical work is common rather than rare. |
| Hyperscaler | 100-300 | Very deep, heavily credentialed. Pedigree filters are real. p75 has shipped at comparable scale. |
| Public mid-cap tech | 40-150 | Moderate depth. Engineering background weighs more than at hyperscalers. |
| Growth-stage startup | 20-80 | Shallower at p90. Breadth and shipping speed beat specialization. Referrals dominate. |
| Defense and aerospace | 40-120 | Clearance and program experience gate before anything else. Different pool entirely. |
| Regulated industry, non-tech | 15-60 | Shallowest pools. Domain and compliance experience outrank general technical strength. |
| Government and public sector | 20-100 | Credential-driven. Delivery history under public contract is the differentiator. |
Then locate the candidate. For each claimed differentiator in preferences.yaml:
- Is it rare in this pool, or median?
- What percentile does it place them in, relative to this pool?
- What would the p75 applicant claim instead?
Output an estimated pool position percentile.
The calibration that matters: a differentiator can be rare in absolute terms and median in a pool of people who self-selected into applying for this exact role. Ten years in a specialized manufacturing domain is rare on Earth, median for a hyperscaler manufacturing role where the pool is ex-contract-manufacturer at consumer scale, and p75+ for a defense role where that exact experience is the gate. Same fact, three answers, and only the pool question distinguishes them.
3e. Risk factors — not blockers
These reduce the score. They do not auto-pass. Whether any of them is a hard blocker is set in preferences.yaml, and by default none of them are.
- Clearance requirement. Note as a gap for the cover letter. Roughly 5-10 percentile points. Auto-pass only if
clearance.hard_blockeris true. There is no gap at all whenclearance.heldalready covers what the posting asks for, and a clearance inclearance.eligible_foris a sponsorship timeline rather than a missing credential — score it at less than full weight and say so in the letter. A held clearance the posting requires is not a neutral fact either: in a pool where most applicants lack one it is a differentiator, and 3d should be told about it. - Degree requirement, even without "or equivalent". Same treatment. Compare the posting's requirement against
education.highest_degree, and score the gap only where there actually is one — a posting asking for a bachelor's is not a risk factor for someone holding one, and scoring it as though it were is how a role drifts a tier for no reason. Auto-pass only ifeducation.degree_requirement_is_hard_blockeris true. - Years-in-domain minimums. Assess whether the pattern transfers. NOT MET only when no meaningful analog exists.
- Location. Check against
relocation. Only a blocker if it falls inexcluded_regionsor the user is not willing to relocate.
The defaults here are deliberate: postings routinely list a clearance or a degree that the hiring manager does not enforce, and treating them as blockers silently removes roles the user would have gotten.
3f. Minimum qualifications
Rate each: MET (direct experience clearly demonstrates it), PARTIAL (related experience covering the pattern but not the domain), NOT MET (nothing to claim).
Count partials carefully. One is usually workable. Two or more raises rejection risk sharply at the screen.
Distinguish three kinds of gap, because they are not equally bridgeable:
- Delivery pattern — what they ran, coordinated, or shipped. Usually transfers across domains.
- Domain vocabulary — specific tools, standards, industry terms. Learnable, and weighs least.
- Credential — exact title, years in exact role, specific certification. Hardest to bridge and the most likely to gate.
"Experience managing ML programs" is PARTIAL for someone who ran AI pipeline delivery under another title.
3g. Preferred qualifications
Same rating. These shape the narrative angle. They do not gate.
Check education.certifications against the ones the posting names before rating any of them NOT MET. A certification the candidate holds and the posting asks for by name is the cheapest MET in this whole assessment, and it is the one most often missed, because it lives in preferences.yaml rather than in the narrative parts of profile.md the assessment spends its attention on.
3h. Pool-anchored fit score
| Pool position | Tier | Meaning |
|---|---|---|
| p75+ | Tier 1 | Above the strong-applicant bar for this specific role |
| p55-p74 | Tier 2 | Competitive, not differentiated. Needs a channel advantage. |
| p35-p54 | Tier 3 | Below median. Pursue only through a strong channel. |
| below p35 | Pass | The pool outclasses the candidate. Cold submission is a wasted slot. |
Inputs, in priority order:
- Minimum qualification match. Two or more NOT MET on critical quals drops the position one to two tiers regardless of everything else.
- Pool comparison. Does the strongest claim actually rank here, or is it median?
- Week-in-the-life divergence. Operating rhythms never lived reduce the position.
- Risk factors. Roughly 5-10 percentile points each.
- Calibration prior. Company conversion below 5% caps the position at p55 absent a specific reason this role differs.
Record the pool percentile as the canonical fit number. If you also want the keyword match, put it in a separate field. Collapsing them is the failure this whole rubric exists to prevent.
When passing, name the single primary blocker. If you cannot name one that independently justifies a pass, the role deserves a score instead.
3i. Channel EV matrix — required for every Tier 1-3 role
| Channel | Tier | Gate | Estimated screen pass |
|---|---|---|---|
| Cold submission | none | 5-15%, varies with pool position | |
| Warm referral | a referrer must exist | 25-50%, pool-dependent | |
| Cold outreach to a named employee | an identifiable target | 5-15% | |
| Inbound from public work | an existing artifact, seeded | 20-40% if it lands | |
| Recruiter inbound | out of the candidate's control | n/a |
Use the user's own history from job_search.md where it exists. An empirical warm-referral rate at this company beats any prior.
The role's tier is the highest tier across available channels. Record which channel unlocks it and what gate must be cleared.
If no referrer currently exists, the warm-referral row is informational only. It does not unlock Tier 1. Inflating the tier by leaning on an unavailable channel is the most common way this matrix gets gamed, and it is self-inflicted.
Name the gate as an action, not as a condition. Where the warm-referral row would raise the tier and the Referrals table in job_search.md holds no strong or moderate contact at this company, say so and name /slushpile:outreach {role folder} as the way to clear it. Read the strength column rather than the row count: that table also carries weak ties and cold targets, and neither is a referrer. A matrix that reports an unavailable channel and stops leaves the user with a number and no move, which is how a search ends with every role routed to the channel that converts worst.
3j. Kill criteria — required, auto-pass at scan time
Every one of these comes from preferences.yaml. Nothing is hardcoded.
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 21
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
job-board-search- Source
- github.com/vonterraproject501c3/slushpile