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
- The engine is vendored, not reimplemented.
vendor/teammatch/scorer.tsis 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. - The recruiter's decision is an n8n state transition. After scoring, the pipeline pauses on a
Waitnode. 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. - 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. - 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.
- 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.
| 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
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- Open http://localhost:8200, pick
ml_researcher, click Submit application. Watch it movereceived → parsed → scored → in_reviewas the pipeline runs (LLM profile extraction takes a few seconds). - 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.
- Pick a role and click Advance — the toast confirms the paused n8n workflow resumed; the outcome lands in the audit log.
- Submit
malformed(no CV) → it lands in Rejects via the dead-letter branch. Re-submit any persona → deduped, nothing double-processed. - Calibration tab → Run calibration now → the report scores the engine against your decisions.
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.
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 -> outcomescripts/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+ |
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)