Ein n8n-Flow, der Greenhouse auf neu eröffnete Reqs überwacht, die früheren Kandidaten findet, die bei einem verwandten Req eine späte Interviewstufe erreicht haben und aus einem nicht-disqualifizierenden Grund abgelehnt wurden — die „Silbermedaillengewinner“ —, jeden einzelnen mit Claude erneut gegen das Rubric des neuen Reqs bewertet und eine gerankte Shortlist in einen Slack-Kanal postet. Er kontaktiert niemanden, fügt nie einen Kandidaten zu einer Pipeline hinzu und bewegt nie einen Kandidaten im ATS. Über jede Kontaktaufnahme entscheidet der Recruiter. Er verwandelt „wir haben letztes Frühjahr jemand anderen eingestellt, wer war noch mal der Zweitplatzierte?“ von einer 40-minütigen archäologischen Grabung in eine Slack-Nachricht, die in der Stunde landet, in der der Req eröffnet wird.
Wann zu verwenden
Sie arbeiten mit Greenhouse (oder einem anderen ATS mit einer Lese-API — die Intake-Nodes lassen sich austauschen), und Sie eröffnen genügend Reqs in wiederkehrenden Job-Familien, sodass die Finalisten des letzten Jahres die Shortlist dieses Jahres sind.
Sie lehnen Finalisten tatsächlich mit strukturierten Ablehnungsgründen ab. Das gesamte Sicherheitsmodell des Flows beruht darauf, „jemand anderen eingestellt“ von „die Background-Prüfung nicht bestanden“ zu unterscheiden. Wenn Ihr Team alle mit einem einzigen generischen Grund ablehnt, beheben Sie das zuerst; der Flow hat nichts, woran er gaten könnte.
Sie haben Feeder-Reqs, auf die Sie zeigen können. Der Flow rät nicht, welche vergangenen Reqs „verwandt“ sind — Sie listen die vergangenen Greenhouse-Job-IDs je Job-Familie in einer Konfigurationsdatei auf. Das macht den Match auditierbar statt zu einer Ähnlichkeits-Blackbox.
Ein Recruiter geht das Digest durch und entscheidet über die Kontaktaufnahme. Der Flow bringt zutage und rankt; ein Mensch screent erneut und kontaktiert.
Wann NICHT zu verwenden
Automatische Kontaktaufnahme im Loop. Der Flow rankt und postet in Slack; er mailt nie, fügt nie zu einer Sequenz hinzu, bewegt nie eine Stufe. Einen Outreach-Versand an das Digest zu verdrahten, verwandelt einen Wiederkontakt-Vorschlag in automatisierte Verarbeitung von Kandidatendaten — und einen Kandidaten nach Ablauf der ihm gegenüber offengelegten Aufbewahrungsfrist erneut zu kontaktieren, ist eine GDPR-Verletzung, kein Growth-Hack. Die Confirm first:-Zeile pro Kandidat im Digest existiert genau deshalb, damit ein Recruiter vor jeder Nachricht Einwilligung und Aktualität prüft.
Kein Aktualitätsfenster. GDPR verlangt, dass Sie Kandidatendaten nicht über die dem Kandidaten mitgeteilte Aufbewahrungsfrist hinaus halten oder erneut verarbeiten — üblicherweise 12–24 Monate für erfolglose Bewerber. Das recency_months-Gate des Flows entfernt jeden, der außerhalb des Fensters liegt. Es länger als Ihre angegebene Aufbewahrungsfrist zu setzen, um den Pool zu erweitern, ist die eine Bearbeitung, die diesen Flow in eine Haftung verwandelt.
Ablehnungsgründe, denen Sie nicht trauen können. Wenn „Position filled“ stillschweigend für „wir hatten Bedenken“ verwendet wird, kann die Deny-List Sie nicht schützen. Der Flow ist nur so sicher wie die Disziplin bei den Ablehnungsgründen, die dahintersteht.
Winzige oder einmalige Einstellungen. Ein Team, das drei unzusammenhängende Reqs pro Jahr eröffnet, ist schneller, wenn es sein eigenes Gedächtnis durchgeht, als ein Rubric und eine Feeder-Req-Liste zu verfassen. Das Setup zahlt sich aus, wenn eine Job-Familie wiederkehrt.
Vertrauliche oder Executive-Suchen. Andere Einwilligungshaltung, andere Audit-Kette. Diese gehören nicht in einen geteilten Slack-Kanal.
Setup
Importieren Sie den Flow. Legen Sie apps/web/public/artifacts/candidate-rediscovery-n8n/candidate-rediscovery-n8n.json in Ihre n8n-Instanz. Jede Node trägt notesInFlow: true, sodass die Notizen auf dem Canvas jede Entscheidung erklären.
Verdrahten Sie die Credentials. Drei: PLACEHOLDER_GREENHOUSE_CRED_ID (Harvest-API-Key, nur Lese-Scope — Jobs, Applications, Scorecards), PLACEHOLDER_ANTHROPIC_CRED_ID (Claude-API-Key), PLACEHOLDER_SLACK_CRED_ID (Slack-Bot-Token mit chat:write für #talent-rediscovery). Die _README.md des Bundles zeigt, wo jeder Wert liegt.
Verfassen Sie eine Konfigurationsdatei pro Job-Familie unter ${CONFIG_DIR}/<family>.json. Sie enthält die match_job_ids (die Feeder-Reqs), min_stage_reached (das Gate für die späte Stufe), die Allow- und Deny-Listen der Ablehnungsgründe, recency_months, fit_threshold, top_n und das Rubric. Das vollständige Format steht in _README.md. Keine Konfiguration für eine Familie → der Flow hält mit missing_config an, statt gegen Defaults zu bewerten.
Setzen Sie den Lookback.POLL_LOOKBACK_HOURS muss ≥ dem Schedule-Intervall sein (Standard 6h), sonst rutscht ein zwischen den Polls eröffneter Req durch. Beide werden zusammen abgestimmt.
Dry-Run auf einer Familie, für die Sie gerade eingestellt haben. Die Zweitplatzierten, an die Sie sich erinnern, sollten nahe der Spitze des Digests landen. Stimmen Sie min_stage_reached und die Rubric-Anker an Ihrem Gedächtnis ab, bevor Sie ihm bei einer frischen Familie vertrauen.
Aktivieren Sie den Trigger. Stellen Sie active: true erst um, nachdem ein Digest gekommen ist, auf das Sie tatsächlich handeln würden.
Was der Flow macht
Zwölf Nodes, in Reihenfolge. Die deterministischen Einwilligungs- und Fairness-Gates laufen vor dem Modell-Aufruf, denn ein LLM auf das vollständige Ablehnungsarchiv loszulassen, ist der Weg, jemanden erneut zu kontaktieren, der Sie gebeten hat, ihn nie zu kontaktieren.
Every 6 Hours — Schedule-Trigger. Greenhouse hat keinen zuverlässigen Job-Created-Webhook, also pollt der Flow.
Fetch New Open Reqs — GET /v1/jobs?status=open&created_after=… gegen Greenhouse Harvest. Das JSON-Array wird in ein Item pro neuem Req aufgeteilt.
Load Match Config — löst die Job-Familie des Reqs auf, lädt deren Konfiguration, hasht sie für das Audit-Log. Hält bei missing_config an.
Config Loaded? — IF-Gate; Reqs ohne Konfiguration stoppen hier.
Fetch Rejected Pool — GET /v1/applications?status=rejected&last_activity_after=…, paginiert. Ein Item pro abgelehnter Application.
Eligibility Filter — der Fünf-Gate-Boden: Feeder-Req-Match, späte Stufe erreicht, Ablehnungsgrund Allow/Deny (Deny gewinnt), Aktualitätsfenster, Do-not-contact-Unterdrückung. Alles andere wird verworfen, bevor irgendein Modell es sieht.
Fetch Scorecards — zieht die früheren Interview-Scorecards des Kandidaten, den Grounding-Text für den Re-Match.
Claude Re-Match — bewertet den früheren Kandidaten gegen das Rubric des neuen Reqs auf Sonnet 4.6, mit der expliziten Anweisung, die alte Ablehnungsentscheidung nicht zu übernehmen und nicht anhand von Proxys für geschützte Merkmale zu bewerten. Evidenz erforderlich: kein wörtliches Scorecard-Zitat → Fit 1.
Parse + Keep — erzwingt die Evidenzregel, markiert Keep, wenn Fit ≥ dem Konfigurations-Threshold ist.
Audit Append — eine pseudonyme JSONL-Zeile pro bewertetem Kandidaten (Kandidaten-ID + Link, kein Name, kein Scorecard-Text).
Build Digest — gruppiert nach Req, dedupliziert einen Kandidaten, der über zwei Feeder-Reqs gematcht hat (höherer Fit gewinnt), rankt, kürzt auf top_n.
Slack Digest — postet eine gerankte Shortlist pro Req in #talent-rediscovery, jeden Kandidaten mit einem einzeiligen Grund zum erneuten Auftauchen und einer Confirm first:-Notiz.
Kostenrealität
Anthropic-API-Token — jeder Kandidat sendet Scorecard-Text + Rubric (~4–5k Input-Token) und liefert ~300 Output-Token zurück. Bei der Sonnet-4.6-Listenpreisgestaltung landet das bei rund $0,015–0,03 pro bewertetem Kandidaten, sodass eine Familie, die 200 berechtigte Silbermedaillengewinner zieht, etwa $3–6 pro eröffnetem Req kostet (aus Token-Zählungen berechnet, nicht an Ihren Daten gemessen).
Greenhouse-Harvest-Aufrufe — nur lesend: ein Jobs-Poll, ein paginierter Applications-Pull, ein Scorecards-Fetch pro berechtigtem Kandidaten. Das bleibt für jede realistische Familiengröße innerhalb des dokumentierten Per-Key-Rate-Limits von Harvest.
n8n-Kosten — selbst gehostet ist im Container kostenlos. Der Starter-Plan von n8n Cloud deckt das Polling-Volumen ab; nur sehr hoher Req-Durchsatz braucht Pro.
Recruiter-Zeit — der Gewinn. Eine Silbermedaillengewinner-Liste über vergangene Reqs hinweg von Hand zu rekonstruieren, dauert den besseren Teil einer Stunde pro Req; das Digest landet gerankt, mit Einwilligungs-Flags und Re-Screen-Prompts vorbereitet, in den Minuten nach Eröffnung des Reqs.
Die Ökonomie hinter dem Gewinn. Veröffentlichte Recruiting-Benchmarks setzen die Cost-per-Hire über $4.500 und die Ersparnis eines wiederentdeckten Hires bei rund $2.000–3.000 an, wobei die Time-to-Fill bei Wiederentdeckungs-Hires um 20–30 Tage sinkt. Teams starten typischerweise bei einer Wiederentdeckungsrate von 5–15 % und zielen innerhalb eines Jahres auf 35–50 %; die Benchmark für die Einstellungsrate von Silbermedaillengewinnern liegt bei rund 8–15 %. Der Flow existiert, um das Erreichen dieser Zahlen zum Default zu machen, nicht zu einem Quartalsprojekt.
Erfolgsmetrik
Verfolgen Sie drei Zahlen pro Job-Familie pro Quartal:
Shortlist-zu-Screen-Rate — Anteil der Digest-Kandidaten, die ein Recruiter zu einem Re-Screen mitnimmt. Unter ~20 % bedeutet, dass das Rubric oder min_stage_reached zu locker ist; ziehen Sie die Anker an, bevor Sie den Pool erweitern.
Wiederentdeckungs-Einstellungsrate — Anteil der Einstellungen in der Familie, die aus dem Digest stammen. Die Benchmark von 8–15 % ist das Ziel; unter 5 % nach zwei Quartalen bedeutet, dass die Feeder-Req-Liste oder das Aktualitätsfenster zu eng ist.
Zeit von Req-Eröffnung bis zum ersten qualifizierten Slate — die Metrik für Candidate Experience und Hiring-Manager. Das Digest sollte das von Tagen auf denselben Tag verschieben.
vs Alternativen
vs Wiederentdeckung durch Gem oder hireEZ — das sind verwaltete Talent-CRM-Produkte mit eigenen Re-Engagement-Kampagnen und einem Candidate-Graph; wählen Sie sie, wenn Sie die Plattform wollen und das Budget es trägt. Wählen Sie den Flow, wenn Sie die Matching-Regeln, die Deny-List und das Audit-Log versionskontrolliert in Ihrem eigenen Repo wollen, auf die von Ihnen gewählten Feeder-Reqs zugeschnitten, mit dem Digest, das in Ihrem Stack landet.
vs Greenhouses eigene „Prospect-Pool“-Suche — die native Suche findet Kandidaten nach Keyword und Stufe, bewertet sie aber nicht erneut gegen das Rubric eines neuen Reqs mit zitierter Evidenz, und das Relevanz-Ranking ist eine Blackbox. Wählen Sie den Flow, wenn die reason_to_resurface- und Confirm first:-Zeilen pro Kandidat das sind, was den Recruiter zum Handeln bringt.
vs einem Recruiter, der das ATS manuell durchforstet — gleiche Qualität an einem guten Tag, aber der Recruiter vergisst das Aktualitätsfenster, überspringt unter Termindruck die Deny-List und macht es nur für die Reqs, an die er sich erinnert. Der Flow macht es für jeden wiederkehrenden Req, jedes Mal, mit nicht-optionalen Einwilligungs-Gates.
Fallstricke
Wiederkontakt über die Aufbewahrungsfrist hinaus.Schutz: das recency_months-Gate entfernt vor der Bewertung jeden, der außerhalb des offengelegten Aufbewahrungsfensters liegt, und das Audit-Log erfasst das verwendete Fenster. Setzen Sie es auf Ihre angegebene Aufbewahrungsfrist oder kürzer — niemals länger, um den Pool zu vergrößern.
Disqualifizierte Kandidaten, die wieder auftauchen.Schutz: die Deny-List der Ablehnungsgründe läuft vor dem Modell, und Deny gewinnt über Allow. Nicht bestandene Background-/Referenzprüfungen, Bedenken zum Verhalten, fehlende Arbeitserlaubnis und explizite Do-not-contact-Gründe können nie das Digest erreichen. Die Disziplin hängt von ehrlichen Ablehnungsgründen vorgelagert ab.
Bias-Übertragung aus alten Entscheidungen.Schutz: das Modell wird angewiesen, das frühere Ablehnungsurteil nicht zu übernehmen — ein Kandidat, der übergangen wurde, weil jemand anderes gewählt wurde, kann für einen neuen Req eine 5 sein — und nicht anhand von Name, Schule als eigenständigem Signal, Alter, Geschlecht oder Beschäftigungslücken zu bewerten. Der config_sha im Audit-Log macht die an jedem beliebigen Datum verwendeten Matching-Regeln unter einem AI-Screening-Bias-Review reproduzierbar.
Veralteter Kandidatenstatus.Schutz: die Confirm first:-Zeile pro Kandidat im Digest zwingt den Recruiter, vor der Kontaktaufnahme zu verifizieren, dass die Person noch in der Region, noch interessiert und noch passend ist; der Flow behauptet einen Match, keine aktuelle Tatsache. Anderswo aktive Kandidaten sind die Prüfung des Recruiters in Greenhouse, vermerkt in den bekannten Grenzen des Bundles.
Dünne Scorecards, die niedrig bewerten.Schutz: der Scorecard-Text ist das einzige Grounding, sodass ein Kandidat, der vor substanziellen Interviews abgelehnt wurde, per Design niedrig bewertet. Heben Sie min_stage_reached an, statt das Modell mit Lebensläufen zu füttern, die es nicht sehen kann.
Stack
Das Artefakt-Bundle liegt unter apps/web/public/artifacts/candidate-rediscovery-n8n/ und enthält:
candidate-rediscovery-n8n.json — der n8n-Flow-Export (jede Node konfiguriert, keine Stub-Parameter)
_README.md — Credential-Setup, Konfigurationsdatei-Format, die Einwilligungs- und Fairness-Gates, die Dry-Run-Prozedur
# Candidate rediscovery (silver medalists) — n8n flow
This flow polls Greenhouse for newly-opened reqs, finds past candidates who reached a late stage on a related req and were rejected for a non-disqualifying reason ("silver medalists"), re-scores each against the new req's rubric with Claude (Sonnet 4.6 by default), and posts a ranked shortlist to Slack. It never contacts a candidate, never adds anyone to a pipeline, and never moves a candidate in Greenhouse. The recruiter decides every outreach.
This README covers import, credentials, the per-job-family config format, the consent and fairness gates, and the dry-run procedure.
## Import
1. Open n8n → Workflows → Import from file → pick `candidate-rediscovery-n8n.json`.
2. Set the workflow timezone (top of the canvas) to your team's working timezone for sane audit-log timestamps. The default is UTC.
3. Do not enable the workflow yet. Configure credentials and at least one job-family config, complete the dry-run, then flip to enabled.
## Credentials (three required)
### `PLACEHOLDER_GREENHOUSE_CRED_ID` — Greenhouse Harvest API key
- Greenhouse admin → Configure → Dev Center → API Credential Management → Create New API Key → type "Harvest". Grant only the read permissions the flow uses: `GET` on Jobs, Applications, and Scorecards. The flow never writes to Greenhouse.
- In n8n, create an HTTP Basic Auth credential. Username = the API token. Password = empty. (Harvest authenticates as base64 of `token:` with a trailing colon — n8n's Basic Auth credential does this for you.)
- Bind the credential to the three Greenhouse nodes: `Fetch New Open Reqs`, `Fetch Rejected Pool`, `Fetch Scorecards`.
### `PLACEHOLDER_ANTHROPIC_CRED_ID` — Anthropic API key
- console.anthropic.com → API Keys → Create Key. Restrict by IP if your n8n is behind a fixed egress.
- In n8n, create a credential of type "Anthropic API". Paste the key.
- Bind to the `Claude Re-Match` node. The model is set to `claude-sonnet-4-6` in the request body — change it there if you want to test other models.
### `PLACEHOLDER_SLACK_CRED_ID` — Slack bot token
- Create (or reuse) a Slack app with the `chat:write` scope. Install to the workspace. Invite the bot into `#talent-rediscovery`.
- In n8n, create a Slack credential with the bot token (`xoxb-…`).
- Bind to the `Slack Digest` node.
### Environment variables
- `CONFIG_DIR` — directory holding the per-job-family config files. Default `/data/rediscovery`.
- `AUDIT_DIR` — directory for the JSONL audit log. Default `/data/audit`.
- `POLL_LOOKBACK_HOURS` — how far back `Fetch New Open Reqs` looks for newly-opened reqs. Must be **≥** the schedule interval (default 6) or a req opened between polls will be missed. Default 6.
## Config file format (one per job family)
The flow expects one config file per job family at `${CONFIG_DIR}/<family>.json`. The family is resolved from the new req's `job_family` custom field, or — if that is absent — the slugified name of the req's first department. Missing config → the flow halts for that req with `missing_config` and leaves the req for manual sourcing.
The config is the only place the matching rules live. Copy this, replace every value, and save as `<family>.json`:
```json
{
"job_family": "backend-engineer",
"version": "2026-06-15",
"match_job_ids": [4012, 3987, 3654],
"recency_months": 18,
"min_stage_reached": ["Onsite", "Final Interview", "Reference Check", "Offer"],
"rejection_reasons_allow": [
"Position filled — strong candidate",
"Hired another candidate",
"Kept warm for future role",
"Timing — not ready to move"
],
"rejection_reasons_deny": [
"Failed background check",
"Not legally authorized to work",
"Conduct / values concern",
"Failed reference check",
"Withdrew — compensation mismatch",
"Do not contact"
],
"do_not_contact_tags": ["do-not-contact", "gdpr-erased", "opted-out"],
"fit_threshold": 4,
"top_n": 10,
"rubric": {
"role": "Senior Backend Engineer (Distributed Systems)",
"dimensions": {
"fit": {
"must_have": [
"Production Go or Rust (3y+)",
"Owned a distributed-system migration"
],
"anchors": {
"5": "Late-stage scorecards show owned, measurable distributed-system outcomes that map to this req's must-haves",
"4": "Strong scorecards on the core skill; one must-have unconfirmed",
"3": "Adjacent skills; would need a fresh screen on the core must-have",
"2": "Partial overlap; likely a stretch for this req",
"1": "No scorecard evidence the candidate matches this req"
}
}
}
}
}
```
- `match_job_ids` are the **feeder reqs** — the past Greenhouse job IDs whose late-stage rejects count as silver medalists for this family. Find them in the URL of each job in Greenhouse. This is what scopes "related req"; the flow does not guess relatedness.
- `min_stage_reached` is the late-stage gate. A candidate rejected at "Application Review" or "Phone Screen" is not a silver medalist — they never got a real read. Use your own stage names exactly as they appear in Greenhouse.
- `rejection_reasons_deny` is the safety floor and **deny wins over allow**. Any disqualifying reason — failed background/reference check, conduct, no work authorization, an explicit do-not-contact — must be listed here so the candidate is never re-surfaced.
- The config is hashed (SHA-256, first 16 hex chars) per run and the hash is written to the audit log and the Slack footer, so the exact rules used on a given date are reproducible.
## Consent and fairness gates (do not weaken to widen the pool)
Two layers protect the candidate, both **before** the LLM call:
1. **`Eligibility Filter`** drops any application that is not a feeder-req match, did not reach a late stage, carries a disqualifying or non-allow-listed rejection reason, falls outside the recency window, or whose candidate carries a do-not-contact / erasure / opt-out tag.
2. **`Claude Re-Match`** is instructed not to inherit the prior reject decision and not to score on protected-class proxies (name, school as a standalone signal, age, gender, employment gaps), and to cite verbatim scorecard evidence — no citation forces fit to 1.
The recency window exists because GDPR requires you not to hold or re-process candidate data beyond the retention period you told the candidate about — commonly 12–24 months for unsuccessful applicants. Set `recency_months` to your stated retention period or shorter; never longer. Candidates past the window are dropped, not re-contacted.
If you find yourself wanting to delete a deny-list reason or stretch the recency window to grow the shortlist, that is exactly the decision a recruiter — not the flow — should make case by case, in Greenhouse, with the candidate's consent status in view.
## Dry-run procedure
1. Author one config file for a family where you recently filled a role and remember the runner-up candidates.
2. Temporarily point `match_job_ids` at the feeder reqs and set the new-req trigger to fire manually: in n8n, click "Execute workflow" with `Fetch New Open Reqs` returning the already-open target req (or pin a sample job item).
3. Read the Slack digest. The runner-ups you remember should appear near the top. If a known strong silver medalist is missing, check, in order: were they within the recency window, did they reach a `min_stage_reached` stage, was their rejection reason allow-listed, do they carry a suppression tag.
4. If obvious mis-fits rank high, the rubric anchors are too loose or the scorecards are thin — look at the `evidence` line in the digest. Empty or paraphrased evidence means the model had little to work with (the candidate was rejected before substantive interviews); raise `min_stage_reached`.
5. Only switch the workflow `active: true` after a digest you would actually act on.
## First-run sanity check
After enabling, watch the first real digest:
1. Confirm the `Confirm first:` line on each candidate is specific (e.g. "still in-region; was a 2024 reject so re-screen on the new framework"). Generic lines mean the model is guessing — check it is on Sonnet 4.6.
2. Confirm the `config <sha>` in the Slack footer matches the file you authored. A mismatch means the wrong family file loaded.
3. Confirm `${AUDIT_DIR}/rediscovery-<YYYY-MM>.jsonl` exists and has one line per scored candidate. No file means you are operating without the audit trail that a GDPR / EEOC inquiry about automated re-contact would require.
## Known limits
- **Active-elsewhere check is the recruiter's, not the flow's.** The pool query returns rejected applications only, so it cannot tell whether a candidate is currently active on another open req. The recruiter sees that in Greenhouse before reaching out; the flow does not auto-suppress active candidates.
- **A candidate who matched via two feeder reqs is scored twice**, then de-duplicated in `Build Digest` (the higher fit wins). The duplicate scoring is a small, bounded token cost, not a correctness problem.
- **Scorecard text is the only grounding.** Greenhouse does not return parsed resume text via Harvest, so a candidate rejected before any substantive interview has thin scorecards and will score low even if their resume is a strong match. That is intended: re-surface people you actually evaluated, not your whole archive.
- **No dedupe table across runs.** If the same req stays open across two polls it will not re-fire (the `created_after` filter only catches newly-opened reqs), but re-opening a req would re-digest it. The audit log makes repeats visible; add a seen-reqs check in front of `Load Match Config` if your audit posture needs hard idempotency.