This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
What I Built
freelance_search is a job agent that runs entirely on my machine. On a schedule it crawls five job sources, discards whatever is a bad fit for me specifically, and posts a shortlist of 20–30 links to Discord. No feed to scroll, no tabs to keep open, no applications wasted on roles I am not senior enough for or cannot legally take.
The problem it solves: if you are a freelancer, intern, or junior developer job-hunting across several boards, you redo the same triage by hand every day. You open five sites, skim a hundred listings, and mentally discard the ones in the wrong country, the ones titled "Staff Engineer," and the ones whose links already 404'd. That is an hour of low-grade work a machine should be doing.
Who would pay for it: anyone stuck in that loop. The job-search tooling market exists because people pay for leverage when they are competing against hundreds of applicants. This automates the triage step, on your own hardware, without shipping your profile to anyone.
Demo
Code
SammySN-car
/
freelance_search
Daily freelance job scout — SerpApi + free feeds, scored by a local Gemma model, shortlist delivered to Discord
freelance_search
freelance_search is a daily opportunity scout that anyone can point at
their own career stage: it searches SerpApi Google Jobs and free remote-job
feeds for freelance work, internships, and entry-level jobs, scores each new
listing with a local Ollama Gemma model against your profile, and posts a
ranked list of up to 25 links to Discord each morning. A job is persisted as
shown only after Discord confirms delivery, so the same job is never shown
twice.
Everything it optimizes for comes from your own configuration — there is no hardcoded location or seniority bias in the code.
Quickstart
Requirements: Windows, Python 3.11+, and Ollama running locally.
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -e ".[dev]"
ollama pull gemma3:4b
Copy-Item .env.example .env
# Edit .env: Discord + SerpApi values, then your PROFILE,
# LOCATION, and EXPERIENCE_LEVEL (see Customization below).
python -m…How I Built It
Two open-weight models, both running locally through Ollama:
-
mxbai-embed-large— embeds my profile and every listing into a shared vector space. Dense cosine similarity ranks candidates; it never discards them. -
gemma3:4b— the judge. It reads the top candidates and scores each one 0–10 against my profile, with a one-line reason.
The retrieval funnel, per run: crawl roughly 700 raw listings from five sources (Arbeitnow, Jobicy, remoteOK, We Work Remotely, and SerpApi's google_jobs engine) → embed → rank by similarity → hand the top 50 to Gemma for scoring → keep everything above a floor → post the best 20–30 as a links-only digest.
The part worth writing up is how the agent decides what "a bad fit" means, because I got it wrong twice.
First I tried keyword filtering. Drop any listing whose text mentions "senior," or whose location field names a country that is not mine. It worked, and it was a mistake. It threw away good listings that used the word incidentally, and it hid listings that described senior work without the title. Filtering on keywords is information loss. So I split the problem in two: dense retrieval handles relevance and discards nothing, and the only hard drops are the two things a keyword genuinely settles — a stated foreign location, and a seniority title that contradicts my level. Both come from my .env, so the agent carries no hardcoded bias toward any country or any seniority. It filters for me, not for a hypothetical average user.
Second, I had it read my GitHub to work out my level. Telling the model "you are matching for an intermediate developer" is a guess I typed in. Instead the agent lists my repos, pulls real source from each, and asks Gemma to name a level — but only after citing the specific files that prove it:
# Instruction goes AFTER the code: with a large code context, gemma3:4b follows
# recency and ignores format rules placed before it (verified locally).
GITHUB_PROMPT = (
"CODE:\n{code}\n\n"
"Now answer based ONLY on the code above, output exactly two lines:\n"
"Level: beginner|intermediate|senior\n"
"Evidence: <one line citing at least two specific repo or file names as proof>\n"
"Judge what the code demonstrates: completeness, structure, testing, and complexity. "
"Ignore stars and popularity. Output only those two lines, nothing else."
)
That comment is the whole post, really. Here is why it is there.
Why Does Open Innovation Matter?
Because I could only fix this bug because the model is open-weight and running on my machine.
The first version of that GitHub prompt asked Gemma for a JSON object — {"level": ..., "evidence": ...} — with the instruction placed at the top, before the code. On a large code context, gemma3:4b under JSON mode collapsed. Feed it roughly 63,000 characters and it returned an empty {}. Feed it roughly 30,000 and it invented an entire basketball league as its JSON response — a structure with nothing to do with the code, confidently emitted.
A closed API would have hidden every step of that. I would have gotten a wrong answer with no way to see why, no way to re-run the exact prompt and watch it fail differently at different context sizes, and no cheap way to iterate through five variations. Running it locally, I reproduced the failure on demand, bisected it by prompt length, and found the two things that actually mattered: put the instruction after the code (the model follows recency, not position), and ask for two plain-text lines instead of JSON (the parser takes the first Level: and Evidence: lines and ignores whatever else is echoed back). One retry, and it has held ever since.
That is the case for open innovation in a single bug report. An open, local model turned a silent wrong answer into a debuggable one. It also means my code, my job history, and my profile never leave my machine — which, for a tool that reads your source repositories to judge your skill level, is not a nice-to-have. It is the only version of this I would be willing to run.
My First User
Me. It has been running against my own profile daily through the entire build, which is how the basketball-league failure got caught in the first place — a staged demo would never have pushed a 63k-character context. No one else has used it yet, and nobody has paid for it. (Cut this section if you would rather not include a self-described first user.)
Prize Categories
-
Best Use of Gemma —
gemma3:4bis both the judge and the level-detector. It runs locally through Ollama and is what makes the ranking work at all. -
Best Use of SerpApi — the
google_jobsengine is one of the five sources, surfacing Google-indexed roles that never appear on the boards directly.

Top comments (0)