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
Leave and Absence Management
[YourCompany.com] · HR Department · Prepared by FullSpec · [Today's Date]
This document is the primary technical reference for the FullSpec team building the Leave and Absence Management automation. It covers the full current-state process map with bottleneck identification, all three agent specifications with IO contracts, the end-to-end data flow trace, and the recommended build stack. Everything the FullSpec team needs to begin the build without ambiguity is contained here. Where a decision or prerequisite remains open, it is called out explicitly so it can be resolved before build starts.
01Process snapshot
02Agent specifications
Receives a structured leave request from the submission form trigger, queries BambooHR via its REST API to retrieve the employee's current leave balance and entitlement for the relevant leave type, then scans the shared team Google Calendar for any overlapping approved absences during the requested date range. If the balance is sufficient and no blocking conflict exists, the agent packages a validated request summary and passes it to the Approval Routing Agent. If the balance is insufficient or a hard conflict is detected, the agent routes to a decline notification via Gmail and terminates the workflow run. Estimated build time: 8 hours. Complexity: Moderate.
// Input employee_id: string // BambooHR employee ID leave_type: string // e.g. 'annual', 'sick', 'unpaid', 'parental' start_date: date (ISO 8601) // e.g. 2025-05-12 end_date: date (ISO 8601) // e.g. 2025-05-16 requested_days: integer // calendar working days in range notes: string | null // optional employee comment // Output (on pass) request_id: string // UUID generated at intake employee_name: string // resolved from BambooHR record leave_balance_remaining: float // days available before this request balance_sufficient: boolean // true if remaining >= requested_days conflict_detected: boolean // true if overlapping events found in Calendar conflict_details: array | null // list of conflicting event summaries status: 'ready_for_approval' | 'declined_balance' | 'declined_conflict' // On decline (balance or conflict fail) decline_reason: string // human-readable reason for Gmail notification employee_email: string // resolved from BambooHR for fallback Gmail send
- BambooHR API access must be enabled at the account plan level before build starts. Confirm with the client that the plan is Standard or above, as the Essentials tier does not expose the Time Off API endpoints required for balance retrieval and request logging.
- The Google Calendar conflict check must target the correct shared calendar. Confirm the calendar ID (typically a long string ending in @group.calendar.google.com) and ensure the service account or OAuth credential has at least 'Reader' scope on that calendar.
- The definition of a 'blocking conflict' must be agreed with HR before the logic is coded. Options include: any overlap with another absence, overlap only if the same team is under minimum headcount, or a percentage threshold. Leave this as a configurable parameter, not a hardcoded rule.
- Dedupe behaviour: if a request with an identical employee_id, leave_type, start_date, and end_date is submitted within 10 minutes of a prior submission, the second run must be suppressed and a duplicate-flag logged to the error table. Do not process it twice.
- If BambooHR returns a non-200 response, the agent must pause, log the error with request_id and HTTP status to the Supabase error table, and send an alert to the FullSpec support channel before retrying once after 5 minutes.
Receives the validated request summary from the Leave Intake and Balance Agent and sends a structured approval message to the line manager via Slack, using a custom Slack app with interactive components (approve and decline buttons). The message includes employee name, leave type, dates, days requested, remaining balance after approval, and any conflict advisory notes. The agent then enters a wait state listening for the manager's button response. On response, it logs the decision with a timestamp and the manager's Slack user ID for audit purposes, then routes the outcome: approved requests proceed to the Post-Approval Sync Agent, and declined requests trigger a Gmail notification to the employee with the manager's decision. Estimated build time: 10 hours. Complexity: Moderate.
// Input (from Leave Intake and Balance Agent) request_id: string employee_name: string employee_email: string employee_slack_id: string // resolved via Slack users.lookupByEmail manager_slack_id: string // resolved from BambooHR supervisor field leave_type: string start_date: date end_date: date requested_days: integer leave_balance_remaining: float conflict_details: array | null // Output (on approval) decision: 'approved' decided_by_slack_id: string // Slack user ID of responding manager decided_at: timestamp (ISO 8601) request_id: string // passed through for audit chain // Output (on decline) decision: 'declined' decline_note: string | null // optional manager comment from Slack modal decided_by_slack_id: string decided_at: timestamp (ISO 8601) // On approval: passes full payload to Post-Approval Sync Agent // On decline: triggers Gmail send to employee_email with decline_note
- A custom Slack app must be created in the target Slack workspace before this agent can be built. Required bot token scopes: chat:write, im:write, users:read, users:read.email, and app_mentions:read. The interactive components endpoint (the webhook URL that receives button clicks) must be registered in the Slack app manifest under Interactivity.
- The manager's Slack user ID must be resolved dynamically. The recommended approach is to read the supervisor email from BambooHR and call Slack's users.lookupByEmail method at runtime. Do not hardcode manager IDs as they change when org structure changes.
- The approval wait state has a maximum timeout of 72 hours. If no response is received within 72 hours, the agent must send a reminder to the manager, wait an additional 24 hours, and if still no response, escalate by notifying the HR Manager via Slack and logging the request as 'pending_escalation' in the Supabase tracking table.
- The Slack interactive message must not allow the same button to be clicked twice. Implement idempotency by storing the request_id against the responding Slack user ID immediately on first click and rejecting any duplicate callback with the same request_id.
- Gmail is used as the fallback notification channel on decline only. Confirm that a dedicated sending address (e.g. hr-noreply@[YourCompany.com]) is available and that an OAuth 2.0 credential with the Gmail API scope gmail.send has been provisioned for it.
- Leave step 6 (manager reviewing and deciding) as a fully human action. The automation surfaces information and waits; it does not auto-approve under any condition unless the client explicitly requests that rule and HR formally signs off on it.
Triggered immediately upon receiving an approved decision from the Approval Routing Agent, this agent executes four sequential write operations across BambooHR, Google Calendar, Gusto, and Slack. It deducts the approved days from the employee's leave balance in BambooHR, creates a dated absence event in the shared team Google Calendar, logs the approved leave period in Gusto for payroll processing, and finally sends a direct Slack message to the employee confirming the approval and a channel post to the team calendar channel. All four writes are logged with request_id and timestamp. Estimated build time: 10 hours. Complexity: Moderate.
// Input (from Approval Routing Agent) request_id: string employee_id: string employee_name: string employee_slack_id: string employee_email: string leave_type: string start_date: date end_date: date requested_days: integer decided_by_slack_id: string decided_at: timestamp team_slack_channel_id: string // resolved from config or HR settings table // Output bamboohr_updated: boolean calendar_event_id: string // Google Calendar event ID for reference gusto_log_id: string // Gusto leave record ID employee_notified: boolean team_notified: boolean sync_completed_at: timestamp // On partial failure failed_steps: array // e.g. ['gusto_log'] if Gusto write fails error_detail: string // logged to Supabase error table with request_id
- Gusto API access requires a company-level OAuth token. Confirm whether the Gusto account is on a plan that exposes the Time Off API. Gusto's partner API and direct API differ in available endpoints; clarify which credential type the client holds before building this step.
- The four write operations (BambooHR, Google Calendar, Gusto, Slack) must be executed in order. If any of the first three fails, the agent must log the failure and halt rather than skip ahead, so partial syncs do not create inconsistent records across systems.
- BambooHR balance deduction: use the approved leave request flow (update request status to 'approved') rather than directly patching the balance field, so BambooHR's own audit trail reflects the change correctly.
- Google Calendar event naming convention must be confirmed with HR before build. A suggested format is: '[Employee Name] - [Leave Type]' with the description field containing the request_id for traceability.
- The team Slack channel ID must be stored in a configuration table or environment variable, not hardcoded. Different teams may use different channels; if the client has multiple departments, this must support a channel lookup by department.
- All four sync results must be written back to the Supabase tracking table against the request_id so HR can audit any request end-to-end without accessing the individual SaaS tools.
03End-to-end data flow
// ─────────────────────────────────────────────────────────────
// TRIGGER: Employee submits leave request form
// ─────────────────────────────────────────────────────────────
FORM_SUBMISSION received
-> employee_id: string // e.g. 'EMP-00142'
-> leave_type: string // e.g. 'annual'
-> start_date: '2025-06-02' // ISO 8601
-> end_date: '2025-06-06' // ISO 8601
-> requested_days: 5
-> notes: string | null
// ─────────────────────────────────────────────────────────────
// AGENT 1: Leave Intake and Balance Agent
// ─────────────────────────────────────────────────────────────
STEP 1.1: Dedupe check
-> query Supabase requests table WHERE employee_id + leave_type + start_date + end_date
-> IF duplicate within 10 min: log, suppress, HALT
STEP 1.2: BambooHR balance query
GET /employees/{employee_id}/timeOffBalances
-> response.timeOffTypes[leave_type].balance: float // e.g. 12.0 days
-> employee_name: string // from employee record
-> employee_email: string // from employee record
-> manager_email: string // from supervisor field
-> balance_sufficient: (balance >= requested_days) // boolean
STEP 1.3: Google Calendar conflict scan
GET /calendars/{team_calendar_id}/events
?timeMin=start_date&timeMax=end_date&singleEvents=true
-> conflict_detected: boolean
-> conflict_details: [{ summary, start, end, organizer }] | null
STEP 1.4: Route decision
IF balance_sufficient == false:
status = 'declined_balance'
-> Gmail: send decline notification to employee_email
-> Supabase: log { request_id, status, decline_reason, timestamp }
-> HALT
IF conflict_detected == true AND conflict_blocks == true:
status = 'declined_conflict'
-> Gmail: send decline notification to employee_email
-> Supabase: log { request_id, status, decline_reason, timestamp }
-> HALT
ELSE:
status = 'ready_for_approval'
-> pass full payload to Agent 2
// ─────────────────────────────────────────────────────────────
// HANDOFF: Agent 1 -> Agent 2 (Approval Routing Agent)
// Fields passed: request_id, employee_name, employee_email,
// employee_slack_id, manager_slack_id, leave_type, start_date,
// end_date, requested_days, leave_balance_remaining,
// conflict_details
// ─────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────
// AGENT 2: Approval Routing Agent
// ─────────────────────────────────────────────────────────────
STEP 2.1: Resolve Slack user IDs
GET /slack/users.lookupByEmail?email=employee_email
-> employee_slack_id: string
GET /slack/users.lookupByEmail?email=manager_email
-> manager_slack_id: string
STEP 2.2: Send Slack approval message to manager
POST /slack/chat.postMessage
channel: manager_slack_id
blocks: [{
employee_name, leave_type, start_date, end_date,
requested_days, balance_after_approval,
conflict_advisory (if conflict_detected but not blocking),
action_buttons: ['Approve', 'Decline']
}]
-> message_ts: string // Slack message timestamp, stored for idempotency
STEP 2.3: Wait for interactive callback (max 72 hours)
CALLBACK received:
-> action_id: 'approve' | 'decline'
-> responding_user_slack_id: string
-> callback_timestamp: ISO 8601
IF no callback after 72 hours: send reminder, wait 24 hours, then escalate to HR Manager
STEP 2.4: Log decision
-> Supabase: { request_id, decision, decided_by_slack_id, decided_at }
STEP 2.5: Route outcome
IF decision == 'approved':
-> pass full payload + audit fields to Agent 3
IF decision == 'declined':
-> Gmail: send decline email to employee_email with decline_note
-> Supabase: update request status to 'declined_by_manager'
-> HALT
// ─────────────────────────────────────────────────────────────
// HANDOFF: Agent 2 -> Agent 3 (Post-Approval Sync Agent)
// Fields passed: all Agent 1 fields + decision, decided_by_slack_id,
// decided_at, employee_slack_id, team_slack_channel_id
// ─────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────
// AGENT 3: Post-Approval Sync Agent
// ─────────────────────────────────────────────────────────────
STEP 3.1: Update BambooHR leave record
POST /time_off/requests/{request_id}/approve
OR PUT /employees/{employee_id}/timeOffRequests with status='approved'
-> bamboohr_updated: boolean
-> new_balance: float // balance after deduction
STEP 3.2: Create Google Calendar event
POST /calendars/{team_calendar_id}/events
summary: '{employee_name} - {leave_type}'
start: { date: start_date }
end: { date: end_date }
description: 'request_id: {request_id}'
-> calendar_event_id: string
STEP 3.3: Log leave in Gusto
POST /v1/companies/{company_id}/time_off_requests
employee_id: {gusto_employee_id}
leave_type: {leave_type}
start_date: {start_date}
end_date: {end_date}
days: {requested_days}
-> gusto_log_id: string
STEP 3.4: Send Slack notifications
DM to employee_slack_id: approval confirmed, dates, remaining balance
POST to team_slack_channel_id: '{employee_name} is on {leave_type} {start_date} to {end_date}'
-> employee_notified: true
-> team_notified: true
STEP 3.5: Final audit log
-> Supabase: { request_id, bamboohr_updated, calendar_event_id,
gusto_log_id, employee_notified, team_notified, sync_completed_at }
// ─────────────────────────────────────────────────────────────
// END OF WORKFLOW RUN
// Total automated steps: 10 of 11 original manual steps
// Human step retained: Manager approve/decline decision (Step 2.3)
// ─────────────────────────────────────────────────────────────04Recommended build stack
More documents for this process
Every document generated for Leave & Absence Management.