Live · daily

Job Radar: an agentic job-matching workflow

Every morning it turns around 350 fresh job offers from three job boards into a top 5, each scored 0–100 with quotes from the posting as evidence. The aim is to rank them the way I would. It is still learning from my decisions. It runs on my PC and is read-only: it never applies or sends anything.

354offers collected
30sent to the LLM judge
5shown each morning
ProjectSolo, product and build
PatternRadar: collect, structure, score, learn
RunsDaily at 08:00, local only
StackPython, LangGraph, Dify, ChromaDB, LM Studio, Arize Phoenix
Job Radar dashboard on 12 Nov 2026: today's top 5 offers with scores from 70 to 81, and the top match at 81%, class A, 8 of 8 requirements and 7 of 7 must-haves, with evidence quotes from the posting. Company names blurred.
Run of 12 Nov 2026: the top 5 and the best match, with its requirement check and evidence quotes. Company names blurred.

Top matches, run of 12 Nov 2026

354 found · 30 scored by the judge
  1. 1(Senior) Product Manager, Consumer TouchpointsPublished 30 Oct · 8/8 requirements · 7/7 must-haves81A
  2. 2Senior Product ManagerPublished 12 Nov75A
  3. 3Senior Product ManagerPublished 12 Nov74A
  4. 4Product Owner, ConsumerPublished 12 Nov71B
  5. 5Associate Product ManagerPublished 12 Nov70B

Scores out of 100. Class A means to apply, B means to consider. Company names hidden.

The problem

Problem: I was triaging about 150 offers by hand, and keyword alerts could not tell a good fit from a near miss.

Objective: a search tailored to me. Each morning, a top 5 of fresh offers (≤14 days), scored 0–100 with evidence, aiming to rank them the way I would. Read-only: it never applies or sends.

How the pipeline works

Sources
Platsbankenopen API
LinkedInpublic listings
Jobbsafaripublic listings
Runs on my PC
Collect + deduplicatesame offer on several boards counted once
Rules + freshnesshard gaps, offers up to 14 days old
Profile similarityChromaDB embeddings
LLM judgeGemma 4 26B in LM Studio, top 30 only
Verify evidencequotes must exist in the posting
StateSQLite
TracesArize Phoenix
Outputs
Daily top 5HTML report, classes A/B/C
Dashboardfavorite, applied, disqualified
↺ Feedback loop. Favorites, applications and written disqualify reasons go back to the judge as labels.

Why only 30 offers reach the LLM. Cheap steps run first: rules, freshness and similarity to the profile are instant and free. The local judge is slow, so it only sees the 30 most promising offers. The risk is a good offer cut before the judge, which the weekly check of rejected offers is there to catch.

Dify workflow of the radar: collect tool, parse shortlist, LLM judge loop on Gemma 4 26B, assemble, verify and rank tool, top 5
The same pipeline as a Dify workflow: Python tools do collect, rules and verify, and Dify loops the local judge (Gemma 4 26B in LM Studio) over each offer.

Who does what

Claude CodeDesigns and writes the code, tests and run scripts. In the search pipeline, it never sees my profile data at runtime.
Local AILM Studio runs the judge, Gemma 4 26B today after starting with Qwen3.5-9B, and nomic for embeddings. ChromaDB and SQLite are local, so profile and scores stay on my PC during a search.
OrchestrationLangGraph, with a Dify version using the same tools via a local API. Arize Phoenix traces every step.
SchedulingWindows task at logon and daily at 08:00, guarded to run once a day.

How I keep it reliable

  • Measure: per-run metrics (found per source, scored, shown, in queue, retries, unverified, time). A typical run: about 350 offers found, 30 scored by the LLM, 5 shown.
  • Find another route: if a source is down, the run goes on with the others. If the LLM is down, it falls back to rules + similarity, capped at class B.
  • Bounded: step limit, shortlist of 30, max 2 LLM retries.
  • Check, don't trust: output is validated with Pydantic, and an independent verify step halves the score when evidence isn't found.
  • Minimal rights: HTTP GET and localhost only. No destructive actions exist.

How it learns from my decisions

Every decision I take in the dashboard becomes a label: favorite or applied is positive, disqualified is negative, with a short reason. The judge reads these, flags offers that look like a past rejection and lowers their score.

So far: more than 100 reviewed offers, so the first gate below is passed. The first real agreement measurement is the next update on this page.

Now: more users, and offline on my phone

Since November the radar has accounts. Each person imports a CV or a LinkedIn PDF export, and that becomes their own profile. Each account runs its own search and only sees its own data. Other accounts use the local model only, so nothing leaves the PC.

When the PC is off, a copy of the dashboard on my Synology NAS lets me keep sorting offers from my phone. My decisions sync back when the PC starts again. Both stay on the home network, not on the internet.

Pipeline board: offers moved from favorites to applications sent, interview, offer and closed, with totals on top. Company names blurred.
After the top 5, the follow-up: a pipeline board from favorite to interview and offer. Company names blurred.
Users page: an admin account and a user account, with password reset, disable, and a form to create a new account (3 accounts maximum).
Accounts are simple and local: the admin creates them and resets passwords, no e-mail involved.
Runs and metrics page of a second account: 243 offers found (LinkedIn 74, Platsbanken 145, Jobbsafari 24), 30 scored by the LLM, 5 shown, 24 in queue; training data and storage panels.
Runs & metrics for a second account, with a different profile and its own run.
Job Radar on a phone: the second account's top offer at 92%, class A, 7 of 8 requirements.
The second account on a phone, over the home network: a different profile gets different top offers.

What unlocks the next step

Gate 1 · 50 reviewsFirst measurement: judge agreement and a precision@5 baseline (how many of the daily top 5 I would really keep). Passed in November 2026.
Gate 2 · 10–15 daysDaily marking of the top 5, plus a weekly check of rejected offers to catch false negatives.
Then · tuneTune the weights of rules, similarity and LLM score, and the A/B/C thresholds. Keep a change only if precision@5 goes up.

What I learned

The first judge, Qwen3.5-9B, clustered its scores (78–84), so it barely told offers apart. I benchmarked Gemma 4 26B on the same offers and switched the judge to it, while rules and similarity still carry much of the signal. The bigger lesson: a model doesn't know what I want until I tell it. Written disqualify reasons turned out to be the most useful feedback, because the judge can read them and cite them. Early agreement, below 50%, showed the prompt alone did not capture my judgement, so the next step is measurement, not more prompt tweaks. Tuning without data is guessing.

Arize Phoenix trace of one LLM judge call
One judge call traced in Phoenix: local model, no API cost.

Same workflow, other cases

Planned

Lead Radar

Goal: give a sales team a short daily list of companies worth contacting.

A company that posts a data or product role is investing in that area, so it is a likely buyer for a BI tool or a consultancy.

Planned

Feedback Radar

Goal: show a product team the 5 user problems that matter most today.

Groups app reviews and support tickets by theme, and scores each theme on severity and how many users it affects.

← Back to projects