Install
$ agentstack add skill-aaddrick-slushpile-job-board-search ✓ 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 No
- ✓ 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.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Job Board Search
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, targeting, claimed differentiators. 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, re
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: aaddrick
- Source: aaddrick/slushpile
- License: MIT
- Homepage: https://github.com/aaddrick/slushpile#readme
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.