Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TalentGraph

Recruiting intelligence on a single AI-native engine. Four queries, four systems deleted.

Built on SynapCores CE 1.13.0-ce. No dependencies, no build step — node server.mjs.


The problem

Candidate matching normally needs four systems:

Need Usual tool
Records and joins Postgres
"Does this resume mean the same thing as this job?" Pinecone / Weaviate
"Who can introduce us?" Neo4j
"Write the outreach" OpenAI API

Every arrow between those boxes is a network hop, a sync job, and a chance for drift — the vector index says one thing, the graph says another, and nobody knows which is right.

TalentGraph runs all four inside one engine, in one transaction.


Run it

1. Start SynapCores (once):

curl -fsSL https://get.synapcores.com | sh

# Before first boot: the default model is qwen2.5-coder:7b (4.4 GB).
# Swap it for something small in ~/.synapcores/gateway.toml:
#   [query.ai_service]
#   model = "llama3.2:1b"

printf 'export AIDB_JWT_SECRET="%s"\n' "$(openssl rand -base64 32)" > ~/.synapcores/gateway.env
source ~/.synapcores/gateway.env
/opt/homebrew/bin/synapcores --config ~/.synapcores/gateway.toml --accept-license \
  >> ~/.synapcores/gateway.log 2>&1 &

grep -A 14 FIRST-BOOT ~/.synapcores/gateway.log     # admin password + API key

2. Seed and run:

cp .env.example .env          # paste the aidb_… key
node scripts/bootstrap.mjs    # schema, 12 candidates, embeddings, graph
node server.mjs               # → http://localhost:3000

No npm install. Node 18+ built-ins only.


The four queries

Each one removes a box from that table above.

1 · Keyword search — what every ATS ships

SELECT name, headline, years_exp FROM candidates
WHERE resume_text ILIKE '%Rust%' OR headline ILIKE '%Rust%'
ORDER BY years_exp DESC

Three hits: a weekend hobbyist, someone who did one experiment, a bootcamp grad.

The strongest candidate in the database is not in that list. Dana Kovalenko has eight years of exactly this work — she writes "memory-safe compiled language with zero-cost abstractions and a borrow checker." She never types "Rust", so substring matching structurally cannot reach her.

2 · Semantic search — replaces Pinecone

SELECT c.name, c.headline,
       ROUND(COSINE_SIMILARITY(c.resume_vec, j.desc_vec), 4) AS match_score
FROM candidates c CROSS JOIN jobs j
WHERE j.id = 'job_001'
ORDER BY match_score DESC LIMIT 6
candidate score
1 Dana Kovalenko 0.6693
2 Tom Achebe (keyword match) 0.6159
3 Priya Raman (keyword match) 0.6025
4 Yuki Tanaka 0.5149
5 Marcus Webb (keyword match) 0.5076

Dana first. Note the keyword matches don't vanish — they rank correctly, beneath the person who actually did the work. Same table, one function call. No second database, no sync job, nothing to drift.

3 · ⭐ Semantic + graph in one query — replaces Pinecone + Neo4j

MATCH (j:Job {id:'job_001'})-[:SIMILAR_TO > 0.6]->(c:Candidate)
MATCH (c)-[:WORKED_AT]->(co:Company)
      <-[:WORKED_AT]-(peer:Candidate)
      -[:WORKED_AT]->(:Company {id:'co_001'})
RETURN c.name AS candidate, co.name AS shared_employer, peer.name AS warm_intro

Dana Kovalenko · Northwind Data · Sam Osei — in ~20ms.

SIMILAR_TO is a vector hop inside a Cypher pattern. Match the job to candidates by meaning, then walk their employment graph to find a warm introduction. One statement, one engine, one transaction.

Dana overlapped with Sam at Northwind Data. Sam is at Vector Labs now. A cold candidate just became a warm introduction.

With Pinecone + Neo4j this is two round-trips, application-layer glue, and two stores that can disagree about what is true.

4 · Draft the outreach — replaces the OpenAI API

SELECT c.name,
       GENERATE('Write a two-sentence recruiting message to ' || c.name || …,
                '{"max_tokens": 90, "temperature": 0.2}') AS draft_message
FROM candidates c WHERE c.id = 'cand_001'

Ranking, traversal and generation all happened inside the database. No feature store, no model server, no API key, no data leaving the machine. The model is a 1.3 GB Llama 3.2 running in-process.


Notes from actually building this

Honest observations from a day with the engine — the docs and the shipping binary disagree in a few places, and these cost me real time:

  • POST /v1/graph/match takes the body field sql, not cypher. The developers page documents cypher; the binary rejects it with missing field 'sql'. You still write Cypher.
  • /v1/graph/match is read-only — it accepts MATCH and nothing else. Cypher writes (MERGE) go through /v1/query/execute instead. CREATE (n:Label) is rejected there, because the SQL parser expects CREATE TABLE; MERGE works and is idempotent.
  • MATCH … SET and UNWIND … MERGE both fail, but several MERGE clauses in one statement work fine — so properties get set inline in the pattern and bulk loads become one statement per edge.
  • SIMILAR_TO only sees vectors stored on the node under the property name embeddingemb and vector are silently ignored, and EMBED() isn't callable inside Cypher. So bootstrap.mjs reads the vectors out of the SQL columns and writes them back as literal arrays. Undocumented, and the single most valuable thing I learned.
  • Health is /health, not /v1/health. There's no ARRAY_LENGTH(); get the embedding dimension from /v1/ai/embeddings.data.dimensions (384 for all-minilm).
  • ROUND(x::NUMERIC, n) fails::NUMERIC casts aren't supported. Plain ROUND(x, n) works.
  • Response envelopes are inconsistent: /v1/query/execute and /v1/ai/embeddings wrap in data; /v1/graph/match, /v1/auth/login and /v1/users/me don't.
  • It's much faster than the docs suggest. They say published binaries are CPU-only; the macOS build links Metal and GENERATE() returns in ~550ms on an M-series laptop, not the 30–60s the CPU-only figures imply.

None of this is a complaint — a v1.13 engine moving this fast will outrun its docs. But it's exactly the friction a design partner hits in week one, and it's cheap to fix.

What I'd build next

  • Vertical first: staffing and recruiting agencies. They feel the four-system tax hardest — small teams, no platform engineers, and a direct revenue link between match quality and placement fees. The pitch isn't "AI-native database", it's "delete three vendors and your sync jobs".
  • A design-partner POC in two weeks: their real ATS export, their real placement history. Success criterion agreed up front — recall@10 against roles they actually filled, measured against their current keyword search. That's a number a VP of Talent can take to a budget meeting.
  • Instrument the wedge: log every query's latency and system-of-origin, so by week one you can show "these 4 services became 1, p99 went from 380ms to 22ms". Land on the pain, expand on the graph — referral networks are where the switching cost gets built.

Layout

server.mjs              zero-dep HTTP server, named-query catalogue
queries.mjs             the four queries — the actual substance
lib/synapcores.mjs      minimal client; wraps the two API quirks above
scripts/bootstrap.mjs   idempotent seeding + self-verification
sql/bootstrap.sql       schema, 12 candidates, 5 companies, 3 jobs, embeddings
sql/graph-edges.cypher  employment edges
public/index.html       single-page UI, inlined CSS/JS

The dataset is deliberately small and hand-written. Dana's resume is constructed to contain no rust substring, so query 1 provably misses her — bootstrap.mjs asserts this on every run.

MIT.

About

Recruiting search on a single AI-native engine: semantic candidate matching, referral-graph traversal and outreach generation in one SynapCores CE instance. Zero dependencies.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages