AI Headhunter
Headhunter turns a natural-language brief — 'Find CTOs at AI startups in London' — into a deduplicated list of contactable candidates. Production runs a deterministic six-step pipeline across Exa, Parallel.ai, and Firecrawl, gated by a current-title relevance check. The 2.0 design makes the internal people database the first free rung of a waterfall, lets the agent pick its own sources, resolves duplicate identities automatically, and writes every discovery back so each run makes the next one cheaper.
Headhunter, and why 2.0
tl;drEnd users are recruiters and agencies running talent search. Headhunter takes a thin intent and progressively enriches it into structured candidate rows — discovery across multiple sources, a relevance gate on the current title, verified email, and a deduplicated CRM-linked row per person. Production works, but it leaks money and forgets what it finds: every run goes straight to paid sources even when half the answer is already in the database, the sources are hardcoded so adding one is surgery, and results land in two tables that never reconcile. 2.0 fixes all three with one idea: the internal database is not a destination, it is the first rung of a waterfall - and every run feeds it.
A candidate is relevant because of who they are now, not who they once were. Exa happily returns a profile when the searched title appears anywhere in work history — so a former CEO now writing software matches a CEO search. Production's defining decision is a relevance gate that validates the correctly-extracted current title against intent, before any per-candidate spend — and 2.0 keeps it.
Write-back is what turns a search tool into an asset that compounds. Each run resolves new people into the graph with provenance and an embedding, so the next run for a similar brief starts with a higher internal hit-rate and a smaller external bill. Cold orgs pay full price once; mature orgs source mostly for free. Drag the slider in section 03 to watch it happen.
System design
before / afterTwo designs, one toggle. Production today is the shipped six-step pipeline: a host-side orchestrator drives Strategist → Storage, reaching providers only through key-holding source tools, with supplements that fail open. The 2.0 design re-arranges the same job around the people intelligence graph: internal-first recall, an agent that routes only the gap, and a write-back flywheel. Toggle the two, then click any box or node to inspect it.
Provider keys never cross into a model context — the trust boundary sits below the source tools. Click any box to trace its connections.
Six deterministic steps; only the Researcher adapts, pulling in supplementary sources when the index is thin. Switch the run shape, then play the run or click any node to inspect it.
Brief shape: “Find me strong candidates fast — Exa returns plenty.”
Strategist
messages.parse()Turns the brief into a query plan: expands title synonyms, splits a LinkedIn tier from an open-web tier, and sets a recency window. One round-trip, no tools.
- title synonym expansion
- LinkedIn + open tiers
- no hallucinated filters
Exa always runs; Parallel.ai supplements only on thin queries and Firecrawl only enriches data-sparse profiles — and both fail open, so a supplement outage never surfaces an error to the user. When two discovery sources return the same person, the records are merged (non-null fields unioned), not deduplicated away. And the qualifier sits ahead of the per-candidate Hunter.io call, so the expensive steps only ever run on candidates worth contacting.
Scope
interactive Scope is one LLM pass: it turns the brief and UI filters into a typed SearchSpec - the what, never the how. It sets the one decision that branches the whole run: mode. Pick a brief and watch it resolve; hover any spec field to trace it back to the tokens it came from.
- titlesMarketing Director
- seniority_banddirector
- industryAI · SaaS
- stageSeries A
- geo.citiesLondon
- must_havesB2B SaaS experience
- exclusionsagencies
No company named, so mode = icp. Attributes bound the search; discovery (step 3) will query by role + industry + geo.
Companies don't have to be typed. A company_scoped run pulls them from four places, and provenance rides along. resolved sources (CRM, Sonar) arrive with a domain and an entity id, so Scope passes them straight through; raw sources (manual, CSV) arrive as a name Scope has to resolve. Click a source to see the companies[] entry it yields.
- name"Ramp"
- sourcesonar
- domainramp.com
- ref_idworkflow_run_company_id
- lead_score8
- signalSeries B · $150M
- resolvedtrue
Scope passes it through and carries the buying signal that made it hot - qualify scores people in that context.
A Sonar run surfaces companies with a live buying signal; handing those straight into a company_scoped Headhunter run finds the people to contact at exactly those companies - domain, lead_score, and the signal all ride along. The company graph feeds the people graph, and write-back stamps each person with the run that found their company, so the lineage is never lost.
No queries, no sources, no recency window are chosen here - Scope only emits the typed target. Generating queries and picking providers is the Source planner's job (step 3), and it runs only on the gap that internal-first leaves. Splitting what from how is exactly what lets the graph answer first.
The internal-first waterfall
playgroundThis is what "internal database first" actually buys. A brief wants 100 people. The graph answers some for free; only the gap waterfalls out to paid sources, cheapest first, stopping at the first hit. Drag how many matching people already live in the graph - or load a scenario - and watch the bill move.
211 credits saved this run by answering from the graph first.
Each rung only sees what the rung above missed. Exa never runs on people the graph already knew; Parallel.ai - the premium rung - only sees Exa's misses. That ordering is the cost discipline. Three to four sources is the known sweet spot; a fifth recovers almost nothing for the credits.
The source registry
click a sourceProduction hardcoded each source and hand-wired it into the pipeline - adding one was surgery. 2.0 puts every source behind one contract, and the agent reads that metadata to route. The order below is the waterfall priority the planner follows. Click any source to expand it.
capabilitiessearch | enrich | bothcost_tierfree | low | premiumrate_limitreq/s + budgetfield_mapperraw → canonical personImplement those four, register the entry, and the planner can route to it. No pipeline change.
The relevance gate
playgroundThe qualifier is the one place precision is won or lost — production shipped it, and 2.0's Qualify step keeps it unchanged. It scores each candidate's current title against the search intent — a free substring pass first, an LLM synonym pass for the rest — and drops anyone below 40. Pick a search and a candidate and watch the verdict. Prior roles never count.
Bands and the synonym map here are illustrative of the deterministic-then-LLM code path. The point: the gate judges the current role, the prior role is preserved for context but never lifts the score.
Entity resolution
cascadeEvery candidate - internal or freshly found - runs the same identity cascade before it touches the graph. Deterministic keys first, then similarity, then semantics. The first tier to match wins; matches merge by field-union, no human review. Pick a record pair and watch which tier resolves it.
j.okafor@acme.com · LinkedIn missingJ. Okafor · j.okafor@acme.comEmail is the strongest identity key. Exact match merges deterministically, no scoring needed.
The people intelligence graph
the store Production splits discoveries across workflow_run_people and crm_people - two tables that never reconcile. 2.0 unifies them into one graph that read and write-back both target. Four layers, one canonical person.
Person records
One row per real human, not per run. The merge target. Field-union of everything every source ever said, each field stamped with its winning provenance.
Identity edges
The graph that ties a LinkedIn slug, an email, and N run-rows to the same canonical id. This is what entity resolution writes and what dedup reads.
Profile embeddings
One pgvector per person. Powers internal-first semantic recall and the embedding tier of the resolution cascade from a single index.
Provenance + freshness
Per-field source and timestamp. Stale fields re-enrich on a TTL instead of being re-searched from scratch. The audit trail of where every fact came from.
Internal-first reads it, entity resolution writes its edges, write-back upserts its people. One store closing the loop is what lets the graph compound instead of fragmenting across runs.
Design decisions
lockedThe forks settled in the design session. Each one trades a knob for less friction - the 2.0 bet is that automatic beats configurable for this workflow.
| Fork | Decision |
|---|---|
| Internal search | Auto prefilter, no UI. The graph is queried first on every run; known people never hit a paid source. |
| Source selection | Agent auto-selects from the registry in waterfall order. No per-run toggles to micromanage. |
| Deduplication | Fully automatic. Deterministic keys plus fuzzy plus embedding, merged by field-union. No review queue. |
| People store | Unify workflow_run_people + crm_people into one graph with identity edges. v1 kept them split. |
| Extensibility | Source registry contract. A new source is one entry - capabilities, cost, mapper - not a pipeline rewrite. |
| Enrichment | Waterfall, cheapest-first, stop on hit. Pay each provider only for what it resolves. |
Build sequence
roadmapGraph first - nothing works without the unified store. Then resolution, then the internal rung for an immediate cost win, then the registry, then write-back to close the loop.
People intelligence graph
Unify the two tables into canonical person + identity edges + per-person embedding. Backfill from existing run rows and CRM. Nothing else can land first.
Entity resolution service
The identity cascade as a standalone service - deterministic, fuzzy, embedding - writing identity edges. Prove it on the backfill before any run uses it.
Internal-first rung
Query the graph before any external source. Instant cost win even before the registry exists - the gap is all that goes out.
Source registry + planner
Move Exa, Parallel, Firecrawl, Hunter behind the contract. The agent routes the gap across them in waterfall order.
Write-back + provenance
Close the loop - every run upserts canonical people with provenance and refreshed embeddings. This is the rung that makes the flywheel turn.