JobHinge · Plan for review

Search: what we already have, and where embeddings actually help

Three layers. Two are already built. Embeddings are the third one — not the first.

August 6, 2026 · Every number below was read directly out of the live database on VPS2, not from documentation · Source of record: plans/jobhinge-search-layers/plan.mdx

The finding that reorders everything

Your parser has already extracted about 100 structured fields on every one of your 298,956 jobs — skills, seniority, work mode, years of experience, salary, location, category.

Which means a search like "remote python developer" is a plain database filter over data you already paid to produce. Embeddings are only for the questions the other two layers genuinely can't answer — and that set is smaller than it feels when you've been talking about AI all night.

01One search, to make this concrete

Watch which layer answers each question. This is the entire plan in one table.

What someone typesWhat answers itWhy
remote python developerLAYER 1Work mode is "remote," skills contain "python." Exact, instant, and you can explain any result.
registered nurse night shift TampaLAYER 1Title, shift and city are all already parsed columns.
senior devops kubernetes terraformLAYER 2Loose pile of skill words. Full-text ranking handles the mix without needing a field for it.
a job where I won't be micromanagedLAYER 3No field holds this. No keyword matches it. Meaning is the only handle.
something like my last job but less travelLAYER 3Needs comparing to a whole profile, not matching terms.

02What actually exists right now

Layer 1 — built, and barely used
~100 structured fields per job
skills · seniority · work mode · salary · location · category · years required
Populated on 298,956 of 298,956 jobs
Cost to search: zero. No AI call, no outside service.
Layer 2 — built, and live today
Postgres full-text search
A proper search index, populated on all 298,956 jobs
This is what your site runs on right now — it is not crude keyword matching
Layer 3 — does not exist
Meaning-based search (embeddings)
There is no embedding column on jobs at all.
The two that do exist are on other tables — and both are completely empty.

The scaffolding that fooled me earlier

ColumnSizeRows in itReality
company_enrichment.embedding15360Built, indexed, never filled
user_profiles.profile_embedding15360Built, indexed, never filled
anything on jobsDoesn't exist
And they're the wrong size anyway

Both are built for 1536 numbers — that's OpenAI's shape. Both models we tested produce 1024. Neither fits. Whatever we do starts with something new, not filling in something old.

03If we do embed — which engine

Same 12 job postings, same 5 plain-English searches, graded on whether the right job came back first.

EngineSizeCorrectSpeedAll 298,956 jobs
Talos — already installed10245 / 58.2/sec~10 hrs, once, overnight
NVIDIA hosted10245 / 517.6/sec~4.7 hrs, once
NVIDIA hosted (bigger)20485 / 516.7/sec~5 hrs, once
Recommendation: Talos

It scored identically. It's hardware you already own and already pay for. And it carries none of NVIDIA's problems — no 40-per-minute ceiling, no terms saying "testing only," no model that can be retired out from under you, no question about who sees your data.

The entire cost of choosing it is about five extra hours on a one-time overnight job, on a box that sits idle anyway.

Why this is low-risk either way — the part that should settle your unease

Embeddings are calculated once and stored in your own database. Nothing calls out to anyone when a user searches — that math happens inside your Postgres.

It's a batch job, not a live dependency. That's the exact opposite of the parser or the classifier, where every single job needs a live call and an outage stops the pipeline. Those would be reckless to move. This one isn't.

04Decisions that are expensive to undo

These get settled before the work, because 298,956 stored vectors are painful to redo.

DecisionRecommendationWhy you can't easily change it later
Which modelTalos, localVectors from different models aren't comparable. Switching = redo everything.
Size1024 (follows the model)Column type is fixed when created. Changing it is a migration plus a full redo.
Where it livesA new side table, not a column on the jobs tableThe jobs table is ~100 columns and busy. A side table can be dropped without touching it.
What text gets usedTitle + summary + must-have skillsChange the recipe and every stored vector is invalid. Decide before the run, not after.
Index tuningSkip it for the trialNeeds real data present to tune. Building it early just means rebuilding it.

05The first cut — small enough to throw away

Nothing live is modified. If it doesn't impress you, we drop one table and walk away.

  1. One new side table
    Separate from your live jobs table, so it can be deleted cleanly.
  2. Embed about 5,000 real jobs on Talos
    Roughly ten minutes at the measured rate. Enough to judge, cheap to redo.
  3. A read-only comparison page
    One search, three columns side by side: structured fields, today's search, embeddings.
  4. Test with searches you write
    My five queries were my own wording — that's the weakest evidence in this whole plan.
  5. You look at them and call it
    If embeddings don't visibly beat the other two columns, we stop and delete the table.

Explicitly not included

The weakest evidence in this plan — stated plainly

That 5-out-of-5 result came from 12 clean job postings and 5 queries I wrote myself. Your real corpus is 298,956 messy ones.

It shows the local model isn't disqualified. It does not prove it wins on real data. The trial exists specifically to replace that weak evidence with real evidence before you commit to anything.

06The order I'd actually do this in

#WorkEffortExpected payoff
1Use the ~100 fields you already extractedLow — plain database queriesHighest. Exact, explainable, nothing new to break.
2Tune the full-text search you already haveLow — already built and filledModerate. Better ranking on messy multi-word searches.
3Embeddings trial on 5,000 jobsMedium — one table, one overnight runUnknown until measured. That's the point of a trial.
4Full corpus + index tuningHigher — 10-hour runOnly if step 3 visibly wins.

07What I need from you

Structured fields first, or the embeddings trial first?
Both are safe. Layer 1 almost certainly gives more improvement per hour of work — but the embeddings trial answers the question you actually asked me.
Structured fields first RECOMMENDED
Biggest likely win, zero new dependencies. Embeddings after.
Embeddings trial first
Answers your question directly. 5,000 jobs, nothing live touched.
Both — they don't conflict
One is database queries, the other is an overnight batch on an idle box.
What are five searches you'd actually type into JobHinge?
My test used my wording, which is the weakest part of this plan. Your real phrasing is what the trial should be graded against — and it's the one thing I can't produce myself.
Any objection to dropping NVIDIA from this entirely?
Talos matched it in testing and carries none of the terms-of-service, rate-limit, or model-retirement risk.
Drop it — use Talos RECOMMENDED
Hardware you own. No outside dependency in the path at all.
Keep NVIDIA as a comparison arm in the trial only
Costs nothing extra, gives a quality reference point on real data.