Skip to content

Recruitment

Nucleus Recruitment is a built-in applicant tracking system (ATS) for managing the full hiring lifecycle — from job creation and candidate sourcing through pipeline management, interviews, offers, and automated onboarding handoff.


  • Job management — create, publish, archive, and republish job listings
  • Candidate pipeline — move candidates through customisable stages (Sourced → Applied → Phone Screen → Interview → Assessment → Offer → Hired)
  • AI scoring — automatically score applicants against job requirements using keyword matching and LLM analysis
  • Communication — email candidates directly from Nucleus with templates and merge fields
  • Interview scheduling — book interviews with Google Calendar integration and self-scheduling links
  • Collaborative evaluation — structured scorecards, independent scoring, team comments
  • Careers page — public job listings page (no auth required) with embedded application forms
  • Reporting — pipeline conversion, time-to-hire, source effectiveness, team productivity
  • Onboarding handoff — hired candidates automatically become employees with onboarding checklists

Recruitment sits between external candidate sourcing and the existing People + Onboarding tools:

External Sources Nucleus Recruitment Nucleus People/Onboarding
───────────────── ─────────────────── ─────────────────────────
LinkedIn Recruiter ─→ Candidate enters pipeline ─→ Hired candidate becomes
Seek / Indeed ─→ Moves through stages ─→ a user with onboarding
Careers page ─→ Interviews & evaluation ─→ template assigned
Employee referrals ─→ Offer → Contract → Hire ─→ Appears in People directory

A job represents an open position. Jobs have:

FieldDescription
TitleThe role being hired for (e.g., “Delivery Manager (Squad)“)
DepartmentTeam or business unit (e.g., “Websites & Campaigns”)
LocationOffice location + workplace type (on-site / hybrid / remote)
Employment typeFull-time, part-time, contract, casual
Salary rangeOptional min/max with currency
DescriptionRich text job description (responsibilities, requirements, benefits)
Pipeline templateWhich hiring stages to use for this job
Hiring teamRecruiter, hiring manager, and interviewers
StatusDraft, Published, On Hold, Closed, Filled
KeywordsSearchable tags for the job (used in AI scoring)

Jobs can be archived and republished — useful for recurring roles.

A candidate is a person in the recruitment system. Candidates exist independently of jobs — one candidate can apply to multiple positions.

FieldDescription
NameFirst and last name
EmailPrimary contact email
PhonePhone number
LocationCity/country
SummaryBrief profile summary
ResumeUploaded file (PDF, DOCX) stored in R2
SourceHow they entered the system (applied, sourced, referral, agency)
TagsFree-form labels for filtering
Talent poolWhether they’ve opted in to be retained for future roles

An application links a candidate to a job. It tracks:

  • Current pipeline stage
  • Status (active, rejected, withdrawn, hired)
  • AI match score
  • Source channel (which job board or link they came through)
  • All stage transitions with timestamps

Each job uses a pipeline template defining the stages candidates move through. The default pipeline:

StageTypeDescription
SourcedEntryAdded by recruiter or via LinkedIn Recruiter
AppliedEntrySubmitted an application (system stage, always exists)
Phone ScreenScreeningInitial phone call — salary expectations, availability, basic fit
InterviewEvaluationFormal interview with hiring manager or panel
AssessmentEvaluationTake-home test, technical challenge, or skills assessment
OfferDecisionOffer extended, negotiation (system stage, always exists)
HiredTerminalOffer accepted, triggers onboarding (system stage, always exists)

Pipeline templates are customisable per department or role type. System stages (Applied, Offer, Hired) cannot be removed.

Rejection is a status within any stage, not a separate stage. When rejecting, record the stage and reason.


Every application receives an AI match score (0–100) based on how well the candidate matches the job requirements.

How scoring works:

  1. Job requirements extraction — when a job is created, requirements are tagged as Required (P3), Preferred (P2), or Optional (P1)
  2. Candidate profile analysis — resume text + profile data is parsed for skills, experience, education
  3. Rule-based matching — weighted scoring:
    • Each required skill matched = +10 points
    • Each preferred skill matched = +5 points
    • Each optional skill matched = +2 points
    • Experience level alignment = +15 points
    • Education match = +10 points
    • Normalised to 0–100
  4. LLM summary (optional) — Claude API generates a plain-English summary of strengths and gaps, similar to Workable’s “How [candidate] matches this job” panel

Display: Score shown as a badge on the candidate card (colour-coded: green 70+, amber 40–69, red 0–39) with an expandable panel showing matched/unmatched criteria.

A public-facing careers page accessible without authentication:

  • Lists all published jobs with search and department/location filters
  • Individual job pages with description, requirements, benefits, and “Apply” button
  • Application form: name, email, phone, resume upload, optional cover letter and summary
  • SEO-optimised with JSON-LD JobPosting structured data for Google Jobs indexing
  • Source tracking via URL parameters (?source=seek, ?source=linkedin)
  • Mobile-responsive design
  • Privacy collection notice and talent pool opt-in checkbox

URL: https://app.nucleus.fast/careers/* (public routes within the app, auth middleware skipped)

Email candidates directly from Nucleus:

  • Templates with merge fields: {{candidate.first_name}}, {{job.title}}, {{interview.date}}, {{company.name}}, etc.
  • Stage-triggered templates: automatic “thank you for applying” on application, customisable templates for each stage transition
  • Communication log: every email sent/received is logged on the candidate profile
  • Bulk actions: send rejection emails to multiple candidates at once
  • Template library: pre-built templates for common scenarios (phone screen invite, interview confirmation, rejection, offer)
  • Google Calendar integration — sync interviewer availability, create calendar events with Google Meet links
  • Self-scheduling links — generate a unique link for the candidate to pick from available time slots
  • Interview types — phone, video, in-person, panel
  • Reminders — automated email reminders 24h and 1h before
  • Interview kits — package sent to interviewers with candidate resume, scorecard criteria, suggested questions
  • Reschedule — candidates can reschedule up to 24h before without recruiter involvement

Structured scorecards for consistent, bias-reduced evaluation:

  • Scorecard templates per pipeline stage with 4–6 role-specific criteria
  • Rating scale — 1 (Strong No) to 5 (Strong Yes) with behavioural anchors
  • Independent submission — interviewers submit scorecards before seeing others’ feedback
  • Locked after submission — prevents editing after viewing others’ scores
  • Debrief view — aggregate scores, areas of agreement/disagreement, overall recommendations
  • Comments — team members can @mention colleagues on candidate profiles

Internal referral portal for employees:

  • Submit a referral (candidate name, email, resume, which job, relationship to referrer)
  • Track referral status: Submitted → Under Review → Interviewing → Hired/Not Selected
  • Referral attribution maintained through the full pipeline
  • Referral dashboard for employees to see their history
  • Reward tracking: eligibility milestones, payment status
  • Candidates by stage per job (funnel visualisation)
  • Stage conversion rates (% passing from each stage to the next)
  • Time in stage (identify bottlenecks)
  • Pipeline velocity (candidates moving per week)

When a candidate reaches the Hired stage:

  1. System creates a users record with candidate’s name, email, job title, start date
  2. onboarding_template_id is set based on the job’s department/role mapping
  3. access_level defaults to employee (admin can adjust)
  4. Candidate record is linked to the new user record
  5. Onboarding module automatically picks them up — checklist appears
  6. Welcome email sent with pre-start information

When an offer is extended:

  • Generate an employment contract from a template (via Contracts tool)
  • Track contract status (sent, viewed, signed) on the candidate profile
  • Offer acceptance can be gated on contract signature
  • Signed contract stored in Documents for the employee record
  • Hired candidates appear in the People directory with their start date
  • “Upcoming starters” view shows hired candidates pre-start-date
  • Recruitment data (source, interview scores, hire date) available on the person profile for reference
  • Interview scheduling creates Google Calendar events
  • Interviewer availability read from Google Calendar free/busy data
  • Self-scheduling links use available slots from connected calendars

Recruitment involves collecting personal information from external candidates. Nucleus must comply with the Privacy Act 1988 (Cth) and the 2024 amendments:

RequirementImplementation
Privacy collection noticeDisplayed on the application form before submission
Purpose limitationOnly collect information reasonably necessary for recruitment
Consent for retentionTalent pool opt-in checkbox (not pre-checked)
Automated decision disclosureAI scoring disclosure on application form (required from Dec 2026)
Right of accessCandidate can request their data via email
Right of correctionCandidate can request corrections
Data securityEncryption at rest (D1) and in transit (HTTPS). Sensitive fields application-encrypted
DataRetentionAction
Active candidatesRetained while application is active
Rejected candidates (no talent pool consent)12 months after rejectionAnonymise or delete
Rejected candidates (talent pool opt-in)Until consent withdrawnProvide opt-out mechanism
Hired candidatesData transitions to employee recordRetained per employment law
Withdrawn candidates6 monthsAnonymise or delete

A scheduled job runs monthly to flag candidate records due for anonymisation based on these rules.

Every consent is recorded with:

  • Type (application, talent pool, marketing)
  • Granted timestamp
  • IP address (audit trail)
  • Exact consent text shown
  • Withdrawn timestamp (if applicable)

Access LevelCan ViewCan UpdateCan Manage
ExecutiveAll jobs, all candidatesFull pipeline managementCreate/archive jobs, manage settings
HeadAll jobs, all candidatesMove candidates, submit evaluationsCreate/archive jobs
ManagerOwn jobs + squad member jobsMove candidates, submit evaluationsCreate jobs for own team
LeadReferral portal onlySubmit referrals
EmployeeReferral portal onlySubmit referrals

Data scoping: Managers see jobs where they are the hiring manager or a member of the hiring team. Leads and employees only see the referral submission portal.


-- Jobs (open positions)
CREATE TABLE IF NOT EXISTS recruitment_jobs (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
department TEXT,
job_code TEXT,
description TEXT NOT NULL, -- rich text (HTML)
requirements TEXT, -- rich text (HTML)
benefits TEXT, -- rich text (HTML)
workplace_type TEXT DEFAULT 'hybrid', -- 'on_site', 'hybrid', 'remote'
location TEXT,
employment_type TEXT DEFAULT 'full_time', -- 'full_time', 'part_time', 'contract', 'casual'
experience_level TEXT, -- 'entry', 'mid', 'senior', 'lead', 'executive'
salary_min REAL,
salary_max REAL,
salary_currency TEXT DEFAULT 'AUD',
keywords TEXT, -- JSON array of searchable tags
pipeline_template_id TEXT REFERENCES recruitment_pipeline_templates(id),
status TEXT NOT NULL DEFAULT 'draft', -- 'draft', 'published', 'paused', 'closed', 'filled'
published_at TEXT,
closed_at TEXT,
created_by TEXT REFERENCES users(id),
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Hiring team members for each job
CREATE TABLE IF NOT EXISTS recruitment_hiring_team (
id TEXT PRIMARY KEY,
job_id TEXT NOT NULL REFERENCES recruitment_jobs(id) ON DELETE CASCADE,
user_id TEXT NOT NULL REFERENCES users(id),
role TEXT NOT NULL, -- 'recruiter', 'hiring_manager', 'interviewer', 'coordinator'
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(job_id, user_id)
);
-- Candidates (people in the recruitment system)
CREATE TABLE IF NOT EXISTS recruitment_candidates (
id TEXT PRIMARY KEY,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT NOT NULL,
phone TEXT,
location TEXT,
summary TEXT, -- brief profile summary
headline TEXT, -- e.g. "Senior Engineer at Acme"
resume_url TEXT, -- R2 file URL
resume_text TEXT, -- extracted plain text from resume
linkedin_url TEXT,
source TEXT DEFAULT 'applied', -- 'applied', 'sourced', 'referral', 'agency', 'careers_page'
tags TEXT, -- JSON array of tags
talent_pool INTEGER DEFAULT 0, -- opted in to talent pool
user_id TEXT REFERENCES users(id), -- set when hired and user record created
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_recruitment_candidates_email
ON recruitment_candidates(email);
-- Applications (candidate × job)
CREATE TABLE IF NOT EXISTS recruitment_applications (
id TEXT PRIMARY KEY,
job_id TEXT NOT NULL REFERENCES recruitment_jobs(id),
candidate_id TEXT NOT NULL REFERENCES recruitment_candidates(id),
current_stage_id TEXT REFERENCES recruitment_pipeline_stages(id),
status TEXT NOT NULL DEFAULT 'active', -- 'active', 'rejected', 'withdrawn', 'hired'
rejection_reason TEXT,
rejection_stage_id TEXT, -- stage at which rejected
source_channel TEXT, -- specific channel: 'seek', 'linkedin', 'indeed', 'careers_page', 'referral'
ai_score INTEGER, -- 0-100 match score
ai_summary TEXT, -- LLM-generated match summary
ai_criteria TEXT, -- JSON: matched/unmatched criteria
applied_at TEXT NOT NULL DEFAULT (datetime('now')),
moved_at TEXT, -- last stage transition
hired_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(job_id, candidate_id)
);
CREATE INDEX IF NOT EXISTS idx_recruitment_applications_job
ON recruitment_applications(job_id);
CREATE INDEX IF NOT EXISTS idx_recruitment_applications_candidate
ON recruitment_applications(candidate_id);
CREATE INDEX IF NOT EXISTS idx_recruitment_applications_status
ON recruitment_applications(status);
-- Pipeline templates (e.g., "Engineering", "Sales", "General")
CREATE TABLE IF NOT EXISTS recruitment_pipeline_templates (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
is_default INTEGER DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Pipeline stages (ordered)
CREATE TABLE IF NOT EXISTS recruitment_pipeline_stages (
id TEXT PRIMARY KEY,
pipeline_template_id TEXT NOT NULL REFERENCES recruitment_pipeline_templates(id) ON DELETE CASCADE,
name TEXT NOT NULL,
stage_type TEXT NOT NULL, -- 'entry', 'screening', 'evaluation', 'decision', 'terminal'
position INTEGER NOT NULL,
is_system INTEGER DEFAULT 0, -- locked stages: Applied, Offer, Hired
color TEXT, -- hex colour for UI
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_recruitment_pipeline_stages_template
ON recruitment_pipeline_stages(pipeline_template_id, position);
-- Stage transition history (audit trail)
CREATE TABLE IF NOT EXISTS recruitment_stage_transitions (
id TEXT PRIMARY KEY,
application_id TEXT NOT NULL REFERENCES recruitment_applications(id) ON DELETE CASCADE,
from_stage_id TEXT,
to_stage_id TEXT NOT NULL REFERENCES recruitment_pipeline_stages(id),
moved_by TEXT REFERENCES users(id),
note TEXT,
moved_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_recruitment_transitions_application
ON recruitment_stage_transitions(application_id);
-- Email templates with merge fields and categories
CREATE TABLE IF NOT EXISTS recruitment_email_templates (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
subject TEXT NOT NULL, -- supports {{merge.fields}}
body TEXT NOT NULL, -- rich text with merge fields
category TEXT NOT NULL DEFAULT 'general', -- 'thank_you', 'interview', 'rejection', 'offer', 'general'
is_active INTEGER NOT NULL DEFAULT 1,
created_by TEXT REFERENCES users(id),
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Stage email rules (auto-send on stage entry)
CREATE TABLE IF NOT EXISTS recruitment_stage_email_rules (
id TEXT PRIMARY KEY,
stage_id TEXT NOT NULL REFERENCES recruitment_pipeline_stages(id) ON DELETE CASCADE,
template_id TEXT NOT NULL REFERENCES recruitment_email_templates(id) ON DELETE CASCADE,
trigger_on TEXT NOT NULL DEFAULT 'enter',
is_active INTEGER NOT NULL DEFAULT 1,
UNIQUE(stage_id, template_id)
);
-- Communication log (all emails sent to candidates)
CREATE TABLE IF NOT EXISTS recruitment_communications (
id TEXT PRIMARY KEY,
candidate_id TEXT NOT NULL REFERENCES recruitment_candidates(id),
application_id TEXT REFERENCES recruitment_applications(id),
template_id TEXT REFERENCES recruitment_email_templates(id),
direction TEXT NOT NULL DEFAULT 'outbound',
channel TEXT NOT NULL DEFAULT 'email',
subject TEXT NOT NULL,
body TEXT NOT NULL,
to_email TEXT NOT NULL,
from_email TEXT NOT NULL,
gmail_message_id TEXT,
status TEXT NOT NULL DEFAULT 'sent', -- 'sent', 'failed'
error TEXT,
sent_by TEXT REFERENCES users(id),
sent_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Interviews with Google Calendar integration and self-scheduling
CREATE TABLE IF NOT EXISTS recruitment_interviews (
id TEXT PRIMARY KEY,
application_id TEXT NOT NULL REFERENCES recruitment_applications(id) ON DELETE CASCADE,
candidate_id TEXT NOT NULL REFERENCES recruitment_candidates(id),
job_id TEXT NOT NULL REFERENCES recruitment_jobs(id),
title TEXT NOT NULL,
description TEXT,
interview_type TEXT NOT NULL DEFAULT 'video_call', -- 'video_call', 'phone', 'in_person', 'take_home'
location TEXT,
scheduled_at TEXT,
duration_minutes INTEGER NOT NULL DEFAULT 60,
timezone TEXT NOT NULL DEFAULT 'Australia/Melbourne',
status TEXT NOT NULL DEFAULT 'scheduled', -- 'scheduled', 'pending_schedule', 'completed', 'cancelled'
scheduling_type TEXT NOT NULL DEFAULT 'manual', -- 'manual', 'self_schedule'
self_schedule_token TEXT UNIQUE,
self_schedule_expires_at TEXT,
self_schedule_slots TEXT, -- JSON array of available slots
google_calendar_event_id TEXT,
reminder_24h_sent INTEGER NOT NULL DEFAULT 0,
reminder_1h_sent INTEGER NOT NULL DEFAULT 0,
scheduled_by TEXT REFERENCES users(id),
completed_at TEXT,
cancelled_at TEXT,
cancelled_by TEXT REFERENCES users(id),
cancel_reason TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Interview participants with feedback and rating
CREATE TABLE IF NOT EXISTS recruitment_interview_participants (
id TEXT PRIMARY KEY,
interview_id TEXT NOT NULL REFERENCES recruitment_interviews(id) ON DELETE CASCADE,
user_id TEXT NOT NULL REFERENCES users(id),
role TEXT NOT NULL DEFAULT 'interviewer',
response_status TEXT DEFAULT 'pending',
feedback TEXT,
rating INTEGER,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(interview_id, user_id)
);
-- Candidate comments (team discussion with @mentions and threading)
CREATE TABLE IF NOT EXISTS recruitment_comments (
id TEXT PRIMARY KEY,
candidate_id TEXT NOT NULL REFERENCES recruitment_candidates(id) ON DELETE CASCADE,
application_id TEXT REFERENCES recruitment_applications(id),
author_id TEXT NOT NULL REFERENCES users(id),
content TEXT NOT NULL,
mentions TEXT, -- JSON array of mentioned user IDs
parent_id TEXT REFERENCES recruitment_comments(id) ON DELETE CASCADE,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Scorecard templates (per stage or job)
CREATE TABLE IF NOT EXISTS recruitment_scorecard_templates (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
pipeline_stage_id TEXT REFERENCES recruitment_pipeline_stages(id) ON DELETE SET NULL,
job_id TEXT REFERENCES recruitment_jobs(id) ON DELETE SET NULL,
is_default INTEGER DEFAULT 0,
created_by TEXT REFERENCES users(id),
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Criteria within a scorecard template
CREATE TABLE IF NOT EXISTS recruitment_scorecard_criteria (
id TEXT PRIMARY KEY,
scorecard_template_id TEXT NOT NULL REFERENCES recruitment_scorecard_templates(id) ON DELETE CASCADE,
name TEXT NOT NULL,
description TEXT,
category TEXT, -- 'technical', 'communication', 'culture', 'leadership'
weight INTEGER NOT NULL DEFAULT 1,
sort_order INTEGER NOT NULL DEFAULT 0,
anchors TEXT, -- JSON: {"1": "Poor", "3": "Meets expectations", "5": "Exceptional"}
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Submitted evaluations (one per evaluator per application per interview)
CREATE TABLE IF NOT EXISTS recruitment_evaluations (
id TEXT PRIMARY KEY,
application_id TEXT NOT NULL REFERENCES recruitment_applications(id) ON DELETE CASCADE,
evaluator_id TEXT NOT NULL REFERENCES users(id),
scorecard_template_id TEXT REFERENCES recruitment_scorecard_templates(id),
interview_id TEXT REFERENCES recruitment_interviews(id) ON DELETE SET NULL,
recommendation TEXT, -- 'strong_yes', 'yes', 'no_decision', 'no', 'strong_no'
notes TEXT,
overall_score REAL, -- computed weighted average
is_submitted INTEGER DEFAULT 0,
submitted_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(application_id, evaluator_id, interview_id)
);
-- Individual criterion scores
CREATE TABLE IF NOT EXISTS recruitment_evaluation_scores (
id TEXT PRIMARY KEY,
evaluation_id TEXT NOT NULL REFERENCES recruitment_evaluations(id) ON DELETE CASCADE,
criteria_id TEXT NOT NULL REFERENCES recruitment_scorecard_criteria(id) ON DELETE CASCADE,
score INTEGER, -- 1-5
evidence TEXT, -- text justification
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Employee referrals
CREATE TABLE IF NOT EXISTS recruitment_referrals (
id TEXT PRIMARY KEY,
referrer_user_id TEXT NOT NULL REFERENCES users(id),
candidate_id TEXT NOT NULL REFERENCES recruitment_candidates(id),
job_id TEXT REFERENCES recruitment_jobs(id), -- NULL if general referral
relationship TEXT, -- 'former_colleague', 'friend', 'professional_contact'
note TEXT, -- referrer's endorsement
status TEXT DEFAULT 'submitted', -- 'submitted', 'reviewing', 'hired', 'rejected'
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Candidate consent tracking (privacy compliance)
CREATE TABLE IF NOT EXISTS recruitment_consents (
id TEXT PRIMARY KEY,
candidate_id TEXT NOT NULL REFERENCES recruitment_candidates(id) ON DELETE CASCADE,
consent_type TEXT NOT NULL, -- 'application', 'talent_pool', 'marketing'
consent_text TEXT NOT NULL, -- exact text shown
granted_at TEXT NOT NULL DEFAULT (datetime('now')),
expires_at TEXT,
withdrawn_at TEXT,
ip_address TEXT
);
-- Activity log (audit trail for all actions)
CREATE TABLE IF NOT EXISTS recruitment_activities (
id TEXT PRIMARY KEY,
application_id TEXT REFERENCES recruitment_applications(id),
candidate_id TEXT REFERENCES recruitment_candidates(id),
job_id TEXT REFERENCES recruitment_jobs(id),
actor_id TEXT REFERENCES users(id),
action TEXT NOT NULL, -- 'applied', 'stage_changed', 'email_sent', 'note_added',
-- 'evaluation_submitted', 'interview_scheduled', 'rejected',
-- 'offer_extended', 'hired', 'referral_submitted'
details TEXT, -- JSON with action-specific data
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_recruitment_activities_application
ON recruitment_activities(application_id);
CREATE INDEX IF NOT EXISTS idx_recruitment_activities_candidate
ON recruitment_activities(candidate_id);
CREATE INDEX IF NOT EXISTS idx_recruitment_activities_created
ON recruitment_activities(created_at);
-- Job requirements (extracted from description, used for AI scoring)
CREATE TABLE IF NOT EXISTS recruitment_job_requirements (
id TEXT PRIMARY KEY,
job_id TEXT NOT NULL REFERENCES recruitment_jobs(id) ON DELETE CASCADE,
category TEXT NOT NULL, -- 'required', 'preferred', 'optional'
description TEXT NOT NULL, -- "Experience in enterprise project management"
priority INTEGER NOT NULL DEFAULT 2, -- 1-5 (5 = most important)
weight REAL NOT NULL DEFAULT 1.0, -- multiplier for scoring (0.1 - 5.0)
position INTEGER NOT NULL
);

The AI scoring system combines rule-based keyword matching with LLM-powered analysis:

  1. Resume text extraction — PDFs uploaded to R2 are parsed via unpdf to extract text
  2. Rule-based scoring — keywords from job requirements are matched against resume text, weighted by category (required ×3, preferred ×2, optional ×1), priority (1-5), and custom weight
  3. AI match summary — Workers AI (@cf/moonshotai/kimi-k2.5) generates a structured analysis with per-requirement match decisions and confidence levels
  4. Score storageai_score (0-100), ai_summary (text), ai_criteria (JSON array), and ai_scored_at on the application record

The scoring engine is in worker/lib/ai-scoring.ts and follows the same AI binding pattern as worker/routes/people.ts (binding + REST API fallback for local dev).


MethodPathDescriptionPermission
GET/api/recruitment/jobsList all jobs (filtered by status, department)view
GET/api/recruitment/jobs/:idGet job details with pipeline stages and teamview
POST/api/recruitment/jobsCreate a new jobmanage
PUT/api/recruitment/jobs/:idUpdate job detailsupdate
PUT/api/recruitment/jobs/:id/statusChange job status (publish, close, archive)manage
GET/api/recruitment/jobs/:id/candidatesList candidates for a job by stageview
GET/api/recruitment/jobs/:id/statsPipeline stats for a jobview
MethodPathDescriptionPermission
GET/api/recruitment/candidatesSearch candidates (name, email, tags, stage)view
GET/api/recruitment/candidates/:idFull candidate profile with all applicationsview
POST/api/recruitment/candidatesCreate candidate (manual add or sourced)update
PUT/api/recruitment/candidates/:idUpdate candidate detailsupdate
DELETE/api/recruitment/candidates/:idAnonymise candidate (privacy compliance)manage
MethodPathDescriptionPermission
POST/api/recruitment/applicationsCreate application (candidate applies to job)update
PUT/api/recruitment/applications/:id/stageMove candidate to a new stageupdate
PUT/api/recruitment/applications/:id/rejectReject candidate with reasonupdate
PUT/api/recruitment/applications/:id/hireMark as hired (triggers onboarding)manage
GET/api/recruitment/applications/:id/timelineFull activity timelineview
MethodPathDescriptionPermission
GET/api/recruitment/email-templatesList all email templatesview
GET/api/recruitment/email-templates/:idGet template by IDview
POST/api/recruitment/email-templatesCreate templatemanage
PUT/api/recruitment/email-templates/:idUpdate templatemanage
DELETE/api/recruitment/email-templates/:idDelete templatemanage
POST/api/recruitment/email-templates/:id/previewPreview with merge fields resolvedview
MethodPathDescriptionPermission
POST/api/recruitment/candidates/:id/send-emailSend email to candidateupdate
GET/api/recruitment/candidates/:id/communicationsGet communication logview
POST/api/recruitment/bulk-emailSend email to multiple candidatesmanage
MethodPathDescriptionPermission
GET/api/recruitment/pipeline-templates/:id/email-rulesGet rules for a pipelineview
PUT/api/recruitment/pipeline-templates/:id/email-rulesUpdate rules for a pipelinemanage
MethodPathDescriptionPermission
GET/api/recruitment/interviewsList interviews (filterable by status)view
GET/api/recruitment/interviews/:idGet interview details with participantsview
POST/api/recruitment/interviewsSchedule an interview (creates Calendar event)update
PUT/api/recruitment/interviews/:idUpdate interview detailsupdate
PUT/api/recruitment/interviews/:id/cancelCancel interview (deletes Calendar event)update
PUT/api/recruitment/interviews/:id/completeMark interview as completedupdate
PUT/api/recruitment/interviews/:id/participants/:userId/feedbackSubmit participant feedback and ratingupdate
MethodPathDescriptionPermission
GET/api/recruitment/candidates/:id/commentsList comments for a candidateview
POST/api/recruitment/candidates/:id/commentsAdd a comment (with @mentions, threading)update
PUT/api/recruitment/comments/:idUpdate a comment (author only)update
DELETE/api/recruitment/comments/:idDelete a commentmanage
MethodPathDescriptionPermission
GET/api/recruitment/scorecard-templatesList all scorecard templatesview
GET/api/recruitment/scorecard-templates/:idGet template with criteriaview
POST/api/recruitment/scorecard-templatesCreate scorecard templatemanage
PUT/api/recruitment/scorecard-templates/:idUpdate template and criteriamanage
DELETE/api/recruitment/scorecard-templates/:idDelete templatemanage
MethodPathDescriptionPermission
GET/api/recruitment/applications/:id/evaluationsList evaluations for application (independent scoring)view
GET/api/recruitment/evaluations/:idGet evaluation with criterion scoresview
POST/api/recruitment/applications/:id/evaluationsCreate new evaluation (draft)update
PUT/api/recruitment/evaluations/:idUpdate draft evaluationupdate
PUT/api/recruitment/evaluations/:id/submitSubmit evaluation (validates, computes score, locks)update
DELETE/api/recruitment/evaluations/:idDelete draft evaluation (own only)update
GET/api/recruitment/applications/:id/debriefAggregate debrief summaryview
MethodPathDescription
GET/api/careers/jobsList published jobs
GET/api/careers/jobs/:idGet job details for public view
POST/api/careers/applySubmit application (with file upload)
GET/api/careers/schedule/:tokenGet self-scheduling interview details and available slots
POST/api/careers/schedule/:tokenConfirm a self-scheduled interview time
MethodPathDescriptionPermission
POST/api/recruitment/referralsSubmit a referralview (any user)
GET/api/recruitment/referrals/mineGet my referral historyview (any user)
GET/api/recruitment/referralsList all referralsmanage
MethodPathDescriptionPermission
GET/api/recruitment/reports/pipelinePipeline funnel dataview
GET/api/recruitment/reports/time-to-hireTime metricsview
GET/api/recruitment/reports/sourcesSource effectivenessview
GET/api/recruitment/reports/teamTeam productivitymanage

RouteDescriptionPhase
/recruitmentJobs list (default view) — all open positions with pipeline stage counts1
/recruitment/jobs/$jobIdJob detail — candidate pipeline view (list by stage)1
/recruitment/jobs/$jobId/editEdit job details1
/recruitment/candidatesCandidate database — search and filter across all candidates1
/recruitment/candidates/$candidateIdCandidate profile — applications, timeline, communications, comments1
/recruitment/templatesEmail template management (create, edit, delete)2
/recruitment/interviewsInterview list with status tabs (Upcoming, Completed, Cancelled)2
/recruitment/scorecardsScorecard template management (create, edit, delete criteria)3
/careersPublic careers page — published job listings1
/careers/jobs/$jobIdPublic job detail and application form1
/careers/schedule/$tokenPublic self-scheduling page for candidates2
src/components/recruitment/
├── jobs-list.tsx # Jobs table with status badges and pipeline counts
├── job-form.tsx # Create/edit job form (Sheet)
├── job-edit.tsx # Edit job page
├── job-pipeline.tsx # Candidates grouped by pipeline stage
├── candidate-search.tsx # Searchable candidate table
├── candidate-profile.tsx # Full candidate detail view (Timeline, Communications, Comments tabs)
├── add-candidate-drawer.tsx # Add candidate to job (Sheet)
├── email-template-list.tsx # Email template management with category tabs
├── email-template-form.tsx # Create/edit template with merge field toolbar (Sheet)
├── email-composer.tsx # Send email with template selection and preview (Sheet)
├── communication-log.tsx # Chronological email history with expandable body
├── stage-email-rules.tsx # Configure auto-send emails per pipeline stage (Sheet)
├── schedule-interview-sheet.tsx # Schedule interview with participant selection (Sheet)
├── interview-list.tsx # Interview list with status tabs
├── candidate-comments.tsx # Threaded comments with @mentions and replies
├── bulk-email-composer.tsx # Send email to multiple candidates (Sheet)
├── scorecard-template-list.tsx # Scorecard template management page
├── scorecard-template-form.tsx # Create/edit scorecard with criteria editor (Sheet)
├── evaluation-form.tsx # Scorecard submission form with 1-5 scoring (Sheet)
├── evaluation-list.tsx # Evaluations tab on candidate profile
├── evaluation-detail.tsx # Full evaluation view with score breakdown (Sheet)
└── debrief-summary.tsx # Aggregate evaluation debrief with averages (Sheet)

Goal: Replace Workable for basic hiring workflow. Create jobs, receive applications, manage pipeline, hire candidates.

Scope:

  • Database migration: core tables (jobs, candidates, applications, pipeline templates/stages, activities)
  • API routes: jobs CRUD, candidates CRUD, applications CRUD, stage transitions
  • Jobs list page with status badges and pipeline stage counts
  • Job detail page with candidate pipeline view (drag-and-drop Kanban by stage)
  • Candidate profile page with resume viewer and activity timeline
  • Move candidates between stages with notes
  • Reject candidates with reason
  • Default pipeline template with seed data (Sourced → Applied → Phone Screen → Interview → Assessment → Offer → Hired)
  • Hiring team assignment per job
  • Basic candidate search (name, email, job, stage)
  • Source tracking on applications
  • Careers page: public job listings and application form with resume upload (R2)
  • Permission setup: tool_permissions seed data for all access levels
  • Sidebar entry, route titles, header actions
  • Mark as hired: create user record, assign onboarding template, link to People

Phase 2 — Communication & Scheduling ✓

Section titled “Phase 2 — Communication & Scheduling ✓”

Goal: Handle all candidate communication and interview coordination inside Nucleus.

Scope:

  • Email templates with merge fields ({{candidate.first_name}}, {{job.title}}, etc.)
  • Send email to candidate from profile (with template selection)
  • Communication log on candidate profile (expandable email history)
  • Stage-triggered automatic emails (configurable per pipeline stage)
  • Interview scheduling with Google Calendar integration (event creation with attendees)
  • Self-scheduling links for candidates (public token-based page)
  • Interview reminders (cron trigger every 15 min, sends 24h and 1h before)
  • Interview list page with status tabs
  • Bulk email (send to multiple candidates with personalised merge fields)
  • Candidate comments (threaded with @mentions and replies)

Phase 3 — Evaluation & Collaboration ✓

Section titled “Phase 3 — Evaluation & Collaboration ✓”

Goal: Structured, bias-reduced evaluation with scorecards and team collaboration.

Scope:

  • Scorecard templates with criteria (per stage, per job, or global)
  • Scorecard submission by interviewers (1–5 rating per criterion + evidence)
  • Independent scoring (hidden until user submits, then revealed)
  • Debrief summary view (aggregate scores, areas of agreement/disagreement)
  • Interview kits (candidate summary + scorecard sent to interviewers)
  • Overall recommendation per evaluator (Strong No → Strong Yes)
  • Evaluation status tracking on candidate profile Evaluations tab

Phase 4 — AI Scoring, LinkedIn Enrichment & Talent Pool

Section titled “Phase 4 — AI Scoring, LinkedIn Enrichment & Talent Pool”

Goal: AI-powered candidate screening, LinkedIn profile enrichment, and a persistent talent database.

Scope:

  • Job requirements extraction (required/preferred/optional tagging with weight and priority)
  • AI-powered requirements extraction from job descriptions
  • Resume text extraction from uploaded PDFs (via unpdf)
  • Rule-based scoring engine (weighted keyword matching with category multipliers)
  • AI match score displayed on candidate cards and pipeline views
  • LLM-powered match summary via Workers AI (@cf/moonshotai/kimi-k2.5)
  • Criteria breakdown panel (matched/unmatched requirements with confidence levels)
  • Score All button for batch scoring all applications on a job
  • Talent pool: toggle candidates into talent pool for future roles
  • Talent pool search and filtering with resume text search
  • Duplicate candidate detection (match by email and name)
  • Job requirements editor with AI extraction and weight/priority controls
  • LinkedIn profile enrichment via Scrapin.io API
    • Auto-enrich on application submission (when linkedin_url is provided)
    • On-demand “Enrich” button on candidate profile
    • Stores work history, education, skills, certifications on candidate record
    • “LinkedIn” tab on candidate profile showing enriched data
    • Enrichment data fed into AI scoring for richer matching
    • Connection card in Settings > Connections for API key management

Goal: Data-driven recruitment insights.

Scope:

  • Pipeline funnel report (conversion rates per stage)
  • Time-to-hire and time-to-fill metrics
  • Time-in-stage analysis (bottleneck identification)
  • Source effectiveness report (applications vs hires by source)
  • Team productivity dashboard (activities per team member)
  • Offer acceptance rate tracking
  • Cost per hire tracking
  • CSV/PDF export for reporting
  • Dashboard widget: open positions + pipeline summary

Phase 6 — Privacy, Compliance & Referrals

Section titled “Phase 6 — Privacy, Compliance & Referrals”

Goal: Australian Privacy Act compliance and employee referral program.

Scope:

  • Privacy collection notice on application form
  • Consent tracking (application, talent pool, marketing)
  • AI scoring disclosure (required from Dec 2026)
  • Data retention automation (monthly job to flag/anonymise expired records)
  • Candidate data access/deletion request handling
  • Employee referral portal (submit, track status)
  • Referral attribution through pipeline
  • Referral dashboard for employees

Goal: Job board integrations, advanced workflows, and contract automation.

Scope:

  • SEEK API integration (direct job posting)
  • Indeed Apply integration (webhook for applications)
  • LinkedIn Recruiter System Connect
  • SMS communication (Twilio integration)
  • Offer letter generation with e-signature (via Contracts tool)
  • Custom pipeline templates per department
  • Kanban drag-and-drop pipeline view
  • Advanced search: full-text candidate search across resumes
  • Candidate self-service portal (view application status)
  • Interview scheduling with panel availability matching
  • Google Jobs structured data on careers page
  • Automated stage transitions (e.g., auto-advance after scorecard threshold)

How Nucleus Recruitment maps to the Workable features observed in the reference screenshots:

Workable FeatureNucleus EquivalentPhase
Jobs list with pipeline stage counts/recruitment jobs list1
Job editor (title, dept, location, description, salary)Job form with all fields1
Application form builder (mandatory/optional/off)Simplified: fixed form with core fields1
Candidate pipeline view (stages as tabs)Job detail page with stage-grouped list1
Candidate profile (summary, resume, contact)Candidate profile page1
AI Screening Assistant (score + criteria)AI score badge + criteria breakdown4
Communication panel (send email, templates)Email composer with templates2
Activity stream (global feed)Activity timeline per candidate1
Candidate database searchCandidate search page1
Premium Job Boards (LinkedIn, Seek, Indeed)SEEK integration (syndicates to LinkedIn) — see SEEK Integration7
Reports (team productivity)Reporting dashboard5
Onboarding triggerAuto-create user + assign onboarding template1
Work calendar (interviews)Google Calendar integration2
Candidate tags & filtersTags + search/filter UI1
”Find Candidates” (AI sourcing)Talent pool search4
Texting candidatesSMS via Twilio7

DecisionChoiceRationale
Careers page hostingPublic route within the app (/careers/*)Single deployment, shared API, public routes skip auth middleware
Resume storageShared R2 bucket with /recruitment/ prefixSimpler config, one bucket to manage, existing infrastructure
Email sendingReuse announcements email infrastructureAlready working, less config, shared sending setup
Calendar providerGoogle Calendar onlyGoogle Workspace is the company standard, no Outlook needed
Contract integrationManual (recruiter clicks “Create Contract”)More flexible, avoids premature drafts, cleaner workflow
Job board distributionVia SEEK, which syndicates to LinkedInSEEK’s syndication is simpler than maintaining two separate API integrations

Nucleus integrates with the SEEK Job Posting API to distribute job ads to SEEK (and optionally LinkedIn via SEEK’s syndication feature).

  1. Go to Settings → Connections
  2. Find the SEEK connection card
  3. Enter your SEEK API Access Token and Advertiser ID
  4. Toggle LinkedIn Syndication on/off as needed
  5. Click Save
EventAction
Job transitions to publishedPosts a new ad to SEEK (applies URL set to /careers/{jobId})
Published job fields updatedPatches the existing SEEK ad with updated content
Job transitions to paused, closed, or filledExpires/closes the SEEK ad

The apply URL on all SEEK and LinkedIn listings points to Nucleus’s hosted careers page (/careers/{jobId}), so all applications are tracked centrally.

On the job detail page, a SEEK badge appears in the header when the job has an active SEEK ad. If there’s a sync error (e.g. invalid credentials), a SEEK Error badge appears instead with the error message in its tooltip.

  • API Access Token — obtain from the SEEK developer portal under your hirer account
  • Advertiser ID — your SEEK advertiser/hirer ID (e.g. seekAnzPublicTest:advertiser:12345)

Credentials are encrypted at rest using AES-GCM before being stored in the database.