Skip to main content
Back to blog

I Automated My Own Job Search

6 min read

I'm looking for a full-time role in AI Product. The honest version of a job search is grinding job boards: open RemoteOK, open the monthly Hacker News "Who's Hiring" thread, skim a few hundred postings, ignore the noise, find the three that actually fit, repeat tomorrow. It's tedious, it's repetitive, and it's exactly the kind of work I keep telling people an agent should be doing.

So I built the agent instead.

The Worker

job-scanner is a Forge worker. It runs once a day at 8 AM, the same way the rest of my overnight and morning chain runs. The flow is simple to describe:

fetch (RemoteOK + HN "Who's Hiring")
  → dedup
  → LLM-score each posting against my preferences brief
  → filter by min score threshold
  → post matches to a dedicated Slack channel

Each posting gets pulled, normalized, and deduped against what I've already seen so I don't get the same listing twice. Then the interesting part: every surviving posting is scored by a local model against a written brief of what I actually want — role type, seniority, AI/PM focus, remote, the things that make a job worth a conversation versus a job that just contains the word "AI."

Matches above the threshold land in a Slack channel I check with coffee. Everything else dies quietly.

The Product Decisions Were the Real Work

The code is maybe an afternoon. The decisions are what make it usable instead of annoying.

A minimum score threshold, not a ranked dump. The naive version scores everything and shows me the top ten. But the top ten of a bad day is still ten mediocre jobs, and a tool that pings me with mediocrity every morning gets muted by Thursday. So the worker only surfaces postings that clear a real bar. If nothing clears it, I see nothing.

No-spam-on-empty. This is the decision I'm most happy with. If zero postings clear the threshold on a given day, the worker skips the digest entirely. No "0 matches today" message. No empty-state noise. Silence is the signal — it means there was nothing worth my attention, and the absence of a ping tells me that without costing me a glance.

That sounds small. It's the whole difference between a tool I trust and a tool I learn to ignore. A notification that's sometimes empty trains you to stop opening it. A notification that only ever contains real matches trains you to open it every time. I'd rather the worker say nothing than say nothing-useful.

This is the same judgment call I'd make on any product: an alert that cries wolf is worse than no alert. The threshold and the empty-skip are guardrails against my own tool becoming spam.

The War Story: The Model That Burned the Clock

The first version returned zero matches. Every day. Not "no good jobs today" zero — broken zero. Nothing ever cleared the bar because nothing was ever getting scored.

I'd built the scorer on a reasoning model — qwen3.5, running in thinking mode. Great model. Wrong job. In thinking mode it spends a token-and-time budget on an internal reasoning trace before it answers. For an open-ended analysis task, that trace is the value. For "score this job 0–100 against this brief and return JSON," the trace is the whole problem. The model would think its way right up to the timeout on every single scoring call and produce nothing usable before the clock ran out.

So I had a job scanner that fetched perfectly, deduped perfectly, and scored nothing.

The fix wasn't a better prompt or a longer timeout. It was picking the right model for the job. I swapped the scorer to gemma4:e4b — a fast, non-thinking model tuned for exactly this: structured JSON output at throughput. Scores started coming back in a fraction of the time. Matches started landing in Slack.

The lesson generalizes well past this worker: pick the model for the task, not for its reputation. A reasoning model is the right call for open-ended analysis. It is the wrong call for structured scoring, classification, or anything where you want a fast, shaped answer and not a philosophy seminar. Structured scoring is not open-ended reasoning, and treating them the same silently sabotages the task — it fails green, looks like it's "working," and returns nothing.

And Then There Was the Infra Reality

I'll be honest about the unglamorous part too. The first deploy mis-tiered: I routed the scoring worker to a CPU-only box. The model technically ran, but slowly enough that it stacked up against the same timeout from the other direction. Moving it to the GPU node fixed the throughput.

That's the part of "AI product" nobody puts on a slide. It's not just prompt and ship. It's: which machine, which model class, which timeout, what happens when the box is busy. The job-scanner only works because it runs on the right hardware with the right model under a coordination lock that keeps it from fighting the rest of my morning chain for the GPU. Product judgment includes the boring operational realism, because that's where tools quietly stop working.

What It Actually Does — and Doesn't

Here's the honest framing, because I think it matters for how you read this.

The worker does not apply to jobs. It does not write cover letters, it does not click "Easy Apply," it does not pretend to be me. It surfaces candidates. It does the part that is genuinely mechanical — fetch, dedup, score, filter — and it hands me a short, high-signal list. The human work is still entirely mine: reading the company, deciding if I care, writing the note, having the conversation, being a person someone wants to hire.

That division is the point. The agent owns the throughput task it's good at. I own the judgment task I'm good at. I'm not automating away the job search; I'm deleting the tedious top of the funnel so I spend my attention on the part that's actually a decision.

Why I Built This Instead of Just Grinding

There's an on-the-nose joke here that I'm fully aware of: a person looking for an AI Product role built an AI product to look for the role. But that's also the most honest interview answer I could give. Faced with a repetitive, judgment-light, high-volume task, my instinct was to define the input-output contract, pick the right model for the scoring, build the guardrails so it wouldn't become noise, ship it, and prove it works against a real need — mine.

That's the job. Not the code — the code is the easy part. The job is deciding what to surface, what to suppress, what to automate, and what to leave to a human. The job-scanner is a small thing, but it's a complete thing, and it's running every morning at 8 AM whether or not I feel like grinding a job board.

If you're hiring for AI Product and you want to talk to someone who reaches for "build the tool" before "do the toil," that's me. reed@grainlabs.io.