Finding Signal Before Spending Reasoning

Finding a job is cheap; deciding whether a job deserves serious attention is not. This chapter focuses on career-ops' discovery funnel: how it moves from public listings to a shortlist using inexpensive, inspectable signals before asking a model or a candidate to do deeper work. The goal is not to make an early filter sound intelligent. It is to make its limited judgment clear, preserve opportunities when a source fails, and reserve expensive reasoning for roles that have earned it.

The escalation order is the cost model

The scanner starts with sources that can be read without an LLM. Provider modules translate public ATS APIs, RSS or JSON feeds, and server-rendered job pages into a common job shape: usually a title, URL, company, and location, with dates, compensation, or description text when a source provides them. A local parser can be even cheaper because it runs against a known careers page with no model interpretation. The default scanner is therefore closer to a data-ingestion process than to an AI search assistant.

When a configured local parser completes successfully, career-ops remembers that company as covered for the current scan. It does not immediately scrape the same careers page with a browser or fetch a redundant ATS endpoint. That rule looks like an implementation detail until the search grows. Repeating the same extraction consumes time, can produce inconsistent versions of a listing, and creates more opportunities for a browser snapshot or model to misread a page. Avoiding redundant work is part of the candidate's attention budget.

If a local parser is unavailable or fails, the workflow can escalate. Direct browser scanning is useful for a live careers page, especially when a site is a client-rendered application whose listings do not appear in static HTML. Structured ATS APIs and feeds can return a fuller set of postings than a first-screen browser snapshot, and often include metadata such as a posting date or compensation. Broad web search is useful for discovering companies that are not yet in the configured list, but its results are treated as stale until the real posting is checked.

The order is not a promise that every run uses every level. A normal zero-token scan may be sufficient for the configured public sources. Browser navigation and web search belong to fallback or agent-assisted paths. The design principle is stable: use the cheapest source that can answer the current question, and do not pay for a richer answer before a simpler answer has made it relevant.

Once a listing has been normalized, career-ops applies filters that are intentionally narrower than a full evaluation. A title filter can require a positive term, reject a negative term, and give seniority terms priority. Location rules can allow, block, or pass an empty location without pretending that missing provider metadata is a geographic judgment. Optional age, compensation, country-eligibility, visa, and content filters can narrow the queue further. These rules answer questions such as "does this look like one of my target role families in a workable place?" They do not answer "would I accept this job?"

That limited scope is a safety feature, not an unfinished version of matching. A title can resemble a target role while the responsibilities reveal a different function. A location can say "remote" while the description later requires office attendance. A compensation field can contain a range with variable components that a simple filter cannot interpret. If the scanner tried to settle all of those questions from sparse metadata, it would create false confidence precisely where the candidate needs a careful reading. It is better for the early stage to admit that it sees a title and a location than to make a polished but unsupported fit claim.

The same principle makes missing data asymmetric. An empty location passes rather than being treated as a rejection, because the provider may simply not expose it. An absent posting date does not prove that the role is recent or old. A provider that lacks a description cannot support a content judgment. These choices can lower precision and leave more work for the next stage, but they protect recall and keep the meaning of each filter stable. The candidate can decide later whether the missing field is important enough to verify.

sequenceDiagram participant S as Scanner participant P as Public provider participant F as Filters participant H as History pipeline tracker participant L as Liveness classifier participant W as Warning signals participant Q as Shortlist S->>P: Request public listings P-->>S: Return normalized job data S->>F: Apply title location and age rules F-->>S: Keep or filter candidate S->>H: Check URL and company role history H-->>S: New or already seen S->>L: Verify liveness when required L-->>S: Active expired or uncertain S->>W: Add trust and cross listing warnings W-->>S: Flag only, do not decide S->>Q: Add surviving role for attention

The sequence shows why the shortlist is not yet a recommendation. It is the result of several cheap gates and one or more evidence warnings, not a conclusion about fit.

Deduplication protects attention and identity

A discovery tool that finds the same role repeatedly can create the illusion of a large market while consuming the same candidate's time over and over. career-ops checks three kinds of existing state: URLs already seen in scan history, URLs already present in the pending or processed pipeline, and roles already represented in the application tracker. URL comparison is normalized so cosmetic tracking parameters, fragments, trailing slashes, and irrelevant casing do not make one posting look like several.

The system keeps the provider's original URL for display, while using a normalized form for comparison. That distinction matters. Rewriting the visible URL could make a link less useful or conceal what the provider actually returned. Keeping the original and normalizing only the comparison key preserves both clickability and deduplication.

Company and role identity add another layer. A job can appear under more than one ATS path, and the same company may post several roles with similar titles. The scanner's history and tracker give it context that a raw URL does not. At the same time, aggressive identity matching can hide legitimate variants. Location-specific openings, renamed requisitions, or roles at related entities can be different opportunities. The repository therefore treats location-aware deduplication and company aliases as explicit configuration or normalization choices rather than universal truths.

The cost of a bad identity key runs in both directions. If the key is too loose, a role returns repeatedly and the candidate wastes attention. If it is too broad, a valid city or team variant disappears because another listing claimed the same company and title first. The default behavior deliberately collapses many city variants, while an explicit location option can preserve distinctions for a candidate whose eligibility depends on place. That is a useful pattern beyond this scanner: a default should be conservative for ordinary use, while a consequential constraint should have a visible way to make identity more precise.

This is where the architecture's canonical records become operational. Scan history is not merely a log for later analytics. It is one of the inputs that decides whether a new result reaches the candidate at all. The pipeline is not merely an inbox. It is a durable handoff between discovery and evaluation. The tracker is not merely a résumé of past applications. It can prevent a role from being reintroduced as if it were new. A mistaken record can therefore suppress attention, which is why the scanner favors conservative parsing and keeps statuses visible.

Liveness is a separate question from relevance

A role can match a title filter and still be gone. Search indexes can retain closed postings, a careers page can redirect a dead link to a general listing page, and an anti-bot service can return a short challenge page that tells the scanner almost nothing. career-ops uses a liveness gate before a stale posting consumes a full evaluation.

The gate distinguishes hard closure evidence from inability to see the posting. A 404 or 410, an explicit "no longer accepting applications" message, a hard expired banner, a redirect that clearly lands on an expired URL, or a page that contains only generic listing content can support an expired result. A visible job description and Apply control support an active result. But a Cloudflare challenge, an hCaptcha wall, a 403, a 429, a transient server error, or a redirect that loses the original job identifier produces uncertainty rather than closure. Content that is present but lacks a recognized Apply control also remains uncertain.

This conservative direction has a reason. An expired result may be written to scan history as skipped and then suppress future sightings of that URL. If a temporary rate limit is misread as a dead posting, a real opportunity can become invisible indefinitely. Treating access failure as uncertainty may leave the candidate with one more role to verify, but it avoids turning a transport problem into a career decision.

The distinction can be expressed as three different statements:

Result What the system knows Appropriate next step
Active The page exposes enough role evidence and an application path Continue to evaluation or the next gate
Expired The source provides direct closure evidence Remove it from active work
Uncertain The system cannot safely distinguish closure from access failure Verify later; do not silently discard

The classifier also guards against a misleading redirect. A dead permalink can land on a real search page that contains Apply controls, but those controls belong to other jobs. When a requested URL carried a recognizable job identifier and the final URL loses it, the Apply buttons are not enough to establish that the original posting is active. Again, the system chooses uncertainty over a confident but misidentified result.

None of these states says whether the candidate should want the role. Liveness protects the relevance of the work that follows; it does not replace that work.

Warnings add context without pretending to know intent

career-ops adds a lightweight trust layer that flags suspicious conditions but does not drop listings. It can lower a trust score for an invalid or missing URL, a suspicious shortener domain, or a mismatch between a company name and a non-ATS hostname. An ATS-hosted URL is treated differently because the domain is expected to belong to the hiring platform rather than the employer. These are signals about the posting's presentation, not findings that an employer acted dishonestly.

The cross-listing check addresses a different risk. Recruitment agencies sometimes repost an employer's description with the employer name removed. The URL and company name change, so ordinary deduplication misses the pair. When an API supplies the description, career-ops computes a local 64-bit SimHash. If descriptions from different companies appear within a 90-day window and differ by no more than five of the 64 bits, the scanner can warn about a possible near-duplicate. It does not automatically remove either listing. The candidate reads both, determines whether one is a direct posting and the other an agency channel, and chooses one route if a double submission would create a problem.

A warning-only design is important for two reasons. First, the same text can legitimately appear in two roles that share a company template. Second, a low-trust signal often changes how much verification is sensible, not whether the opportunity exists. The system can say that a result deserves caution; it should not convert a heuristic into an accusation or a hard stop.

The same discipline applies to missing data. A provider that supplies no posting date does not thereby prove that the role is fresh or stale. A browser that sees only a partial first screen does not prove that a company has no other listings. A search result is not evidence that a link is still live until the posting itself is checked. The funnel is useful precisely when its silence and uncertainty remain visible.

The shortlist is a threshold for attention

At the end of discovery, a surviving role has earned a place in the candidate's attention queue. It may have passed a title and location filter, avoided known duplicates, and received an active or pending liveness result. It may also carry a trust or cross-listing warning. That is enough to justify asking a deeper question, not enough to answer it.

The next decision is therefore bounded: should this role receive full evaluation, should verification happen first, or should the candidate stop because a known constraint makes further work wasteful? Keeping that decision separate from the evaluator prevents the scanner from pretending to understand a candidate's fit and prevents a weak source signal from becoming a hidden rejection. The role is now a candidate for reasoning. It is not yet a recommendation to apply.

References