Developer Handover Pack
Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.
Developer Handover Pack
Job Posting & Applicant Tracking
[YourCompany.com] · HR Department · Prepared by FullSpec · [Today's Date]
This document is the authoritative technical reference for the three-agent automation covering job posting and applicant tracking. It is written for the FullSpec build team and covers the current-state process map, per-agent specifications with IO contracts, the end-to-end data flow, and the recommended build stack. Everything the FullSpec team needs to begin implementation without further discovery is contained here. Where a decision is still open, an explicit note flags what must be confirmed before that build stage begins.
01Process snapshot
02Agent specifications
Listens for a Greenhouse webhook firing when a vacancy is marked as approved. Pulls the vacancy record fields (role title, department, hiring manager, salary band, and stored job description template) from Greenhouse via the Jobs API, passes them to a prompt template stored in the orchestration layer, and generates a complete, on-brand job advertisement. The draft is dispatched to the hiring manager's Gmail inbox as a structured email containing the ad copy and a single approval link. On approval the agent triggers the job board posting workflow. On rejection it loops back with the hiring manager's revision notes attached.
// Input
greenhouse.job.id : string // Greenhouse internal job ID
greenhouse.job.title : string // Role title
greenhouse.job.department : string // Department name
greenhouse.job.description : string // Raw JD template stored in Greenhouse
greenhouse.job.hiring_mgr : string // Hiring manager email address
greenhouse.job.salary_band : string // Salary range if public-facing
// Output (on draft sent)
gmail.message.to : string // hiring manager email
gmail.message.subject : string // '[Action Required] Job Ad Draft: {job.title}'
gmail.message.body : string // Generated ad copy + approval/reject links
gmail.message.sent_at : datetime // ISO 8601 timestamp
// On approval
greenhouse.job.ad_approved : boolean // Flag set to true on hiring manager click
workflow.next_trigger : string // 'post_to_linkedin' event emitted- Greenhouse API access must be confirmed before build begins. The Jobs API endpoint requires a Harvest API key with read scope on jobs and job stages. Confirm the key with the Greenhouse admin before week 1 ends.
- The prompt template must be stored as a named variable in the orchestration layer's credential/config store, not hardcoded. FullSpec will supply an initial version; the HR coordinator must review and approve it against three sample vacancies before the agent goes live.
- The approval link in the Gmail draft must encode the Greenhouse job ID and a signed HMAC token to prevent spoofed approvals. Token TTL should be set to 72 hours to accommodate busy hiring managers.
- If the hiring manager does not click within 72 hours, the agent must send one reminder email and then escalate to Slack with a direct mention of the hiring manager's Slack user ID (confirm the ID mapping before build).
- Revision loop: if the hiring manager clicks 'Request changes', their reply text is captured, appended to the prompt as a correction instruction, and the agent regenerates and re-sends. Maximum two auto-revision loops before flagging for manual intervention.
- Greenhouse webhook endpoint must be registered under Settings > Dev Center > Web Hooks using the orchestration layer's inbound webhook URL. Confirm the secret key is stored in the credential store, not in plain text.
Monitors Greenhouse for newly submitted application records on any active role. On each new application event it reads the full applicant payload (name, email, phone, resume URL, source channel, and applied-at timestamp), writes a structured record to Greenhouse via the Applications API if not already complete, appends a corresponding row to the live Google Sheets hiring tracker, and dispatches a personalised acknowledgement email via Gmail within five minutes of the application being received. The agent includes a dedupe check against the Greenhouse candidate ID to prevent duplicate records on re-submissions or webhook retries.
// Input
greenhouse.application.id : string // Greenhouse application ID (used for dedupe)
greenhouse.application.candidate_id: string // Greenhouse candidate ID
greenhouse.application.job_id : string // Linked job ID
greenhouse.application.first_name : string
greenhouse.application.last_name : string
greenhouse.application.email : string
greenhouse.application.phone : string
greenhouse.application.resume_url : string // Hosted resume link from Greenhouse
greenhouse.application.source : string // e.g. 'LinkedIn', 'Direct'
greenhouse.application.applied_at : datetime // ISO 8601
// Output (Greenhouse write-back)
greenhouse.application.status : string // Set to 'active'
greenhouse.application.tag : string // 'intake_complete'
// Output (Google Sheets append)
sheets.row.job_title : string
sheets.row.candidate_name : string
sheets.row.email : string
sheets.row.phone : string
sheets.row.resume_url : string
sheets.row.source : string
sheets.row.applied_at : datetime
sheets.row.status : string // Default: 'Applied'
// Output (Gmail acknowledgement)
gmail.message.to : string // applicant email
gmail.message.subject : string // 'We received your application for {job_title}'
gmail.message.body : string // Personalised template with estimated response timeline
gmail.message.sent_at : datetime- Dedupe logic: before writing to Greenhouse or Sheets, query Greenhouse for any existing application record matching the candidate_id and job_id pair. If a record already exists, skip the write and log a 'duplicate_skipped' entry to the error log table. Do not send a second acknowledgement email.
- Google Sheets target: confirm the Sheet ID and tab name with the HR coordinator before build. The tab must already exist with header row matching the output field names above. FullSpec will provide the header template.
- Gmail sending account: must use a verified Gmail address authorised under the [YourCompany.com] Google Workspace. The OAuth 2.0 refresh token must be stored in the orchestration layer's credential store with offline access scope (https://www.googleapis.com/auth/gmail.send).
- Acknowledgement email template must be reviewed and approved by the HR coordinator before the agent goes live. It should include the role title, a realistic response-time estimate, and a named HR contact for accessibility queries.
- Greenhouse Applications API rate limit is 50 requests per 10 seconds per key. At 6 to 10 active roles with burst application periods, implement a queue with a 200 ms delay between writes to stay within limits.
- Source channel field mapping: Greenhouse returns source as a nested object. Extract source.public_name for the Sheets row and for reporting purposes.
Triggers when an application record in Greenhouse is tagged 'intake_complete' by the Application Intake Agent, confirming the record is fully populated. Reads the resume content from the Greenhouse-hosted URL and evaluates it against the role's defined screening criteria stored as a custom field set on the Greenhouse job record. Assigns a numeric fit score (0 to 100) and a short reasoning note (maximum 80 words). Writes the score, reasoning, and a candidate tag (shortlist, hold, or reject) back to the Greenhouse application record. After all applications for a role batch are scored, compiles a ranked shortlist and sends a Slack notification to the hiring manager's Slack user ID with candidate names, scores, and direct Greenhouse profile links.
// Input greenhouse.application.id : string // Application to score greenhouse.application.resume_url : string // Hosted resume document greenhouse.job.screening_criteria : string // Custom field: structured criteria stored on job record greenhouse.job.id : string greenhouse.job.title : string greenhouse.job.hiring_mgr_slack_id : string // Slack user ID for notification // Output (Greenhouse write-back per application) greenhouse.application.fit_score : integer // 0 to 100 greenhouse.application.score_note : string // Max 80 words reasoning greenhouse.application.tag : string // 'shortlist' | 'hold' | 'reject' greenhouse.application.scored_at : datetime // ISO 8601 // Output (Slack shortlist notification, sent once per role batch) slack.message.channel : string // Hiring manager Slack user ID (DM) slack.message.text : string // Ranked candidate list with scores and links slack.message.blocks : array // Structured Block Kit payload slack.message.sent_at : datetime
- Screening criteria must be stored as a structured custom field on the Greenhouse job record before this agent can score reliably. Confirm field name and format (plain text or JSON array) with the Greenhouse admin. FullSpec recommends a JSON array of weighted criteria strings for maximum scoring precision.
- Resume parsing: fetch the resume from the Greenhouse-hosted URL, extract plain text content, and pass it to the LLM prompt alongside the criteria. PDF and DOCX formats must both be handled. Use a document parsing utility layer in the orchestration platform.
- Fit score thresholds for tagging: 70 to 100 tags as 'shortlist', 40 to 69 tags as 'hold', 0 to 39 tags as 'reject'. These thresholds must be stored as configurable variables in the credential/config store, not hardcoded, so the HR coordinator can adjust them without a code change.
- Batch trigger for Slack notification: the agent should not fire a Slack message per application. Implement a batching check: after each score write, query Greenhouse for any remaining 'intake_complete' unscored applications on the same job. Only send the Slack notification when zero unscored applications remain for that job, or after a 4-hour window from the first score in the batch.
- Slack Block Kit payload must include: candidate full name, fit score as a bold integer, tag as a coloured emoji indicator (green circle for shortlist, yellow for hold, red for reject), and a direct URL to the Greenhouse application profile. Confirm the Greenhouse application profile URL pattern before build.
- The agent must never auto-reject a candidate without the score and note being written to Greenhouse first. If the LLM call fails, tag the application as 'screening_error' and alert the HR coordinator via Slack rather than defaulting to any tag silently.
03End-to-end data flow
// ============================================================
// TRIGGER: Greenhouse vacancy approved
// ============================================================
EVENT: greenhouse.webhook -> job.approved
payload.job_id : string // e.g. 'gh_job_00412'
payload.job_title : string // e.g. 'Senior Marketing Manager'
payload.department : string // e.g. 'Marketing'
payload.hiring_mgr_email: string // e.g. 'jordan@yourcompany.com'
payload.hiring_mgr_slack: string // e.g. 'U04XKJW29AB'
payload.salary_band : string // e.g. '$70,000 to $85,000'
payload.jd_template : string // Raw JD from Greenhouse custom field
// ============================================================
// AGENT 1: Job Ad Drafting Agent
// ============================================================
AGENT_1_INPUT:
job_id, job_title, department, hiring_mgr_email, salary_band, jd_template
STEP 1.1: LLM prompt with stored ad template + job fields
-> ad_draft.body : string // Generated ad copy
-> ad_draft.subject : string // Email subject line
STEP 1.2: Gmail send to hiring_mgr_email
gmail.to = hiring_mgr_email
gmail.subject = '[Action Required] Job Ad Draft: {job_title}'
gmail.body = ad_draft.body + approval_link + reject_link
approval_link.token = HMAC(job_id + timestamp, secret_key) // TTL 72 hrs
STEP 1.3: Await webhook callback from approval link
IF approved:
greenhouse.job.ad_approved = true
EMIT event: 'post_to_linkedin'
IF rejected:
hiring_mgr.revision_notes -> append to prompt -> regenerate (max 2 loops)
IF timeout (72 hrs):
SEND Gmail reminder -> IF no response after 24 hrs: ALERT Slack @hiring_mgr
STEP 1.4: LinkedIn post (on approval)
linkedin.job_post.title = job_title
linkedin.job_post.body = ad_draft.body
linkedin.job_post.location = greenhouse.job.location
linkedin.response.post_url -> written to greenhouse.job.board_links
// ============================================================
// AGENT 2: Application Intake Agent
// ============================================================
TRIGGER: greenhouse.webhook -> application.created
payload.application_id : string
payload.candidate_id : string
payload.job_id : string
payload.first_name : string
payload.last_name : string
payload.email : string
payload.phone : string
payload.resume_url : string
payload.source : object // -> extract source.public_name
payload.applied_at : datetime
STEP 2.1: Dedupe check
QUERY greenhouse.applications WHERE candidate_id = payload.candidate_id
AND job_id = payload.job_id
IF record exists:
LOG 'duplicate_skipped' -> error_log table
HALT
STEP 2.2: Greenhouse write-back (if new)
greenhouse.application.status = 'active'
greenhouse.application.tag = 'intake_complete'
STEP 2.3: Google Sheets append (Sheets API v4)
sheets.spreadsheet_id = config.SHEETS_HIRING_TRACKER_ID
sheets.range = 'Applications!A:K'
sheets.row = [
job_title, candidate_id, first_name + ' ' + last_name,
email, phone, resume_url, source.public_name,
applied_at, 'Applied', '', ''
]
STEP 2.4: Gmail acknowledgement send
gmail.to = payload.email
gmail.subject = 'We received your application for {job_title}'
gmail.body = template.acknowledgement(first_name, job_title)
gmail.sent_at -> logged to error_log on failure
// ============================================================
// AGENT 3: Resume Screening Agent
// ============================================================
TRIGGER: greenhouse.application.tag == 'intake_complete'
application_id, resume_url, job_id, job_title, hiring_mgr_slack_id
STEP 3.1: Fetch screening criteria
QUERY greenhouse.jobs[job_id].custom_fields.screening_criteria
-> criteria_array : array // JSON array of weighted criteria strings
STEP 3.2: Fetch and parse resume
HTTP GET resume_url -> raw document (PDF | DOCX)
PARSE -> resume_text : string
STEP 3.3: LLM scoring call
INPUT: resume_text + criteria_array
OUTPUT:
fit_score : integer // 0 to 100
score_note : string // max 80 words
tag : string // derived from thresholds in config store
70-100 -> 'shortlist'
40-69 -> 'hold'
0-39 -> 'reject'
STEP 3.4: Greenhouse write-back
greenhouse.application.fit_score = fit_score
greenhouse.application.score_note = score_note
greenhouse.application.tag = tag
greenhouse.application.scored_at = NOW()
STEP 3.5: Batch check
QUERY greenhouse.applications WHERE job_id = job_id
AND tag NOT IN ['shortlist','hold','reject']
IF count == 0 OR elapsed_since_first_score >= 4 hrs:
COMPILE ranked_shortlist (tag='shortlist', sorted by fit_score DESC)
SEND slack.chat.postMessage to hiring_mgr_slack_id
blocks: [candidate_name, fit_score, tag_emoji, greenhouse_profile_url]
ELSE:
WAIT (next application.scored event)04Recommended build stack
More documents for this process
Every document generated for Job Posting & Applicant Tracking.