DEV Community

4 Files Into One Human-Reviewed Interview Brief

4 Files, One Interview Brief: The Bot That Preps the Human Your hiring manager opens the CV in one tab, hunts through Gmail for the application thread in another, digs up the role scorecard in a shared drive, and joins the call three minutes late still not sure what to ask. That is 15 minutes of tab-switching before a 30-minute interview. This post walks through the exact bot I build for clients that turns those four scattered inputs into a single evidence-linked briefing packet - without letting the model touch a hiring decision. The one rule that keeps this safe: the bot preps humans, it does not judge them This bot prepares the interviewer. It does not score, rank, reject, advance, or message the candidate. That boundary is the entire reason the workflow is worth building first. Every automation vendor demo I have seen starts at the riskiest step - resume scoring - which is exactly where bias, EEOC exposure, and bad candidate experience live. Flip the order. Automate the boring information-gathering that your hiring manager is doing badly at 8:57 AM, and leave the judgment where it belongs. Practically, that means the bot has three hard "no" rules baked into the system prompt and the workflow logic itself: - No numeric fit score, no "recommend hire / no hire" language, no ranked comparison across candidates. - No candidate-facing output. The bot writes to the interviewer only. - No stage changes in the ATS. If you use Greenhouse, Ashby, or a spreadsheet, the bot reads. A human writes. If you skip this framing, the same code becomes a compliance problem the moment a rejected candidate asks how the decision was made. The 4 inputs, and the boring identifier problem that breaks most builds The four inputs are: calendar event, candidate CV, application form response, and role scorecard. The one people skip is the scorecard, and without it the model produces a fluent summary that has nothing to do with what you are hiring for. Keep the scorecard to five fields so a hiring manager will actually maintain it: | Field | Example (customer support role) | |---|---| | Role title | Senior Support Specialist, SaaS | | Must-have | Fluent written English, 2+ yrs ticketing (Zendesk/Intercom), B2B SaaS exposure | | Nice-to-have | SQL basics, technical writing samples | | Concerns to verify | US timezone overlap, availability start date, examples of angry-customer handling | | Interview areas | Written comms sample, live triage exercise, escalation judgment | The second boring problem: one clean identifier per candidate. Email is the only reliable one. Names collide, LinkedIn URLs get edited, phone numbers are missing half the time. If your calendar event only has a name, matching becomes a guessing game the moment you interview two people named David Chen in the same quarter. The fix is a title convention, not code: Interview - Jane Doe - ja******@gmail.com - Support Specialist Or put the email in the event description field. Not glamorous. But every reliable automation I have shipped starts with a boring, consistent input format. The trigger, the extractor, and the "do not guess" rule The trigger is a scheduled job that runs every 10 minutes and looks for calendar events with an Interview label starting in the next 30-60 minutes. When it finds one, it pulls: candidate email, candidate name, interviewer name, role title, event start time, meeting link. If any critical field is missing, the bot does not guess. It sends the interviewer a short exception notice and stops. # pseudo-code, n8n / Make / Python all map to this shape def handle_event(event): ctx = extract_calendar_fields(event) missing = [k for k in ("candidate_email", "role_title", "interviewer") if not ctx.get(k)] if missing: notify_interviewer( to=ctx.get("interviewer_email"), subject=f"Briefing skipped: {event['summary']}", body=f"Missing fields: {', '.join(missing)}. " f"Fix the event and I will regenerate on next run.\n{event['htmlLink']}" ) return build_briefing(ctx) Silent guessing is how automations create embarrassing failures - the wrong candidate summary sent to the wrong interviewer, or a briefing about "David Chen" that mixes two people's CVs. An exception notice takes 20 seconds for a human to fix. A hallucinated brief takes a week of trust to rebuild. Fields the trigger must always confirm before proceeding - candidate_email - the join key for every downstream lookup - role_title - required to load the correct scorecard - interviewer - required to send the brief to the right person - event_start - required to schedule delivery 30 min before Gmail search, CV extraction, and keeping the payload small Once the trigger has the candidate email, the bot searches Gmail with a narrow query. Broad searches ("everything from this person, ever") are how you end up with a 40,000-token context window full of scheduling back-and-forth. from:ja******@gmail.com OR to:ja******@gmail.com label:Applications OR label:Recruiting OR label:Interviews newer_than:120d has:attachment OR subject:(application OR interview OR resume OR cv) Then apply three filters: - Newest CV wins. If the candidate sent three versions, use the most recent. List older files as source links, do not blend them into the extracted text. - Extract with page references. For PDFs, keep page_number metadata alongside every text chunk.pdfplumber orpypdf both work. This is what lets the final brief cite "CV page 2" instead of a vague summary. - Pull only the fields the interviewer needs from the application form. Work history, key open-ended answers, portfolio links, stated availability. Skip the "how did you hear about us" field. import pdfplumber def extract_cv(path): pages = [] with pdfplumber.open(path) as pdf: for i, page in enumerate(pdf.pages, start=1): text = page.extract_text() or "" pages.append({"page": i, "text": text.strip()}) return pages # keep structure, do not join into one blob The payload going into the model should be small and labeled. For a typical candidate this is roughly: | Source | Size | |---|---| | Calendar event | ~200 tokens | | CV (extracted, page-labeled) | 1,500-3,000 tokens | | Application form (filtered) | 400-800 tokens | | Selected email threads (max 3) | 500-1,500 tokens | | Role scorecard | 200-400 tokens | | Total input | ~3,000-6,000 tokens | At current Claude Sonnet or GPT-4-class pricing, one briefing runs about $0.03-$0.08. Dumping every email would push that past $0.50 per brief and would make the citations unverifiable. The prompt: labels, citations, and no opinions Every input chunk gets a source label before it hits the model. This is the single change that separates a useful brief from an AI-generated opinion. [SOURCE: cv_page_2] "Led a support team of 4 at Acme SaaS, 2022-2024..." [SOURCE: application_q3_dated_2026-03-04] "Available to start within 3 weeks..." [SOURCE: email_thread_2026-03-06] "Confirmed she is US Pacific time..." [SOURCE: scorecard_v1.3_must_have] "US timezone overlap required" The system prompt then enforces the boundary: You are preparing a hiring manager for an interview. You do NOT evaluate the candidate. You do NOT recommend hire/no-hire. You do NOT produce a score. For every claim you make, cite the source label in brackets, e.g. [cv_page_2]. If a scorecard requirement has no matching evidence in the sources, say "no evidence found in provided sources" - do not infer, do not fill in from general knowledge. Output sections (in this order): 1. Snapshot (3 bullets, facts only) 2. Scorecard coverage (each must-have + nice-to-have, with evidence or "no evidence found") 3. Concerns to verify (from scorecard, phrased as questions) 4. Suggested questions (3-5, grounded in gaps or claims worth probing) 5. Source index (list of files/links used) That "no evidence found" clause is the anti-hallucination lever. Without it, the model will happily write "Jane has strong SQL skills" because SQL is on the scorecard, even though nothing in her CV mentions it. Delivery, review, and what a good brief looks like at 8:30 AM The brief lands in the interviewer's inbox (or Slack DM) 30 minutes before the call, with the meeting link at the top and the source index at the bottom. Every claim has a bracketed citation the interviewer can click. A real one looks roughly like this: INTERVIEW BRIEFING - Jane Doe - Senior Support Specialist Interview: 2026-04-15 09:00 PT with Marcus | [Join call] SNAPSHOT โ€ข 2 yrs support lead at Acme SaaS (B2B) [cv_page_2] โ€ข Available in 3 weeks, US Pacific [application_q3, email_2026-03-06] โ€ข Portfolio: writing samples + one incident postmortem [application_q7] SCORECARD COVERAGE โœ“ Ticketing 2+ yrs - Zendesk at Acme [cv_page_2] โœ“ B2B SaaS exposure - Acme is B2B [cv_page_1] โœ“ US timezone - confirmed Pacific [email_2026-03-06] ? Angry-customer example - no evidence found in provided sources CONCERNS TO VERIFY โ€ข Ask for a specific escalation she handled end-to-end โ€ข Confirm start date given current notice period SUGGESTED QUESTIONS 1. Walk me through the incident in your postmortem - what did you own? 2. Tell me about a ticket you escalated. What was the trigger? [...] The interviewer skims for 3 minutes, spots the "no evidence found" gap, and knows exactly what to probe. That is the whole product. What breaks in the first two weeks of running this - Calendar events without the email in title or description - fix the convention, do not patch the code. - Gmail label drift ("Recruiting-2026" vs "Recruiting") - normalize labels once, monthly. - CVs sent as image-only PDFs - add an OCR fallback (Tesseract or a vision model) and flag the brief as "OCR used, verify accuracy." - Interviewer forwards the brief to the candidate by accident - put a visible INTERNAL - DO NOT FORWARD header on every brief. Why bizflowai.io helps with this This is exactly the class of workflow I ship for small teams at bizflowai.io - the boring, high-leverage pre-work that a human still owns the decision on. For hiring specifically, that means the calendar-trigge

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.