Skip to content

Repository files navigation

TalentFlow — Recruiter Copilot on the TeamMatch Engine

The hiring-side companion to TeamMatch: external candidates flow through an n8n-orchestrated funnel — intake, LLM profile extraction, cold-start match scoring, recruiter review gates, and a weekly calibration loop — scored by the identical engine code that powers TeamMatch's internal mobility.

 application ──► n8n: intake ──► extract-profile ──► match-review ─────────────┐
 (webhook)        validate,       Groq LLM → catalog-  score via vendored      │
                  dead-letter,    constrained profile  TeamMatch scorer        ▼
                  dedupe          (heuristic fallback) (cold-start)      ┌───────────┐
                                                                         │ Wait node │
       weekly-calibration (Mon 08:00 + on-demand):                       │ recruiter │
       funnel stats · engine↔recruiter agreement ·                       │ decides   │
       threshold pressure · catalog gaps                                 │ in the UI │
                                                                         └─────┬─────┘
              TalentFlow app (Node 24, zero npm deps) ◄────── outcome logged ──┘
              candidates · matches · review tasks · audit log · lineage panel

Why this is interesting

  1. The engine is vendored, not reimplemented. vendor/teammatch/scorer.ts is the untouched TeamMatch hybrid scorer (skill match + collaborative signal + Big Five + level/role fit + segment routing), run server-side under Node 24's native TypeScript type-stripping. External candidates are cold-start by construction — empty history and no MF row exercise exactly the fallback paths the engine was designed with.
  2. The recruiter's decision is an n8n state transition. After scoring, the pipeline pauses on a Wait node. Advance / Reject in the UI POSTs to the execution's resume URL; the workflow wakes, logs the funnel outcome, and the 48-hour SLA timeout auto-escalates undecided reviews.
  3. The extraction is catalog-constrained and honestly degraded. The Groq extraction prompt is built from the live /api/catalog (TeamMatch's 20-skill vocabulary — the scorer is exact-match, so a free-text skill would silently score zero). Off-catalog skills are dropped and recorded; with no API key the pipeline falls back to a deterministic keyword heuristic instead of stalling.
  4. The calibration loop closes the funnel. A scheduled workflow compares recruiter behavior against the engine's strong-match line: advanced-but-weak candidates (recall pressure), rejected-but-strong (over-confidence), and the dropped-skill tally (catalog gaps) — the raw material for re-ranking.
  5. Lineage as UI. Every pipeline callback carries workflow + $execution.id; the candidate page renders each stage with a deep link into the n8n execution view.

Workflows

Workflow Trigger Demonstrates
talentflow/intake-candidate Webhook talentflow-intake Boundary validation → dead-letter, idempotent dedupe on email, sub-workflow composition
talentflow/extract-profile Called by intake Catalog-constrained LLM extraction (JSON mode), deterministic heuristic fallback, retry + continue-on-fail
talentflow/match-review Called by intake Server-side engine invocation, LLM fit summary (template fallback), Wait-node review gate with 48h SLA escalation, outcome logging
talentflow/weekly-calibration Schedule Mon 08:00 + on-demand webhook Multi-trigger pattern, funnel analytics, engine↔recruiter agreement report

Specs with failure branches and test cases: docs/workflows.md · Ontology: docs/ontology.md

Quick start

Prereqs: Node ≥ 24 (no npm install needed — zero dependencies) and Docker.

# 1. configure FIRST — docker compose reads .env to inject TALENTFLOW_API_TOKEN
#    and GROQ_API_KEY into the container. Starting n8n before .env exists gives
#    the container the placeholder token, and n8n->app callbacks then 401.
cp .env.example .env

# 2. n8n (image pinned to the validated version)
docker compose up -d
#    open http://localhost:5678, finish owner setup, create an API key
#    (Settings -> n8n API), paste into .env as N8N_API_KEY, then re-inject:
#    docker compose up -d --force-recreate

# 3. workflows + app
node scripts/bootstrap-n8n.mjs   # import + activate the 4 workflows (idempotent)
node server/main.ts              # http://localhost:8200 (75 open roles seeded)

# 4. prove the whole loop works (optional)
node scripts/e2e.mjs             # intake -> extraction -> scoring -> review -> resume

Demo in 60 seconds

  1. Open http://localhost:8200, pick ml_researcher, click Submit application. Watch it move received → parsed → scored → in_review as the pipeline runs (LLM profile extraction takes a few seconds).
  2. Open the candidate: extracted profile (L8 Staff, AI Platform), five scored role matches with per-signal breakdowns, a generated fit summary, and the pipeline lineage linking every stage to its n8n execution.
  3. Pick a role and click Advance — the toast confirms the paused n8n workflow resumed; the outcome lands in the audit log.
  4. Submit malformed (no CV) → it lands in Rejects via the dead-letter branch. Re-submit any persona → deduped, nothing double-processed.
  5. Calibration tab → Run calibration now → the report scores the engine against your decisions.

Env

GROQ_API_KEY enables LLM extraction + fit summaries (deterministic fallbacks otherwise). Keys live in .env / container env only — never in workflow JSON. See .env.example.

Reproducing everything from zero

Written for the case where the local checkout and all Docker state are gone.

# 0. (optional) wipe every trace of this project's Docker state
docker compose down -v                  # container + named volume
docker rmi n8nio/n8n:2.32.7             # the pinned image

# 1. the code
git clone https://github.com/jadzoghaib/talentflow.git
cd talentflow

# 2. secrets FIRST — compose bakes these into the container at create time.
#    Starting n8n before .env exists gives it the placeholder token, and every
#    n8n->app callback then 401s.
cp .env.example .env
#    set TALENTFLOW_API_TOKEN (any shared secret); GROQ_API_KEY optional

# 3. n8n (image pinned; docker pulls it automatically)
docker compose up -d
#    http://localhost:5678 -> owner setup -> Settings -> n8n API -> create key
#    -> paste into .env as N8N_API_KEY
docker compose up -d --force-recreate   # re-inject the edited .env

# 4. workflows + app  (no npm install — zero dependencies)
node scripts/bootstrap-n8n.mjs          # imports + activates the 4 workflows
node server/main.ts                     # http://localhost:8200

# 5. prove the whole loop end to end
node scripts/e2e.mjs
#    intake -> extraction -> scoring -> review gate -> resume -> outcome

scripts/e2e.mjs is the real check: it drives a candidate through the live pipeline and asserts the paused n8n execution actually resumes. If it passes, the rebuild is correct.

Symptom Cause Fix
n8n→app calls 401 container holds the placeholder token (.env written after up) docker compose up -d --force-recreate
access to env vars denied in expressions N8N_BLOCK_ENV_ACCESS_IN_NODE not false already set in docker-compose.yml; recreate the container
n8n can't reach the app on Windows localhost resolves to IPv6 in the container compose uses host.docker.internal; leave TALENTFLOW_BASE_URL alone
Parent workflow won't activate sub-workflows must be published first re-run bootstrap-n8n.mjs (leaves before parents)
node server/main.ts fails to start Node < 24 (needs node:sqlite + TS type-stripping) upgrade to Node 24+

Stack

Node 24 (node:sqlite, native TS type-stripping, zero npm dependencies) · n8n (self-hosted) · Groq Llama-3.3-70b · vendored TeamMatch engine (see vendor/teammatch/NOTICE.md)

About

Recruiter copilot on the TeamMatch engine: n8n-orchestrated hiring funnel with LLM profile extraction, cold-start match scoring, recruiter review gates that resume paused workflows, and weekly calibration. Zero-dependency Node 24 + 4 n8n workflows.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages