Job hunting is exhausting.
So we built an agent to handle it.
Opshire turns a raw daily feed of job listings into a short list of scored, explainable matches — then helps the candidate act on them with constrained resume tailoring, generated interview prep and salary intelligence. Built for mid–senior professionals in India, with the candidate holding the only decision that matters: whether to apply.
The whole job search, running itself
India’s hiring market produces thousands of new roles a day. The candidate’s loop is brutal and entirely manual: filter the noise, judge fit from a job description, rewrite a resume for each posting, guess the salary band, and assemble interview prep from scattered blog posts — usually after a full day of work. Doing that well doesn’t scale past a handful of applications a week.
Opshire is an always-on agent that owns everything up to the decision. It ingests and de-duplicates listings nightly, extracts normalised skills from each job description, embeds roles and profiles to find fit beyond keyword overlap, scores every candidate role across six weighted signals, and suppresses everything under threshold. What surfaces arrives with a human-readable reason. Everything generative — tailoring, prep, mocks, coaching — waits for explicit intent from the candidate and runs inside entitlement limits.
Why a better job board wouldn’t do
Every existing tool optimises for listing volume. The candidate’s actual problem is the opposite — too many options, no trustworthy ranking, and all the real work sitting after the click.
Keyword filters miss the fit
An SRE never sees the Platform Engineer role that suits them perfectly, because the title doesn’t contain their search term. Exact-match filtering fails precisely where a career change matters most.
Tailoring doesn’t scale by hand
Rewriting a resume per job description costs 30–45 minutes. Do it properly and you apply to four roles a week; skip it and the ATS filters you out before a human ever reads it.
Prep arrives after it’s needed
Candidates walk into interviews with no company context and no salary range, having pulled together generic advice from five browser tabs the night before.
Alerts train you to ignore them
Daily digests full of near-misses are worse than nothing. Once a candidate learns the emails aren’t worth opening, the good match arrives and goes unread.
Nothing closed the loop
Discovery, applications, interviews and negotiation lived in different places — so follow-ups were forgotten and no system knew an interview was two days away.
A score nobody believes is worthless
One “94% match” on a role paying 30% under the candidate’s floor poisons trust in every number that follows. Explainability isn’t polish here — it’s the product working at all.
Eight stages, routed by cost class
Stages never call each other. Each one reads rows in a given state, writes its output and advances a status column — so every stage is independently re-runnable, inspectable and resumable. Pick a stage to see its contract, where it spends money, and how it fails.
Three thousand listings in, nine roles out
One nightly run, per profile, unattended. Volumes below are representative of a single candidate’s scan — the shape is the point: aggressive reduction at every stage, and the last stage’s job is to say nothing at all when nothing qualifies.
Bar widths are scaled for legibility, not drawn to proportion — the counts beside them are the real reduction.
The scoring contract
Weights are versioned configuration, not code, and they sum to 100. Every surfaced match keeps its per-signal contributions, so the reason string is derived from the same numbers the gate used — it can’t contradict the score it explains.
| Signal | Weight | Computed from | Cost class |
|---|---|---|---|
| Role fit | 30 | Embedding similarity across title and JD summary | Embeddings |
| Skills overlap | 25 | Normalised extracted skills ∩ profile skills | Code + LLM |
| Experience fit | 15 | Years against the posted band, penalised both directions | Code |
| Salary alignment | 12 | Expectation against the posted or inferred band | Code |
| Location & mode | 10 | City plus remote / hybrid / onsite preference | Code |
| Company fit | 8 | Stage, size and sector signals against stated preference | Code |
| Hard blockers | veto | Salary floor, work authorisation, mode conflict, seniority gap | Code |
Three surfaces, none of them a firehose
Every surface below is threshold-gated. If a scan produces nothing above the bar, the candidate hears nothing — because the fastest way to make a good agent useless is to make its alerts skippable.
Dashboard
The full picture: surfaced matches with scores and reasons, the watchlist behind a click, the application tracker, and every preference control that feeds scoring.
- Score plus the two signals that earned it
- Blocker named explicitly when present
- Stale rows labelled, never shown as current
Email & push
New-match notifications that name the actual roles and deep-link straight to them. Sent only when the scan clears the threshold, so an empty night sends nothing at all.
- Real role titles in the subject, not “new matches”
- Deep links into the specific match
- Interview reminders driven by tracker stage
Assisted flows
WhatsApp-assisted journeys and in-product chat for candidates who’d rather message than navigate — carrying the same scores and the same reasons, never a different answer.
- Coaching grounded in the product’s own data
- Rate limited and capped per tier
- No company salary figures stated as fact
A tailored resume that is still, entirely, true
An unconstrained rewrite will happily promote a side project to an employer or add the exact skill the JD asked for. For a document a recruiter will cross-examine, that is the worst failure the product can produce — so tailoring is built as a constrained transform, not a generation task.
Facts are immutable inputs
Employers, titles, dates, degrees and skills are passed as fixed context. The model chooses emphasis, ordering and phrasing — it is never the source of content.
Output is validated against the source
The rewrite is checked back against the profile. New employers, dates, degrees or skills mean the tailoring is rejected and the untouched resume is used instead.
Gaps are named, not filled
Skills the JD wants that the candidate doesn’t have are surfaced as a gap to decide about — not quietly written into the document.
Explanations come from the numbers
Reason strings are templated over score components rather than generated beside them, so the sentence and the percentage can never disagree.
Autonomous up to the decision, never past it
Opshire is a decision-support agent, not an auto-applier. Everything before the click is automated; the click itself is deliberately, permanently manual.
🙋 The candidate applies
No blind submissions on anyone’s behalf. Tailoring runs on apply intent for one specific job description, and the application is sent by the person whose name is on it.
🎚️ Preferences are the control surface
Title, skills, salary floor, work mode and company preferences are the scoring inputs. Changing one is how a candidate re-tunes the agent — no support ticket required.
👁️ Suppression is visible
The watchlist exists so “we hid things from you” is inspectable. Anything scored between 60 and the threshold is one click away rather than silently discarded.
♻️ Edits invalidate stale scores
Raising a salary expectation marks that candidate’s scored rows stale. The next scan re-scores them and the dashboard labels anything not yet re-scored instead of showing an old number.
The hard part was making autonomy safe and affordable
Nothing here runs while somebody is watching. Every guardrail below exists because of a failure mode we hit, and each one is now a bound rather than a hope.
Blockers outside the blend
Hard constraints are evaluated separately from the weighted score so they can veto a high number instead of being averaged away inside it.
Entitlements before the API call
Per-intent generation has no natural ceiling — a motivated candidate can request fifty tailorings in an evening. Limits are enforced before the request, not in the UI.
Extraction cached by JD hash
The same posting is never parsed twice, and nightly re-scans reuse it. Matching for a free user is effectively a rounding error because almost nothing repeats an LLM call.
Async generation, progressive UI
A full prep pack takes long enough to die behind a gateway timeout, and it’s requested when the candidate is anxious. Sections stream in as they complete and each one is cached.
Recency is a first-class field
Listings are re-validated and expired; salary intel carries its vintage and degrades to a wider range as it ages rather than presenting one confident figure.
Every stage re-runnable
Coordination through row state rather than function calls means any stage can be re-run for one candidate, one night, or one job — which is what makes debugging possible at all.
Shipped in disciplined milestones
The pivotal release wasn’t the one that added a capability. It was the one that gave the scoring stage authority to hide roles the candidate would otherwise have seen.
Prove the loop end to end
Preference filters and keyword matching over ingested listings. It worked — and it recreated the exact problem candidates already had: a wall of near-misses with no ranking anyone believed.
Fix the adjacent-title blindness
Embedding roles and profiles meant an SRE finally saw Platform Engineer postings. Recall improved sharply; precision didn’t, because everything retrieved still reached the candidate.
Make relevance measurable
Six weighted sub-scores, weights in versioned config, and a gate at 75. The first release that suppressed anything — and the first candidates called genuinely useful, because the list got short.
Make the score defensible
Explanations templated from the same numbers the gate used, plus hard blockers pulled outside the blend. Trust in the percentage turned out to depend entirely on the sentence beside it.
Make acting on a match cheap
Resume tailoring per job description, bounded by profile facts, with output validated against the source. The validation pass was added after the first fabricated employer, not before it.
Close the loop after the click
Reaching interview stage in the tracker became a trigger: async prep generation with progressive UI, graded mock sessions, and salary intel paired with negotiation language.
Make autonomy affordable
Free and Pro drawn along the line the cost model exposed: cached and arithmetic work is free, per-intent generation is gated — checked before the API call rather than in the interface.
A production agent, not a prototype
A nightly scan ends with a short list the candidate can actually read, each row carrying the two signals that earned it and any blocker that didn’t.
Tailoring a resume to a specific job description stops being the reason someone applies to four roles a week instead of fifteen.
Company context, likely questions, questions to ask and salary framing arrive when an interview enters the tracker — not the night before, assembled by hand.
Every surfaced match ships with a reason derived from its own score components, so the product can defend any number it shows.
Nightly matching is cached and arithmetic; only per-intent generation costs real money, and that is exactly where the entitlement boundary sits.
Zero applications sent autonomously, zero facts invented on a resume, and suppression visible rather than silent.
Idea in your head? Let’s
bring it to life.
Got a project? A wild idea? Or just want to say hey?
We're here for all of it — reach out anytime.