| list_meetingsA | List interviews for the authenticated HR user. Optional filters:
- role_id, candidate_id — narrow to a specific role / candidate
- status — one of: Scheduled, Completed, Cancelled, Expired
- scheduled_after — ISO datetime or YYYY-MM-DD; only meetings
scheduled on/after this point. Use for "last week" / "this
month" queries (compute the cutoff client-side).
Returns paginated meeting records with totals for completed and
cancelled. The 'role_name' field on each row is the easiest way to
surface 'recent roles' when the user hasn't named one yet.
Rows are summaries: transcripts and full report payloads are
stripped to keep responses small — use get_meeting / get_report
for one meeting's full detail.
|
| list_rolesA | List the authenticated HR user's roles (job postings / openings). Use this whenever the user hasn't named a role and you need to ask
"which role should I schedule against?" — surface 2-3 recent ones
from the response by 'name' / 'position'. Also use for queries like
"what roles do I have" or "what openings am I hiring for".
Returns: {"data": [{roleId, name, position, experience, skills,
evaluationFocus, interviewType, jobDescription, language, ...}]}.
|
| get_meetingA | Full details of a single meeting including transcript and feedback. meeting_id accepts the UUID or the integer primary key.
|
| get_reportA | Scored interview report: analysisSummary, strengths, weaknesses,
recommendations, averagePercentage, nextSteps. Returns status=processing if the report hasn't generated yet.
|
| get_pipelineA | Pipeline view (invited / scheduled / completed / no-show) for a role. Use to answer 'how is the hiring going for this role'. |
| schedule_interviewA | Schedule AI-driven interviews. Creates meetings and sends invite emails. ASK BEFORE CALLING — DO NOT PICK A ROLE ON YOUR OWN:
- A role is MANDATORY and now ENFORCED: if you pass neither role_id
nor a position, this tool schedules NOTHING and returns
{"status": "role_required", "existingRoles": [...]}. When you get
that, ask the user which role to use, suggesting the returned
existingRoles, and only call again once they pick one.
- If the user did not name a role AND did not give enough info to
create a new one (position + JD/skills), STOP and ask first.
- Wrong role = wrong invite goes out. Treat role selection as
mandatory clarifying input; never default.
DRY RUN FIRST IF YOU'RE AUTO-FILLING ANYTHING:
- If you are supplying any field the user did not explicitly state —
interview_type, position, job_description, evaluation_focus,
duration, language, required_skills, OR candidates extracted from
a paste/CV — CALL WITH dry_run=True FIRST.
- The response returns the normalized candidates + the role payload
that WOULD be created or used, including the evaluation focus
split. Persists nothing, sends no email, consumes no quota.
- Read it back to the user in chat as a brief "here's what I'd send"
summary (4–6 bullet lines covering candidates, role, interview
type, focus split, JD if auto-generated). Get explicit go-ahead.
- THEN call again with dry_run=False to actually send invites.
- Only skip dry_run when EVERY field came from the user verbatim
(e.g. they named the role, the interview type, and pasted clean
structured candidates with no extraction).
EVALUATION FOCUS HANDLING:
- If user didn't specify, leave evaluation_focus out — backend
reuses the existing role's stored split, or applies an
interview-type default for new roles (screening:
role-fit/comms/experience/motivation; technical:
depth/problem-solving/comms/system-design; hr:
comms/culture/motivation/leadership).
- The dry-run response surfaces what the resolved split will be so
the user can override before invites go out.
CANDIDATE FIELDS:
- Each candidate dict requires 'firstName' and 'email'. Optional:
'lastName', 'phoneNumber', 'experience', 'summary'.
- Email must be well-formed; phone numbers auto-normalise to 10
digits (Indian format).
TIMING — IMPORTANT:
- This tool sends an invite EMAIL. The candidate opens the link
when they're ready; THEY pick the moment to start the interview.
You cannot pin an interview to a clock time via this tool.
- If a user says "schedule Priya for 3pm Tuesday" — explain that
AIIA invites are candidate-self-served, then offer to send the
invite now (so Priya has it in her inbox) or note the desired
window in the candidate summary so HR can chase if she hasn't
joined by then.
OTHER:
- interview_type: 'screening', 'technical', or 'hr'.
- duration: seconds per interview (default 900 = 15 min).
- language: 'en', 'hi', or 'ar'.
- If role_id is provided, position/jobDescription are ignored.
Returns per-candidate status. Some rows may succeed while others
fail (invalid email, quota exhausted, duplicate within role). Never
assume the whole batch succeeded.
|
| cancel_interviewA | Cancel a scheduled interview. Destructive — meeting and candidate
rows are deleted. ASK BEFORE CALLING if the user said "that interview" / "Priya's"
/ anything ambiguous and you don't have a specific meeting_id from
a prior list_meetings result. Call list_meetings first to surface
candidates, then confirm with the user which meeting they mean.
Cancelling the wrong meeting cannot be undone in-band — the
candidate has to be rescheduled from scratch.
|
| reschedule_interviewA | Reschedule a meeting. Emails the candidate a new invite link. ASK BEFORE CALLING if the meeting isn't unambiguously identified
by the user's message. Same rule as cancel_interview: a
rescheduled-wrong-meeting message is awkward to clean up.
|
| send_remindersA | Send reminder emails for one or more pending interviews. Skips
already-completed meetings. Pass the same idempotency_key on retry to
avoid duplicate sends. ASK BEFORE CALLING if the user said "remind everyone" / "the
no-shows" / anything that resolves to >1 meeting and you don't
have an explicit list. Call list_meetings (filter by status) first,
show the user the meetings you'd remind, and confirm before sending.
Reminder emails to the wrong candidates are an embarrassing leak.
Cap: 100 meeting_ids per call. For more, batch across multiple calls.
|
| reassign_candidateA | Move a candidate from their current role to a different one. Existing
meetings cascade to the new role. Records the reassignment in
reassignmentAuditLogs with the optional reason. ASK BEFORE CALLING if either candidate_id or to_role_id is not
unambiguous from the user's message. Reassignment affects all the
candidate's open meetings — confirm before moving.
|
| generate_questionsA | Generate interview questions using AI. Read-only — does not create a
role or schedule anything. Use to preview what the bot will ask before
committing to schedule_interview. interview_type: 'screening' or 'technical'.
language: 'en', 'hi', 'ar'.
|
| parse_cvsA | Extract candidate identity (name, email, phone) from CV/resume files
(PDF or DOCX, up to 20 per call). Runs OCR fallback for image-based PDFs.
Returns deduplicated candidate rows ready to pass to schedule_interview. Each entry in 'files' must have:
- 'name': filename including extension
- 'bytes_base64': base64-encoded file contents
If role_id is provided, dedups against candidates already attached to
that role. Files exceeding 20 are rejected — batch across multiple calls.
After parsing, ALWAYS show the user the extracted name/email/phone for
each file before scheduling, then call schedule_interview with the
returned 'candidates' list.
|
| create_screening_roleA | Create a Screening — a role to rank CVs against a JD before spending
any interview credit. Returns {"roleId": , ...}; keep that roleId
for the rest of the flow. THE SCREENING FLOW (call these tools in order):
1. create_screening_role ← you are here
2. parse_cvs(files=...) — extract candidates from resume files
3. add_screening_candidates(role_id, candidates=<parse_cvs rows>)
4. run_screening(role_id) — starts async CV-vs-JD scoring
5. get_screening_results(role_id) — poll until scored, read the ranking
6. schedule_screened_candidates(role_id, candidate_ids) — invite the good ones
FIELDS:
- name: the screening / role title (required, e.g. "Senior Backend Engineer").
- job_description: paste the full JD text if you have it — it drives the
score most. Optional but strongly recommended.
- must_have_skills vs nice_to_have_skills: skills the role weighs heavily
vs would-be-nice. Neither AUTO-REJECTS anyone — they shape the LLM
score and the matched/related/missing breakdown.
- experience: free text, e.g. "5+ years".
- location + work_mode: work_mode is 'remote', 'onsite', or 'hybrid'.
Location is a soft signal only (a remote role ignores it); nobody is
dropped for being in the wrong city.
- additional_requirements: free text the model folds into scoring, e.g.
"valid work visa", "bachelor's degree", "AWS certified".
- language: 'en', 'hi', or 'ar'. company_name: optional.
This creates a role record (screening_enabled). If the user already has a
screening for this opening, call list_screening_roles first and reuse its
roleId instead of making a duplicate.
|
| list_screening_rolesA | List the authenticated user's screenings (CV-screening roles). Use this to find an existing screening's roleId (so you don't create a
duplicate), or to report progress. Each row carries:
{roleId, name, position, location, workMode, candidateCount,
screenedCount, topMatches (score >= 60), scheduledCount, createdAt}.
screenedCount == candidateCount means scoring has finished for that
screening (the role-level done-signal for run_screening). topMatches is
the count worth scheduling.
|
| add_screening_candidatesA | Attach parsed CVs to a screening as candidates, ready to be scored. Pass the rows returned by parse_cvs straight through — each
{"identity": {...}, "details": {...}} row is reshaped automatically into
what the backend needs. Already-flat dicts ({firstName, lastName, email,
summary, parsedResumeData, number, location}) also work.
Email is NOT required here — screening scores a CV, it doesn't message
anyone. CVs with no email are kept and given an unroutable placeholder so
they still rank; the response's 'withoutEmail' count tells you how many.
Those rows CANNOT be scheduled later until a real email is added — surface
that number to the user.
Returns {savedCount, skippedEmpty (CVs with no readable content),
withoutEmail, candidates}. After this, call run_screening(role_id).
|
| run_screeningA | Start CV-vs-JD scoring for a screening's candidates. ASYNC: this
returns immediately with {"status": "queued"} and NO scores — the LLM
scoring runs in the background. To read results, poll get_screening_results(role_id) a few seconds later
(and again until candidates leave the 'not_screened' status). Do not
expect scores in this tool's response.
- Scoring is idempotent: candidates whose CV+JD are unchanged since the
last run are skipped. Pass force=True to re-score everything (e.g.
after you have not changed the JD but want a fresh pass).
- limit: optionally cap how many candidates get scored this run.
- Uses a cheap model; safe to run on a large roster.
|
| get_screening_resultsA | Read a screening's ranked candidates (the poll for run_screening). Returns {roleId, count, role:{name, mustHaveSkills, niceToHaveSkills,
location, workMode, additionalRequirements}, candidates:[...]}, candidates
sorted best-first by screeningScore. Each candidate:
{id, name, email, hasEmail, phone, position, experienceYears, location,
locationFit ('remote'|'in_region'|'other'|null), screeningScore (0-100),
screeningVerdict ('strong'|'possible'|'weak'|'reject'), screeningStatus,
reason, matchedSkills, relatedSkills, missingSkills, status, screenedAt}.
DONE-SIGNALS (scoring is async): a candidate is finished when
screeningStatus is 'scored', 'hard_filtered', 'overridden', or 'error',
and still pending at 'not_screened'. The whole screening is done when
every row has left 'not_screened' (or screenedCount == candidateCount in
list_screening_roles). If rows are still 'not_screened', wait and call again.
To schedule, take the 'id' of each candidate you want and pass them to
schedule_screened_candidates. Skip rows where hasEmail is false — they
need a real email first.
Results are capped at `limit` (default 100, top by score) and each
'reason' is truncated to keep the response small; raise limit if you
need more rows.
|
| schedule_screened_candidatesA | Schedule AI interviews for chosen candidates of a screening. Sends a
real invite EMAIL to each and consumes interview quota. CONFIRM BEFORE CALLING:
- Read back to the user which candidates (by name) you're about to invite
and get explicit go-ahead. This spends money and emails real people —
inviting the wrong candidates is not undoable in-band.
- Pull candidate_ids from get_screening_results 'id' fields. Only include
rows with hasEmail=true; rows with a placeholder email are REJECTED
(the whole call 400s) until a real email is added.
The interview reuses the screening role's stored config (JD, questions,
evaluation focus) — same as the web 'Schedule these N' action. The
candidate self-serves the interview when they open the link; you cannot
pin a clock time. duration is seconds per interview (default 900 = 15 min).
Returns {scheduledCount, scheduled_candidates:[{candidateId,
candidate_email, status ('scheduled'|'needs_email'|'skipped_existing'|
'failed'), meetingId, meetingUrl, reason}]}. Some rows may succeed while
others fail or are skipped (already scheduled) — never assume the whole
batch went out.
|