Back to Performance Review Cycle

Developer Handover Pack

Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.

4 pagesPDF · Technical
FS-DOC-04Technical

Developer Handover Pack

Performance Review Cycle

[YourCompany.com] · HR Department · Prepared by FullSpec · [Today's Date]

This document is the primary technical reference for the FullSpec team building and configuring the Performance Review Cycle automation. It covers the current-state process map with bottleneck identification, full agent specifications with IO definitions, the end-to-end data flow trace, and the recommended build stack. Everything needed to configure, connect, and test all three agents is contained here. Where credentials, API tiers, or form structures are still to be confirmed, those items are flagged as pre-build blockers inside the relevant agent specification.

01Process snapshot

Step
Name
Description
1
Export Active Employee List
HR exports the current headcount from BambooHR, removes contractors and leavers, and confirms the final review population with department heads. Time cost: 45 min/cycle.
2
Send Review Kick-Off Notifications
HR drafts and sends individual or batched emails to every employee via Gmail with self-assessment form links and the submission deadline. Time cost: 60 min/cycle.
3
Send Manager Briefing Emails
HR sends a separate briefing email to every people manager explaining the timeline, responsibilities, and manager form links for each direct report. Time cost: 50 min/cycle.
4
Track Submission Status in Spreadsheet
HR manually updates a Google Sheets tracker each day, logging which employees and managers have submitted and which are outstanding. BOTTLENECK. Time cost: 120 min/cycle day, repeated daily across the 4-week window.
5
Send Manual Chase Reminders
HR identifies non-submitters from the tracker and sends individual reminder emails or Slack messages, often repeating two or three times per person. BOTTLENECK. Time cost: 90 min/cycle day, repeated on multiple days.
6
Collect and Collate Form Responses
HR downloads Google Forms responses, organises them by department and manager, and pastes relevant employee responses into a shared folder structure. Time cost: 100 min/cycle.
7
Draft Manager Summary Documents
HR or each manager manually writes a one-page summary per direct report, pulling together self-assessment, manager ratings, and peer feedback. BOTTLENECK. Time cost: 180 min/cycle (scales linearly with headcount).
8
Distribute Summaries to Managers
HR emails completed summary documents to each manager ahead of calibration, confirming meeting date and outstanding items. Time cost: 40 min/cycle.
9
Conduct Calibration Review Meeting
HR and senior management hold calibration meetings to align ratings, identify high performers, and flag inconsistencies. Time cost: 90 min/cycle. Retained as human step.
10
Upload Final Ratings to BambooHR
HR manually enters agreed final ratings and review notes for each employee into BambooHR for compensation planning and records. Time cost: 75 min/cycle.
Time cost summary: Total manual time per cycle is approximately 850 minutes (14.2 hours) across all ten steps, assuming a single pass with no re-work. At 2 cycles per year with daily coordination tasks throughout each 4-week window, the real-world weekly overhead during an active cycle is 10.5 hours/week. Steps 1 through 8 are replaced or substantially reduced by the three agents. Steps 9 (calibration meeting) and 10 (BambooHR write-back after human approval) remain as gated human actions.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Cycle Launch Agent

Reads the confirmed active employee list from BambooHR via API at the moment the review cycle is marked open, resolves the employee-to-manager mapping, generates personalised email content for each recipient using a stored template, dispatches kick-off notifications via Gmail, and posts channel and direct-message alerts in Slack. No manual drafting or list preparation is required. Estimated build time: 10 hours. Complexity: Moderate.

Trigger
HR marks the review cycle as open in BambooHR (webhook event or polling on the review_cycle_status field)
Tools
BambooHR, Gmail, Slack
Replaces steps
Steps 1, 2, 3 (employee export, kick-off emails, manager briefing emails)
Estimated build
10 hours — Moderate
// Input
BambooHR.review_cycle_status == 'open'
BambooHR.employees[] -> { employee_id, full_name, email, manager_id, manager_email, department }
BambooHR.review_cycle -> { cycle_name, submission_deadline, self_assessment_form_url, manager_form_url }

// Output
Gmail.send_batch -> employees[]: { to: employee.email, subject: 'Your performance review is open', body: templated(employee.full_name, self_assessment_form_url, submission_deadline) }
Gmail.send_batch -> managers[]: { to: manager.email, subject: 'Manager briefing: review cycle open', body: templated(manager.full_name, direct_reports[], manager_form_url, submission_deadline) }
Slack.post_message -> channel: #hr-announcements, text: 'Review cycle [cycle_name] is now open. Deadline: [submission_deadline].'
Slack.post_dm -> managers[]: { user: manager.slack_id, text: 'Your team review forms are live. Check your email for links.' }
tracker_sheet.initialise_rows -> employees[]: { employee_id, full_name, manager_name, self_assessment_status: 'Pending', manager_assessment_status: 'Pending', last_reminder_sent: null }
  • BambooHR API tier must support third-party API access. Confirm the account plan before build; some smaller tiers require an upgrade. The API key should be stored in the shared credential store, not hardcoded.
  • The employee-manager mapping is read from BambooHR's standard employeeId and supervisorId fields. If the org has matrix reporting or interim managers, a supplementary mapping override table in Google Sheets must be agreed and loaded before the agent runs.
  • Gmail dispatch uses a single authenticated service account with domain-wide delegation enabled for the Google Workspace domain. The sending address should be confirmed with the HR team (e.g. hr-reviews@[YourCompany.com]) before build.
  • Slack integration requires the bot token with chat:write and users:read scopes. Confirm whether Slack user IDs are stored in BambooHR or whether a separate email-to-Slack-ID lookup table is needed.
  • The kick-off email template and manager briefing template must be signed off by the HR team before build. Store templates as variables in the orchestration layer, not inside the email tool.
  • Dedupe guard: if the trigger fires twice (e.g. a webhook retry), the agent must check whether tracker rows already exist for the current cycle_name before re-sending emails. Use an idempotency key composed of cycle_name + employee_id.
Submission Tracker and Chaser Agent

Continuously monitors Google Forms for new responses by polling the linked Google Sheet at a configurable interval (default every 4 hours). On each poll it updates the submission status for each employee and manager in the master tracker. On a daily schedule it identifies rows where status is still Pending and the deadline has not passed, then dispatches reminder emails via Gmail and Slack nudges. A final 48-hour deadline warning is sent to all outstanding submitters regardless of prior reminders. Estimated build time: 14 hours. Complexity: Moderate.

Trigger
Review cycle is live and the submission window is open; polling schedule every 4 hours plus a daily reminder job at 09:00 local time
Tools
Google Forms, Google Sheets, Gmail, Slack
Replaces steps
Steps 4, 5, 6 (daily tracker update, manual chase reminders, form response collation)
Estimated build
14 hours — Moderate
// Input
Google Forms (via linked Google Sheet): responses_sheet.getNewRows() -> { response_timestamp, employee_id, form_type: 'self_assessment' | 'manager_assessment', respondent_email }
tracker_sheet.rows[] -> { employee_id, full_name, manager_email, self_assessment_status, manager_assessment_status, last_reminder_sent, reminder_count }
cycle_config -> { submission_deadline, reminder_threshold_hours: 24, max_reminders: 3 }

// Output (on each poll)
tracker_sheet.update_row -> { employee_id, self_assessment_status: 'Submitted' | 'Pending', manager_assessment_status: 'Submitted' | 'Pending', last_updated: now() }

// Output (on daily reminder job)
IF row.self_assessment_status == 'Pending' AND row.reminder_count < max_reminders:
  Gmail.send -> { to: employee.email, subject: 'Reminder: self-assessment due [deadline]', body: templated(employee.full_name, submission_deadline, self_assessment_form_url) }
  Slack.post_dm -> { user: employee.slack_id, text: 'Friendly reminder: your self-assessment is due [deadline].' }
  tracker_sheet.update_row -> { last_reminder_sent: now(), reminder_count: reminder_count + 1 }

// Output (48-hour deadline warning, sent once)
IF hours_until_deadline <= 48 AND row.status == 'Pending' AND final_warning_sent == false:
  Gmail.send -> { to: employee.email, subject: 'URGENT: review form due in 48 hours', body: templated(...) }
  tracker_sheet.update_row -> { final_warning_sent: true }
  • Google Forms must be configured to write responses to a linked Google Sheet (standard Forms setting). The sheet name and tab name must be confirmed before build; the agent reads from a named tab, not by index.
  • The response sheet must include an employee_id or email column that can be matched back to the tracker. If the current form design does not capture this, a hidden pre-filled field or a unique form URL per employee must be implemented before build.
  • The reminder threshold (24 hours before sending first reminder) and max_reminders (default 3) are stored as configurable variables in the orchestration layer so the HR team can adjust them without code changes.
  • If peer review feedback is in scope, the matching logic must be extended to track a third form type per employee. This adds estimated build time; confirm scope before starting this agent.
  • Dedupe guard: the poll step must record the last processed response row index or timestamp to avoid processing the same response twice across polling windows.
  • The daily reminder job and the 4-hour poll are separate scheduled triggers. Both must be paused automatically once the submission_deadline has passed to prevent post-deadline reminders firing.
Summary Drafting Agent

Listens for the moment both the employee self-assessment and the manager assessment have been submitted for the same employee (matched on employee_id). Once a matched pair is detected in the tracker, the agent reads both sets of form responses, applies a structured Google Docs template, and uses an AI drafting step to produce a coherent one-page narrative summary. The completed document is saved to the manager's designated Google Drive folder and a Gmail notification with a direct link is sent to the manager. Estimated build time: 14 hours. Complexity: Complex.

Trigger
tracker_sheet row for a given employee_id shows self_assessment_status == 'Submitted' AND manager_assessment_status == 'Submitted' (checked on each poll or on form submission event)
Tools
Google Forms, Google Docs, Gmail
Replaces steps
Steps 7, 8 (draft manager summary documents, distribute summaries to managers)
Estimated build
14 hours — Complex
// Input
tracker_sheet.matched_pair -> { employee_id, employee_name, manager_id, manager_name, manager_email, manager_drive_folder_id }
self_assessment_responses -> { question_1_response, question_2_response, ..., overall_rating_self, key_achievements, development_areas }
manager_assessment_responses -> { question_1_response, question_2_response, ..., overall_rating_manager, strengths_observed, areas_for_growth, recommended_action }
summary_template_doc_id -> Google Docs template ID (stored in config)

// AI drafting step (intermediate)
ai_prompt -> { system: 'You are an HR assistant. Produce a structured one-page performance summary.', input: { employee_name, self_assessment_responses, manager_assessment_responses, template_sections: ['Overview', 'Achievements', 'Development Areas', 'Manager Perspective', 'Agreed Next Steps'] } }
ai_output -> { summary_text_per_section{} }

// Output
Google Docs.copy_template(summary_template_doc_id) -> new_doc_id
Google Docs.populate_placeholders(new_doc_id) -> { {{EMPLOYEE_NAME}}, {{CYCLE_NAME}}, {{OVERVIEW}}, {{ACHIEVEMENTS}}, {{DEVELOPMENT_AREAS}}, {{MANAGER_PERSPECTIVE}}, {{AGREED_NEXT_STEPS}} }
Google Drive.move(new_doc_id) -> manager_drive_folder_id
Google Drive.set_permissions(new_doc_id) -> { role: 'writer', emailAddress: manager.email }
Gmail.send -> { to: manager.email, subject: 'Summary ready: [employee_name] — [cycle_name]', body: templated(manager.full_name, employee_name, doc_url) }
tracker_sheet.update_row -> { summary_status: 'Draft Ready', summary_doc_url: doc_url, summary_generated_at: now() }

// On approval (post-calibration, human-gated)
HR_approval_flag -> tracker_sheet row: final_rating_approved == true AND final_rating_value set
BambooHR.update_employee_record -> { employee_id, performance_rating: final_rating_value, review_cycle: cycle_name, review_notes: summary_doc_url }
  • The Google Docs template must be created and approved by the HR team before this agent is built. The template must use double-curly-brace placeholder syntax (e.g. {{EMPLOYEE_NAME}}) consistently across all sections so the population step can target them reliably.
  • The AI drafting step uses a configured LLM API call (model and endpoint stored in the credential store). The prompt must be reviewed and iterated against real sample form responses before go-live. Summary quality is directly dependent on the structure and completeness of the Google Form questions.
  • The manager Google Drive folder structure must be confirmed and pre-created (or auto-created by this agent using the Drive API) before the first summary is generated. The folder ID per manager must be stored in the tracker or a config sheet.
  • The BambooHR write-back in the 'On approval' path fires only after a human sets final_rating_approved to true in the tracker. This is a gated step and must never fire automatically. Implement an explicit approval column in the tracker that defaults to false.
  • Dedupe guard: check whether a summary_doc_url already exists for the employee_id before generating a new document. If a doc already exists, log the duplicate trigger event and skip rather than overwriting.
  • Confirm BambooHR API write permissions: the API key in use must have the performance_reviews:write scope. Read-only keys will not support the final ratings write-back.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow for the Performance Review Cycle automation. Field names match the Google Sheets tracker columns and BambooHR API payload keys.
// ============================================================
// PERFORMANCE REVIEW CYCLE: END-TO-END DATA FLOW
// ============================================================

// TRIGGER
// BambooHR emits event or polling detects: review_cycle_status == 'open'
BambooHR.event -> {
  cycle_name: string,
  cycle_id: string,
  submission_deadline: ISO8601_date,
  self_assessment_form_url: string,
  manager_form_url: string
}

// ============================================================
// AGENT 1: CYCLE LAUNCH AGENT
// ============================================================

// Step 1: Pull active employee roster from BambooHR
BambooHR.GET /v1/employees?status=active ->
  employees[]: {
    employee_id: string,
    full_name: string,
    email: string,
    slack_id: string,         // from BambooHR custom field or lookup table
    manager_id: string,
    manager_name: string,
    manager_email: string,
    manager_slack_id: string,
    department: string
  }

// Step 2: Initialise master tracker in Google Sheets
Google Sheets.appendRows(tracker_sheet_id, tab: 'Submissions') ->
  per employee: {
    employee_id, full_name, department, manager_name, manager_email,
    self_assessment_status: 'Pending',
    manager_assessment_status: 'Pending',
    last_reminder_sent: null,
    reminder_count: 0,
    final_warning_sent: false,
    summary_status: 'Not Started',
    summary_doc_url: null,
    final_rating_approved: false,
    final_rating_value: null
  }

// Step 3: Send Gmail kick-off emails (employees)
Gmail.send_batch ->
  to: employee.email
  subject: 'Your performance review is open — [cycle_name]'
  body_vars: { full_name, self_assessment_form_url, submission_deadline }

// Step 4: Send Gmail manager briefing emails
Gmail.send_batch ->
  to: manager.email (deduplicated across direct reports)
  subject: 'Manager briefing: review cycle open — [cycle_name]'
  body_vars: { manager_name, direct_reports[], manager_form_url, submission_deadline }

// Step 5: Post Slack notifications
Slack.post_message ->
  channel: #hr-announcements
  text: 'Review cycle [cycle_name] is now open. Deadline: [submission_deadline].'

Slack.post_dm (per manager) ->
  user: manager.slack_id
  text: 'Your team review forms are live. Check your email for links.'

// -- HANDOFF: Cycle Launch Agent -> Submission Tracker and Chaser Agent --

// ============================================================
// AGENT 2: SUBMISSION TRACKER AND CHASER AGENT
// ============================================================

// Polling job: every 4 hours while cycle is open
Google Sheets.getRows(forms_response_sheet_id, tab: 'Form Responses') ->
  new_rows[]: {
    response_timestamp: ISO8601,
    respondent_email: string,
    employee_id: string,          // from pre-filled hidden field
    form_type: 'self_assessment' | 'manager_assessment'
  }

// Match response to tracker row, update status
Google Sheets.updateRow(tracker_sheet_id) ->
  WHERE employee_id matches:
    IF form_type == 'self_assessment': self_assessment_status = 'Submitted'
    IF form_type == 'manager_assessment': manager_assessment_status = 'Submitted'
    last_updated = now()

// Daily reminder job: 09:00 local time
FOR each tracker row WHERE (self_assessment_status == 'Pending' OR manager_assessment_status == 'Pending')
  AND reminder_count < max_reminders (default: 3)
  AND now() < submission_deadline:

  Gmail.send ->
    to: employee.email (if self_assessment_status == 'Pending')
    subject: 'Reminder: your self-assessment is due [submission_deadline]'
    body_vars: { full_name, self_assessment_form_url, submission_deadline, reminder_count }

  Slack.post_dm ->
    user: employee.slack_id
    text: 'Friendly reminder: self-assessment due [submission_deadline].'

  Google Sheets.updateRow -> { last_reminder_sent: now(), reminder_count: reminder_count + 1 }

// 48-hour deadline warning (once per outstanding submitter)
FOR each tracker row WHERE status == 'Pending'
  AND hours_until_deadline(submission_deadline) <= 48
  AND final_warning_sent == false:

  Gmail.send -> { subject: 'URGENT: review form due in 48 hours', body_vars: { full_name, submission_deadline } }
  Google Sheets.updateRow -> { final_warning_sent: true }

// -- HANDOFF: Submission Tracker Agent -> Summary Drafting Agent --
// Condition: self_assessment_status == 'Submitted' AND manager_assessment_status == 'Submitted'

// ============================================================
// AGENT 3: SUMMARY DRAFTING AGENT
// ============================================================

// Read matched pair of form responses
Google Sheets.getRow(forms_response_sheet_id) ->
  self_assessment: {
    employee_id, question_1_response, question_2_response,
    key_achievements: string, development_areas: string,
    overall_rating_self: string
  }
  manager_assessment: {
    employee_id, question_1_response, question_2_response,
    strengths_observed: string, areas_for_growth: string,
    overall_rating_manager: string, recommended_action: string
  }

// AI drafting step
LLM.complete(prompt) ->
  input: { employee_name, self_assessment{}, manager_assessment{}, template_sections[] }
  output: {
    overview: string,
    achievements: string,
    development_areas: string,
    manager_perspective: string,
    agreed_next_steps: string
  }

// Populate Google Docs template
Google Docs.copy(template_doc_id) -> new_doc_id
Google Docs.batchUpdate(new_doc_id) ->
  replace: { '{{EMPLOYEE_NAME}}': employee_name,
             '{{CYCLE_NAME}}': cycle_name,
             '{{OVERVIEW}}': overview,
             '{{ACHIEVEMENTS}}': achievements,
             '{{DEVELOPMENT_AREAS}}': development_areas,
             '{{MANAGER_PERSPECTIVE}}': manager_perspective,
             '{{AGREED_NEXT_STEPS}}': agreed_next_steps }

// Save to manager Drive folder and set permissions
Google Drive.files.update(new_doc_id) -> parents: [manager_drive_folder_id]
Google Drive.permissions.create(new_doc_id) -> { role: 'writer', emailAddress: manager.email }

// Notify manager
Gmail.send ->
  to: manager.email
  subject: 'Summary ready: [employee_name] — [cycle_name]'
  body_vars: { manager_name, employee_name, doc_url, cycle_name }

// Update tracker
Google Sheets.updateRow -> {
  summary_status: 'Draft Ready',
  summary_doc_url: doc_url,
  summary_generated_at: now()
}

// -- HANDOFF: Summary Drafting Agent -> Human gated approval --

// ============================================================
// HUMAN STEP: CALIBRATION MEETING (not automated)
// HR Director and managers conduct calibration; HR sets:
//   tracker_sheet.final_rating_approved = true
//   tracker_sheet.final_rating_value = agreed_rating
// ============================================================

// Post-approval: BambooHR write-back (gated on final_rating_approved == true)
BambooHR.POST /v1/employees/{employee_id}/performance_reviews ->
  payload: {
    cycle_name: string,
    final_rating: final_rating_value,
    review_notes_url: summary_doc_url,
    reviewed_by: manager_id,
    approved_by: hr_approver_id,
    completed_at: now()
  }

// ============================================================
// END OF CYCLE
// All scheduled triggers are paused once cycle_status == 'closed'
// ============================================================
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation platform (tool to be selected at project kick-off). One workflow per agent: 'Cycle Launch', 'Submission Tracker and Chaser', 'Summary Drafting'. All three workflows share a single credential store for BambooHR API key, Google service account JSON, Gmail OAuth token, Slack bot token, and LLM API key. No credentials are hardcoded in workflow nodes.
Webhook configuration
BambooHR cycle-open event: configure an outbound webhook in BambooHR pointing to the orchestration platform's inbound webhook URL for the Cycle Launch workflow. Secure with a shared secret header (X-BambooHR-Signature). Google Forms responses: Forms native response collection writes to a linked Google Sheet; the Tracker Agent polls this sheet on a 4-hour cron schedule rather than relying on a Forms webhook, which avoids the instability of Apps Script-based triggers. A secondary cron at 09:00 local time drives the daily reminder job.
Templating approach
Email body templates are stored as text variables in the orchestration platform's environment config (not inside the email tool). Google Docs summary template is a single master document in the HR team's Google Drive; the Summary Drafting Agent copies it per employee using the Drive API and then performs a batchUpdate replace-all operation using {{PLACEHOLDER}} syntax. Template doc ID is stored in the platform config, not hardcoded in the workflow node.
Error logging
All agent runs write a status record to a dedicated 'Automation Logs' tab in the master Google Sheet (columns: run_id, agent_name, employee_id, step, status, error_message, timestamp). Any run that fails with an unhandled error triggers a Slack alert to #hr-automation-alerts and an email to support@gofullspec.com. The orchestration platform's built-in retry policy is set to 3 attempts with exponential backoff before the error is surfaced. BambooHR write-back failures are treated as critical and alert immediately.
Testing approach
All agents are built and validated in a sandbox environment first. BambooHR has a sandbox mode (API base URL: api.sandbox.bamboohr.com); this must be confirmed as available on the account's plan tier. Google Sheets and Docs testing uses a dedicated 'TEST - Performance Review' folder in Google Drive, isolated from production data. The Slack workspace has a #automation-test channel used during QA. A 10-person pilot cohort (identified by the HR team) runs a full dry-run cycle before the first live cycle. LLM summary quality is reviewed against 5 real anonymised response pairs before go-live.
Estimated total build time
Cycle Launch Agent: 10 hours. Submission Tracker and Chaser Agent: 14 hours. Summary Drafting Agent: 14 hours. End-to-end integration testing and QA: 8 hours. Discovery and data mapping (pre-build): included in project scope. Total: 38 hours (aligns with build_effort_hours in project spec). Build cost: $3,800 (Standard build, one-off).
Pre-build blockers that must be resolved before any build work starts: (1) BambooHR API access confirmed on the correct plan tier; (2) Google Workspace service account created with domain-wide delegation and the following scopes granted: gmail.send, drive.file, docs, spreadsheets, and users.read; (3) Slack bot token issued with chat:write and users:read scopes; (4) Google Forms confirmed to write to a linked Google Sheet with an employee_id column present or pre-filled URL strategy agreed; (5) Google Docs summary template designed and approved by HR. Any of these items remaining unresolved at build start will delay the timeline beyond the 3 to 4 week estimate. Contact the FullSpec team at support@gofullspec.com to confirm readiness before the build kick-off call.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Performance Review Cycle.

Launch Plan
Operations · Owner
View
ROI and Business Case
Finance · Owner
View
Process Runbook / SOP
Operations · Owner
View
Integration and API Spec
Technical · Developer
View
Test and QA Plan
Quality · Developer
View