People Signals
People Signals finds the person worth contacting right now - someone who just changed jobs, was promoted into a buyer role, published thought leadership, or spoke at a relevant event - and scores each by how strongly that signal predicts a reachable, in-market decision-maker for the agency running the search. It runs on the existing headhunter spine plus a signal-extraction and substantiation layer, with a composite score computed in code.
What People Signals does
tl;drCompany Signals finds the company; People Signals finds the person, and the moment. End users are marketing agencies doing cold outreach: account-level intent says "Acme is buying" but not who to call. People Signals surfaces a specific decision-maker because of a career event - a job change, a promotion, a talk - then scores them by how strongly that event predicts a reachable, in-market buyer. The signal is the entry point, not the filter: a five-day-old "just became CMO" beats a CMO who has held the seat six years.
A person is worth an agency's attention at the moment a career event creates budget, authority, or a warm door - not because they sit on a static title list. People Signals treats intent as perishable: it discovers dated career events, substantiates them against real evidence, weights them by source trust and freshness, and ranks against the agency's own ICP. It reuses the headhunter machinery wholesale and adds only the signal layer.
System design
interactiveOne People Signals search is a closed loop, mirroring Company Signals at the person grain. A brief leaves the frontend, the API opens a run on the Claude Agents Platform, which discovers and resolves signal-rich people then fans out per person to the enrichment agent - enrich the person, resolve and enrich the new company, and write the facts back. Results return along two paths: per-field patch frames stream live to fill the table (emit → run-SSE → apply) while resolved cells persist durably to workflow_run_people and the people intelligence DB. A later search skips enrichment for any person already cached. Follow the arrows, or click any stage to trace it.
Agent workflow
C4 · containersThe detailed breakdown of one search as a tiered map - one colour per repo, top to bottom along the data path. Search career-event signals from the public sources; once found, the run fans out per person to the enrichment agent (enrich the person, then resolve and enrich the new company); each enrichment writes back to the intel layer with provenance; then per-field frames stream to the live table. Hover any container for what it owns, or trace one person down the happy path.
The pipeline
interactiveA deterministic skeleton over the headhunter spine; discovery is the one agentic stretch and it is hard-capped. Switch the discovery anchor, then play the run or click any node to inspect it.
Answers "Who at my target companies just became worth contacting?"
Query build
deterministicExpands the ICP (titles, seniority, geo) and the chosen signal types into Exa / Parallel queries per company. No LLM - reuses the headhunter query_builder.
- title expansion (CMO -> full forms)
- signal templates per type
- recency window baked in
Signal-first queries
interactiveHeadhunter queries match a filter ("CMOs at these 50 companies"). People Signals queries hunt the event. Each signal type expands into its own templates - pick one to see what the query builder emits. Placeholders fill from the ICP and the company set.
"{name}" appointed / named {title} 2026new {title} at {company}{company} welcomes {title}"joined {company} as {title}"
{company} placeholder and cast across the ICP. Composite scoring
playgroundThe people score is computed in code - no override-rule prompt, no LLM-emitted number. Compose a few signals and watch it move; each weight here is a tunable constant in the real config. ICP fit is the headhunter qualifier; it gates hard, so a great signal on the wrong person still ranks low.
fit × Σ( type × source × recency )Curve is illustrative of the deterministic formula - exact constants are tuned at build from eval data. The point: the number is reproducible code, not model judgement.
Signal taxonomy
interactive v1 seed weights, tuned to outreach timing: events that create a fresh mandate or a warm door rank highest. They share the company SignalType enum - JOB_CHANGE, PROMOTION, TENURE are added; CONTENT, LEADERSHIP, EVENT are reused. Pick the agency's offer and watch the weights re-rank.
Just joined or left a company - the warm-intro window. Strongest people signal for outreach timing. JOB_CHANGE
Promoted into a buyer role = fresh mandate, new budget authority, a reason to reach out now. PROMOTION
Became a new CMO / VP at a target account. Reuses the company LEADERSHIP signal at the person grain. LEADERSHIP
Published or actively posting on a relevant topic = demonstrated focus and a personalization hook. CONTENT
Keynote, podcast, or conference slot. Timely, public, and easy to reference in the first line. EVENT
Hit a ripe-to-move window (~2yr in seat). Soft signal, best stacked with another. TENURE
Reuse & enrichment
headhunterAlmost nothing here is new. People Signals is the headhunter pipeline with a signal layer - the table maps what is reused as-is against the thin slice that is added.
| Stage | Reused as-is | Added for signals |
|---|---|---|
| Query build | query_builder, title / geo expansion | signal-type query templates |
| Discovery | per-company orchestrator, PeopleExaTools, Parallel.ai | event-led queries, dual anchor |
| Extraction | tool-less batched extraction | emit (person, signal) pairs |
| Enrichment | Firecrawl, Hunter.io, company-match | nothing |
| Trust | signal_substantiation.py | people evidence patterns |
| Qualify | two-pass qualifier -> ICP fit | composite signal blend |
| Persist | WorkflowRunPeopleService | person_signals + workflow_run_person_id |
LLMs will happily invent a job change from a vague sentence. Every event-class signal must pass a deterministic evidence check - the source text has to contain the move ("joined / appointed / named as", "promoted to") - or it is downgraded to a plain NEWS mention that cannot carry the score. This is the company substantiation gate, extended with people patterns.
Staging & storage
lifecycle Discovery is noisy, so people land in staging first - exactly like Sonar prospects - and promote to CRM on review. This is also what finally fills the person_signals table, which has shipped empty, reserved for this work.
workflow_run_peoplestagingNet-new discovered people land here first, in a review state. No unreviewed writes hit CRM - mirrors Sonar prospect staging.
signalsglobalThe dated career event: type · signal_date · source_url · snippet · source_agent. A new workflow_run_person_id column tags each signal to the staged person, mirroring the existing signals.workflow_run_company_id.
person_signalsglobalThe signal-to-person join. Built at promotion by looking up signals on workflow_run_person_id - exactly how company_signals is created from signals at add-to-CRM. Ships empty today, reserved for this.
crm_peopleper-orgOn promote-to-CRM, the staged person becomes a CRM person and the person_signals join is written against person_id - mirrors _link_staging_signals_to_crm.
Design decisions
trade-offsBoth, company-anchored primary
Per-company orchestrator hunts events at your CRM / Sonar list for precision; open signal search adds reach beyond it. Mirrors the headhunter companies-present + open-search split.
Share the company SignalType enum
Add JOB_CHANGE, PROMOTION, TENURE; reuse CONTENT, LEADERSHIP, EVENT. One signal vocabulary, gated by subject; person_signals already mirrors company_signals.
Deterministic composite
ICP fit x type weight x source reputation x recency decay, ~30d half-life. Source reputation matters: an official appointment beats the same claim on a scraped blog.
Staging-first, mirror Sonar
People to workflow_run_people; each signal tagged with workflow_run_person_id; the person_signals join is built on promote-to-CRM. Mirrors the Sonar staging lifecycle, keeps CRM clean.