name: evaluate-a-candidate
description: "Score a candidate against an open role - paste a resume or share a LinkedIn URL and I give you a rubric score, the evidence behind it, red flags, and what to probe in interviews. Branches on source: resume or linkedin."
version: 1
category: People
featured: yes
image: busts-in-silhouette
integrations: [googlesheets, googledrive, linkedin, firecrawl]
Evaluate a Candidate
Two paths, same output: one candidate file per applicant, scored against role rubric. Pick source based on what in front of you.
When to use
source=resume- "screen this resume", "screen resume stack for {role}", "rank these resumes", "who strongest on pile". Single and batch both supported.source=linkedin- "score {LinkedIn URL}", "is candidate fit for {role}", "rate this profile", "0-100 on this LinkedIn". One per invocation. Batch = run many times.
Both chain into prep-an-interviewer and debrief-an-interview-loop, which expect candidates/{slug}.md exist.
Connections I need
I run external work through Composio. Before this skill runs I check that the categories below are linked. Missing → I name the category, ask you to connect it from the Integrations tab, stop.
- Files (Google Drive) — pick up resume PDFs you drop. Required when source is resume.
- Web scrape (Firecrawl) — read public LinkedIn or profile URLs. Required when source is linkedin.
- Sheets (Google Sheets, Airtable) — write back ranked stack if you want one. Optional.
- ATS (Ashby, Greenhouse, Lever, Workable) — dedupe and write back candidate state. Optional.
If none of the required categories are connected I stop and ask you to connect the one that matches your source.
Information I need
I read your people context first. For every required field that's missing I ask ONE plain-language question (best modality: connected app > file drop > URL > paste) and wait.
- Role rubric — Required. Why I need it: I score every candidate against your must-haves. If missing I ask: "What role and level is this candidate for, and what are your top three must-haves?"
- Resume or profile — Required. Why I need it: I have nothing to evaluate without it. If missing I ask: "Drop the resume in your connected drive folder, or paste the LinkedIn URL."
- Candidate name — Optional. Why I need it: clean filing under the right slug. If you don't have it I pull it from the resume or profile and keep going.
Parameter: source
resume- parses resume PDF(s) from connected Google Drive / Dropbox (or pasted files) via Composio docs tool. Batch-capable: N resumes → each own record AND ranked summary. Output band: pass / borderline / fail.linkedin- scrapes LinkedIn or public-profile URL via Composio web-scrape tool (Firecrawl). Output: 0-100 total + 4-6 sub-scores (level-fit, domain-fit, scope, tenure, culture-signal) with profile evidence cited per sub-score.
Steps
Read ledger, fill gaps with ONE targeted question.
Read
context/people-context.md. Missing or empty → tell you: "I need people context first - run set-up-my-people-info skill." Stop. Pull leveling framework for target level.Read req. Open
reqs/{role-slug}.mdfor criteria rubric. Missing → ask ONE targeted question ("What role? Top 3 must-haves?") and writereqs/{role-slug}.md.Branch on
source:If
source = resume:- Locate resumes. Attached or folder connected → run
composio search docsto discover docs tool slug (Google Drive / Dropbox) and list PDFs. Paths pasted → use those. Neither → ask ONE question naming best modality ("Connect Google Drive / Dropbox from Integrations, or paste resume files.") and stop. - Parse each resume. Execute docs slug to extract text. Pull structured fields per candidate: name, contact; education (school, degree, dates); roles (company, title, dates, tenure); skills (stated + inferred from role descriptions); notable projects / publications. Mark ambiguous fields UNKNOWN - never infer.
- Evaluate against rubric. Per candidate, score each criterion pass / borderline / fail with one-line reason citing resume evidence (or "not stated in resume" → UNKNOWN). Overall band. 3-5 red flags (tenure pattern, skill-gap vs must-haves, unexplained gaps). Never flag protected-class attributes.
- Write one record per applicant to
candidates/{candidate-slug}.md(slug = kebab-case{first-last}). File exists → append new dated## Screen {YYYY-MM-DD}section - never overwrite. Per section: Structured fields → Rubric scoring → Overall band → Red flags → Suggested next step (interview / reject with rationale). Atomic write. - More than one resume → build ranked summary table (name → band → one-line reason → candidate path), include in
outputs.jsonsummary text.
- Locate resumes. Attached or folder connected → run
If
source = linkedin:- Parse URL. Accept LinkedIn or any public-profile URL. Derive
{candidate-slug}from URL or stated name (kebab-casefirst-last). - Discover scrape tool:
composio search web-scrape. Nothing connected → tell you which category to link and stop. - Scrape. Execute slug. Extract: current title + company + tenure; prior roles (company, title, dates, tenure); education; skills (stated + inferred from role / headline); recent activity (posts, publications, speaking); geo if stated. Mark ambiguous fields UNKNOWN. Scrape empty or gated → say so, ask for paste of profile summary.
- Score 0-100 against rubric. Break into 4-6 sub-scores (e.g. level-fit, domain-fit, scope-signal, tenure-signal, culture-signal). Each sub-score 0-25 with one-line reason citing profile evidence. Total ≤ 100.
- Produce: background summary (3-5 sentences), total + sub-scores with reasoning, 3-5 red flags to probe in interviews. Never infer protected-class attributes.
- Write to
candidates/{candidate-slug}.md. File exists → append## LinkedIn Score {YYYY-MM-DD}- never overwrite. Doesn't exist → create with header stub then score section. Atomic write.
- Parse URL. Accept LinkedIn or any public-profile URL. Derive
Append to
outputs.jsonwith:{ "id": "<uuid v4>", "type": "candidate-evaluation", "title": "<source> - <candidate name or stack count>", "summary": "<2-3 sentences; for batch: counts by band + top 3>", "path": "candidates/<candidate-slug>.md", "status": "draft", "createdAt": "<ISO>", "updatedAt": "<ISO>", "domain": "hiring" }Batch resume runs → one entry per batch with
path: "candidates/".Summarize. One paragraph.
resume: count screened, bands breakdown, top 3 named with file paths.linkedin: total score, top 2 reasons high/low, top 2 red flags, candidate file path.
Outputs
candidates/{candidate-slug}.mdper applicant (appended; created if missing).- Appends to
outputs.jsonwithtype: "candidate-evaluation",domain: "hiring".
What I never do
- Infer or score protected-class attributes (race, gender, age 40+, pregnancy, disability, religion, national origin, sexual orientation, veteran status). Only objective criteria rubric.
- Invent credentials, references, or claims. Resume / LinkedIn thin or gated → mark UNKNOWN, ask for paste.
- Overwrite prior candidate sections - always append dated sections.
- Commit to hire / no-hire. That call yours; I band and flag.