Um flow do n8n que monitora o Greenhouse em busca de reqs recém-abertas, encontra os candidatos do passado que chegaram a um estágio avançado de entrevista em uma req relacionada e foram rejeitados por um motivo não desqualificante — os “silver medalists” — re-pontua cada um contra a rubrica da nova req com o Claude e publica uma shortlist ranqueada em um único canal do Slack. Ele nunca entra em contato com ninguém, nunca adiciona um candidato a um pipeline e nunca move um candidato no ATS. O recrutador decide cada abordagem. Ele transforma “contratamos outra pessoa na primavera passada, quem foi o segundo colocado mesmo?” de uma escavação arqueológica de 40 minutos em uma mensagem no Slack que chega na hora em que a req abre.
Quando usar
Você roda no Greenhouse (ou outro ATS com uma API de leitura — os nodes de intake são trocáveis) e abre reqs suficientes em famílias de vaga recorrentes para que os finalistas do ano passado sejam a shortlist deste ano.
Você de fato rejeita finalistas com motivos de rejeição estruturados. Todo o modelo de segurança do flow se apoia em distinguir “contratamos outra pessoa” de “não passou na verificação de antecedentes”. Se sua equipe rejeita todo mundo com um único motivo genérico, corrija isso primeiro; o flow não tem em que se basear para filtrar.
Você tem feeder reqs para apontar. O flow não adivinha quais reqs do passado são “relacionadas” — você lista os IDs de vaga do Greenhouse do passado por família de vaga em um arquivo de config. Isso torna o match auditável em vez de uma caixa-preta de similaridade.
Um recrutador percorre o digest e decide a abordagem. O flow expõe e ranqueia; um humano faz a re-triagem e o contato.
Quando NÃO usar
Auto-abordagem no loop. O flow ranqueia e publica no Slack; ele nunca envia e-mail, nunca adiciona a uma sequência, nunca move um estágio. Conectar um envio de abordagem ao digest transforma uma sugestão de re-contato em processamento automatizado de dados de candidatos — e re-contatar um candidato depois do período de retenção que você divulgou a ele é uma violação de GDPR, não um growth hack. A linha Confirm first: por candidato no digest existe justamente para que um recrutador verifique consentimento e atualidade antes de qualquer mensagem.
Sem janela de recência. O GDPR exige que você não retenha nem reprocesse dados de candidatos além do período de retenção que você informou ao candidato — comumente 12–24 meses para candidatos não selecionados. O gate recency_months do flow descarta qualquer um fora da janela. Defini-lo maior que o seu período de retenção declarado para ampliar o pool é a única edição que transforma este flow em um passivo.
Motivos de rejeição em que você não pode confiar. Se “Position filled” é usado silenciosamente para “tivemos preocupações”, a deny-list não consegue proteger você. O flow é tão seguro quanto a disciplina de motivos de rejeição por trás dele.
Contratação pequena ou pontual. Uma equipe que abre três reqs não relacionadas por ano é mais rápida lendo a própria memória do que escrevendo uma rubrica e uma lista de feeder reqs. O setup se paga quando uma família de vaga se repete.
Buscas confidenciais ou executivas. Postura de consentimento diferente, cadeia de auditoria diferente. Essas não pertencem a um canal compartilhado do Slack.
Setup
Importe o flow. Solte apps/web/public/artifacts/candidate-rediscovery-n8n/candidate-rediscovery-n8n.json na sua instância do n8n. Todo node carrega notesInFlow: true, então as notas no canvas explicam cada escolha.
Conecte as credenciais. Três: PLACEHOLDER_GREENHOUSE_CRED_ID (chave da Harvest API, escopo de leitura apenas — Jobs, Applications, Scorecards), PLACEHOLDER_ANTHROPIC_CRED_ID (chave da API do Claude), PLACEHOLDER_SLACK_CRED_ID (token de bot do Slack com chat:write para #talent-rediscovery). O _README.md do bundle mostra onde cada valor fica.
Escreva um arquivo de config por família de vaga em ${CONFIG_DIR}/<family>.json. Ele contém os match_job_ids (as feeder reqs), min_stage_reached (o gate de estágio avançado), as allow-lists e deny-lists de motivos de rejeição, recency_months, fit_threshold, top_n e a rubrica. O formato completo está no _README.md. Sem config para uma família → o flow para com missing_config em vez de pontuar contra defaults.
Defina o lookback.POLL_LOOKBACK_HOURS precisa ser ≥ ao intervalo do schedule (default 6h) ou uma req aberta entre os polls escapa. Os dois são ajustados em conjunto.
Faça um dry-run em uma família para a qual você acabou de contratar. Os segundos colocados de que você se lembra devem aparecer perto do topo do digest. Ajuste min_stage_reached e as âncoras da rubrica contra a sua memória antes de confiar nele em uma família nova.
Habilite o trigger. Vire active: true apenas depois de um digest sobre o qual você de fato agiria.
O que o flow faz
Doze nodes, em ordem. Os gates determinísticos de consentimento e fairness rodam antes da chamada do modelo, porque soltar um LLM no arquivo completo de rejeições é como você re-contata alguém que pediu para nunca mais ser contatado.
Every 6 Hours — schedule trigger. O Greenhouse não tem um webhook confiável de job-created, então o flow faz polling.
Fetch New Open Reqs — GET /v1/jobs?status=open&created_after=… contra a Greenhouse Harvest. O array JSON é dividido em um item por nova req.
Load Match Config — resolve a família de vaga da req, carrega sua config, faz o hash dela para o audit log. Para em missing_config.
Config Loaded? — gate IF; reqs sem config param aqui.
Fetch Rejected Pool — GET /v1/applications?status=rejected&last_activity_after=…, paginado. Um item por aplicação rejeitada.
Eligibility Filter — o piso de cinco gates: match de feeder-req, estágio avançado alcançado, allow/deny de motivo de rejeição (deny vence), janela de recência, supressão de do-not-contact. Todo o resto é descartado antes que qualquer modelo veja.
Fetch Scorecards — puxa os scorecards de entrevistas anteriores do candidato, o texto de grounding para o re-match.
Claude Re-Match — pontua o candidato do passado contra a rubrica da nova req no Sonnet 4.6, instruído explicitamente a não herdar a antiga decisão de rejeição e a não pontuar com base em proxies de classe protegida. Evidência obrigatória: sem citação literal de scorecard → fit 1.
Parse + Keep — aplica a regra de evidência, sinaliza keep quando o fit ≥ ao threshold da config.
Audit Append — uma linha JSONL pseudônima por candidato pontuado (ID do candidato + link, sem nome, sem texto de scorecard).
Build Digest — agrupa por req, deduplica um candidato que deu match via duas feeder reqs (o fit mais alto vence), ranqueia, trunca para top_n.
Slack Digest — publica uma shortlist ranqueada por req em #talent-rediscovery, cada candidato com um motivo de uma linha para ressurgir e uma nota Confirm first:.
Realidade de custo
Tokens da Anthropic API — cada candidato envia texto de scorecard + rubrica (~4-5k tokens de input) e retorna ~300 tokens de output. No preço de tabela do Sonnet 4.6, isso fica em torno de $0.015-0.03 por candidato pontuado, então uma família que puxa 200 silver medalists elegíveis custa aproximadamente $3-6 por req aberta (calculado a partir de contagens de tokens, não medido nos seus dados).
Chamadas à Greenhouse Harvest — somente leitura: um poll de jobs, um pull paginado de applications, um fetch de scorecards por candidato elegível. Isso fica dentro do rate limit documentado por chave da Harvest para qualquer tamanho realista de família.
Custo do n8n — self-hosted é grátis em container. O plano Starter do n8n Cloud cobre o volume de polling; só uma vazão de reqs muito alta precisa do Pro.
Tempo do recrutador — o ganho. Reconstruir à mão uma lista de silver medalists ao longo de reqs do passado consome boa parte de uma hora por req; o digest chega ranqueado, com flags de consentimento e prompts de re-triagem pré-montados, nos minutos seguintes à abertura da req.
A economia por trás do ganho. Benchmarks publicados de recruiting colocam o custo por contratação acima de $4,500 e a economia de uma contratação por rediscovery em aproximadamente $2,000-3,000, com o time-to-fill em contratações por rediscovery caindo 20-30 dias. As equipes normalmente começam com uma taxa de rediscovery de 5-15% e miram 35-50% em um ano; o benchmark de taxa de contratação de silver medalist fica em torno de 8-15%. O flow existe para tornar bater esses números um default, não um projeto trimestral.
Métrica de sucesso
Acompanhe três números por família de vaga por trimestre:
Taxa de shortlist-para-triagem — fração de candidatos do digest que um recrutador leva a uma re-triagem. Abaixo de ~20% significa que a rubrica ou o min_stage_reached está frouxo demais; aperte as âncoras antes de ampliar o pool.
Taxa de contratação por rediscovery — fração de contratações na família originadas do digest. O benchmark de 8-15% é o alvo; abaixo de 5% depois de dois trimestres significa que a lista de feeder reqs ou a janela de recência está estreita demais.
Tempo da abertura da req ao primeiro slate qualificado — a métrica de experiência do candidato e do hiring manager. O digest deve mover isso de dias para o mesmo dia.
vs alternativas
vs rediscovery do Gem ou hireEZ — esses são produtos gerenciados de talent-CRM com suas próprias campanhas de re-engajamento e um candidate graph; escolha-os se você quer a plataforma e o orçamento suporta. Escolha o flow se você quer as regras de matching, a deny-list e o audit log versionados no seu próprio repo, escopados para feeder reqs que você escolhe, com o digest chegando no seu stack.
vs a busca de “prospect pool” do próprio Greenhouse — a busca nativa encontra candidatos por keyword e estágio, mas não os re-pontua contra a rubrica de uma nova req com evidência citada, e o ranking de relevância é uma caixa-preta. Escolha o flow quando o reason_to_resurface e as linhas Confirm first: por candidato são o que faz o recrutador agir.
vs um recrutador minerando o ATS manualmente — mesma qualidade em um bom dia, mas o recrutador esquece a janela de recência, pula a deny-list sob pressão de prazo e só faz isso para as reqs de que se lembra. O flow faz isso para toda req recorrente, toda vez, com os gates de consentimento não opcionais.
Pontos de atenção
Re-contatar além da retenção.Proteção: o gate recency_months descarta qualquer um fora da janela de retenção divulgada antes de pontuar, e o audit log registra a janela usada. Defina-o como o seu período de retenção declarado ou menor — nunca maior para crescer o pool.
Candidatos desqualificados ressurgindo.Proteção: a deny-list de motivos de rejeição roda antes do modelo e deny vence allow. Verificações de antecedentes/referências reprovadas, preocupações de conduta, ausência de autorização de trabalho e motivos explícitos de do-not-contact nunca podem chegar ao digest. A disciplina depende de motivos de rejeição honestos a montante.
Carregamento de viés de decisões antigas.Proteção: o modelo é instruído a não herdar o veredito de rejeição anterior — um candidato preterido porque outra pessoa foi escolhida pode ser um 5 para uma nova req — e a não pontuar com base em nome, escola como sinal isolado, idade, gênero ou lacunas de emprego. O config_sha no audit log torna as regras de matching usadas em qualquer data reproduzíveis sob uma revisão de AI-screening-bias.
Estado desatualizado do candidato.Proteção: a linha Confirm first: por candidato no digest força o recrutador a verificar se a pessoa ainda está na região, ainda interessada e ainda é um fit antes da abordagem; o flow afirma um match, não um fato atual. Candidatos ativos em outro lugar são a verificação do recrutador no Greenhouse, anotada nos known limits do bundle.
Scorecards rasos pontuando baixo.Proteção: o texto do scorecard é o único grounding, então um candidato rejeitado antes de entrevistas substantivas pontua baixo por design. Aumente min_stage_reached em vez de alimentar o modelo com currículos que ele não consegue ver.
Stack
O bundle do artifact fica em apps/web/public/artifacts/candidate-rediscovery-n8n/ e contém:
candidate-rediscovery-n8n.json — o export do flow do n8n (todo node configurado, sem parâmetros stub)
_README.md — setup de credenciais, formato do arquivo de config, os gates de consentimento e fairness, o procedimento de dry-run
# 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.