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
Device and Hardware Management
[YourCompany.com] · IT Department · Prepared by FullSpec · [Today's Date]
This document is the primary technical reference for the FullSpec build team delivering the Device and Hardware Management automation. It covers the full current-state step map, all three agent specifications with IO contracts, the end-to-end data flow, and the recommended build stack. The FullSpec team owns every build, integration, and testing task described here. Your IT team's only ongoing responsibility is physical device handover, condition checks on return, and responding to exception escalations surfaced by the automation.
01Process snapshot
02Agent specifications
Monitors incoming device events from Jira and HR triggers, queries the Airtable register for availability, and writes assignment or return records back automatically. It handles all lookup and record-keeping work that currently falls to the IT Coordinator, including availability checking, register writes on assignment, register updates on return, and Jira ticket creation or update. This is the central orchestration agent: the other two agents fire in response to record writes it produces. Complexity: Moderate. Estimated build time: 14 hours.
// Input jira_event.issue_type // 'new_hire_device' | 'offboarding_return' | 'device_request' jira_event.assignee_email // target employee email jira_event.required_spec // e.g. 'MacBook Pro 14 M3' jira_event.expected_return // ISO 8601 date, nullable hr_webhook.employee_id // fallback join key if Jira field is absent // Output (on device found) airtable_record.device_id // Airtable record ID airtable_record.serial_number // device serial airtable_record.status // 'assigned' | 'available' | 'pending_wipe' airtable_record.assigned_to_email // employee email written to record airtable_record.assignment_date // UTC timestamp airtable_record.expected_return // ISO 8601 date jira_ticket.id // created or updated ticket ID jira_ticket.status // 'In Progress' | 'Awaiting Handover' // Output (no device found) availability_flag: false procurement_alert_payload // passed to Device Lifecycle Comms Agent
- Airtable base must be on the Team plan or above to support API write access; confirm the base ID and the 'Devices' table schema before build begins. Required fields: device_id (auto), serial_number (text), status (single select: available, assigned, pending_wipe, retired), assigned_to_email (email), assignment_date (date), expected_return (date), spec (text), condition (single select: good, fair, poor).
- Jira project key must be confirmed. The agent uses the Jira REST API v3. Issue type names must match exactly: 'new_hire_device', 'offboarding_return', 'device_request'. Custom issue types need remapping before build.
- Deduplication: before writing a new assignment record, the agent queries Airtable for any existing record with the same serial_number and status 'assigned'. If found, it logs a conflict and halts rather than creating a duplicate.
- Fallback for HR events: if no Jira ticket exists for a hire or departure, the agent accepts a daily Google Sheets export row as a trigger source. The sheet must contain columns: employee_email, event_type, event_date, required_spec.
- The step-12 end-of-life flag is implemented as a nightly scheduled query: any device with assignment_date older than 36 months or condition set to 'poor' is written to an 'eol_flagged' view in Airtable and a summary is posted to the IT Slack channel. No automatic disposal action is taken.
- Confirm with IT Manager that Airtable API token has read and write scope on the Devices table and read scope on the Staff table before build kickoff.
Handles all outbound communication triggered by device events: assignment confirmations, audit request emails, overdue return reminders, and procurement alerts. It fires on record changes written by the Asset Register Agent or on a scheduled trigger for the quarterly audit cycle. Every message uses a stored template with dynamic fields pulled from Airtable so wording is consistent across all events. This agent replaces all manual email drafting and the manual Slack reminder step. Complexity: Simple. Estimated build time: 8 hours.
// Input (assignment confirmation) airtable_record.assigned_to_email airtable_record.serial_number airtable_record.assignment_date airtable_record.expected_return template_id: 'device_assignment_confirmation' // Input (audit cycle) airtable_view: 'all_active_assignments' // list of current assigned records audit_period: 'Q2 2025' // injected from scheduled trigger // Input (overdue return) airtable_record.assigned_to_email airtable_record.serial_number airtable_record.expected_return days_overdue: integer // Output gmail.message_id // sent message ID logged to Airtable comms_log field slack.message_ts // Slack message timestamp airtable_record.last_comms_sent // UTC timestamp written back to record
- Gmail OAuth must be authorised against the IT team's shared Gmail account (not a personal account). Scope required: gmail.send. The service account must be set up under Google Workspace with domain-wide delegation if sending on behalf of a distribution address.
- Three email templates must be created and version-controlled before build: 'device_assignment_confirmation', 'quarterly_audit_request', and 'overdue_return_reminder'. Each template uses Airtable field tokens ({serial_number}, {assigned_to_name}, {expected_return}, {days_overdue}).
- Slack alerts use two separate webhooks: one posting to the #it-ops channel for general events, and one posting a DM to the IT Manager for procurement alerts and overdue returns exceeding 14 days. Both webhook URLs must be supplied before build.
- The quarterly audit email instructs recipients to reply with a confirmation or to complete a linked Airtable form. The form approach is preferred because it avoids inconsistent free-text replies. Confirm with the IT Manager which method to use; form requires an Airtable form URL to be embedded in the template.
- Deduplication: the agent checks airtable_record.last_comms_sent before sending a reminder. If a reminder was sent within the past 48 hours for the same record, it skips and logs a suppression note.
Calls the Jamf Pro API when a device is assigned or returned to verify enrollment status, apply the correct policy profile, and trigger a remote wipe on returned devices before they are marked available again. It fires on Airtable record writes from the Asset Register Agent and writes its results back to the same record. Any enrollment error is flagged to the IT Coordinator via the Device Lifecycle Comms Agent rather than silently failing. Physical wipe confirmation still requires a human to verify the device is received and in acceptable condition before the record status advances to 'available'. Complexity: Moderate. Estimated build time: 12 hours.
// Input (assignment) airtable_record.serial_number // used as Jamf device lookup key airtable_record.assigned_to_email jamf_policy_profile_id // supplied per device category in config // Input (return / wipe) airtable_record.serial_number airtable_record.device_id wipe_command: 'EraseDevice' // Jamf MDM command // Output (assignment) jamf_device.managed_id // Jamf device record ID jamf_device.enrollment_status // 'enrolled' | 'not_enrolled' | 'error' jamf_device.policy_applied // boolean airtable_record.jamf_managed_id // written back airtable_record.enrollment_status // written back // Output (wipe) jamf_wipe.command_status // 'Pending' | 'Completed' | 'Failed' airtable_record.wipe_status // written back airtable_record.status // remains 'pending_wipe' until human confirms receipt // On enrollment error error_payload.serial_number error_payload.error_code // Jamf API HTTP status + message error_payload.flagged_for_review // true — triggers Comms Agent IT alert
- Jamf Pro API requires an API role with the following minimum permissions: 'Read Computers', 'Send Computer Remote Wipe Command', 'Update Computer Management Data', 'Read Mobile Devices' (if tablets are in scope). A dedicated API service account must be created in Jamf; do not use a named user account.
- Self-hosted Jamf Pro instances may sit behind a firewall or VPN. Confirm with the IT Manager that the automation platform's outbound IP range can reach the Jamf host URL on port 443 before build begins. Cloud-hosted Jamf Pro (Jamf Cloud) has no additional network requirement.
- The agent uses the Jamf Pro REST API (v1) for all calls. The Classic API is not used. Base URL pattern: https://{instance}.jamfcloud.com/api/v1/. Bearer token authentication is used with a token refresh cycle of 30 minutes.
- Policy profile IDs for each device category (MacBook, iPad, PC) must be documented and supplied in the agent configuration before build. A minimum of three profiles is assumed: 'standard_employee', 'contractor', 'shared_device'.
- The remote wipe command sets airtable_record.status to 'pending_wipe', not 'available'. The status advances to 'available' only after the IT Coordinator manually marks the device as physically received and inspected. This field update triggers the Comms Agent to post a re-stock alert to the IT Slack channel.
- If the Jamf API returns a non-200 response, the agent retries twice with a 60-second backoff before writing an error flag to Airtable and triggering a Comms Agent alert to the IT Coordinator. Errors are also written to the error log table in Supabase.
03End-to-end data flow
// ============================================================
// DEVICE AND HARDWARE MANAGEMENT — END-TO-END DATA FLOW
// ============================================================
// TRIGGER
// Source: Jira webhook (issue created/updated) OR HR webhook POST OR Google Sheets daily sync
inbound_event = {
source: 'jira' | 'hr_webhook' | 'sheets_sync',
event_type: 'new_hire_device' | 'offboarding_return' | 'device_request',
assignee_email: string, // e.g. 'jane.doe@yourcompany.com'
required_spec: string, // e.g. 'MacBook Pro 14 M3'
expected_return: string | null, // ISO 8601 date
employee_id: string // HR system ID, used as fallback join key
}
// ============================================================
// AGENT 1 — Asset Register Agent
// ============================================================
// Step 1: Query Airtable for matching available device
airtable_query = {
table: 'Devices',
filter: "status = 'available' AND spec = inbound_event.required_spec",
fields: ['device_id','serial_number','spec','condition','jamf_managed_id']
}
airtable_result = Airtable.GET /v0/{base_id}/Devices?filterByFormula=...
// Step 2: Availability decision
IF airtable_result.records.length === 0:
// No device available — build procurement alert payload
procurement_payload = {
required_spec: inbound_event.required_spec,
requestor_email: inbound_event.assignee_email,
event_type: inbound_event.event_type
}
// Hand off to Device Lifecycle Comms Agent (procurement path)
--> AGENT 2 (procurement_alert, slack_it_manager)
HALT
ELSE:
selected_device = airtable_result.records[0] // first matching record
// selected_device.serial_number e.g. 'C02XJ1YZJGH5'
// selected_device.device_id e.g. 'recABC123XYZ'
// Step 3: Write assignment record to Airtable
airtable_write = Airtable.PATCH /v0/{base_id}/Devices/{selected_device.device_id}
body: {
status: 'assigned',
assigned_to_email: inbound_event.assignee_email,
assignment_date: utc_now(),
expected_return: inbound_event.expected_return
}
// Returns: updated_record with all fields
// Step 4: Create or update Jira ticket
IF inbound_event.source === 'jira':
jira_update = Jira.PUT /rest/api/3/issue/{jira_event.issue_id}
body: { fields: { status: 'In Progress', serial_number_cf: selected_device.serial_number } }
ELSE:
jira_create = Jira.POST /rest/api/3/issue
body: {
project: { key: 'IT' },
issuetype: { name: inbound_event.event_type },
summary: 'Device assignment: ' + inbound_event.assignee_email,
serial_number_cf: selected_device.serial_number
}
// Output: jira_ticket.id, jira_ticket.key (e.g. 'IT-4821')
// ============================================================
// AGENT HANDOFF 1 → 3 — Asset Register Agent to MDM Sync Agent
// Trigger: Airtable record status set to 'assigned'
// ============================================================
// ============================================================
// AGENT 3 — MDM Sync Agent (fires in parallel with Comms Agent)
// ============================================================
// Step 5: Jamf enrollment check
jamf_token = Jamf.POST /api/v1/auth/token
headers: { Authorization: Basic {base64(api_user:api_pass)} }
// Returns: token (valid 30 min)
jamf_lookup = Jamf.GET /api/v1/computers-inventory?filter=serialNumber=={serial_number}
// Returns: { id, serialNumber, managementId, enrollmentStatus }
IF jamf_lookup.enrollmentStatus !== 'ENROLLED':
error_payload = { serial_number, error_code: 'NOT_ENROLLED', flagged_for_review: true }
--> AGENT 2 (enrollment_error_alert, gmail_it_coordinator)
Supabase.INSERT INTO error_log (serial_number, error_code, ts)
HALT
// Step 6: Apply policy profile
jamf_policy = Jamf.POST /api/v1/preview/mdm/commands
body: {
commandType: 'SETTINGS',
managementIds: [jamf_lookup.managementId],
profileId: config.policy_profile_id[device_category]
}
// Returns: command_uuid
// Write Jamf result back to Airtable
Airtable.PATCH /v0/{base_id}/Devices/{device_id}
body: {
jamf_managed_id: jamf_lookup.managementId,
enrollment_status: 'enrolled',
policy_applied: true
}
// ============================================================
// AGENT HANDOFF 1 → 2 — Asset Register Agent to Comms Agent
// Trigger: Airtable record status set to 'assigned' AND enrollment_status = 'enrolled'
// ============================================================
// ============================================================
// AGENT 2 — Device Lifecycle Comms Agent
// ============================================================
// Step 7: Send assignment confirmation via Gmail
gmail_send = Gmail.POST /gmail/v1/users/me/messages/send
body: {
to: airtable_record.assigned_to_email,
subject: 'Your device is ready — ' + airtable_record.serial_number,
template: 'device_assignment_confirmation',
fields: {
assigned_to_name: employee_name, // looked up from Airtable Staff table
serial_number: airtable_record.serial_number,
assignment_date: airtable_record.assignment_date,
expected_return: airtable_record.expected_return,
policy_link: config.acceptable_use_policy_url
}
}
// Returns: gmail.message_id — written to airtable_record.comms_log
// Step 8: Post summary to IT Slack channel
slack_post = Slack.POST {it_ops_webhook_url}
body: {
text: ':white_check_mark: Device assigned',
blocks: [
{ type: 'section', fields: [
{ label: 'Employee', value: airtable_record.assigned_to_email },
{ label: 'Serial', value: airtable_record.serial_number },
{ label: 'Jira', value: jira_ticket.key },
{ label: 'Return by', value: airtable_record.expected_return }
]}
]
}
// Returns: slack.message_ts — written to airtable_record.last_comms_sent
// ============================================================
// RETURN / OFFBOARDING PATH
// Trigger: inbound_event.event_type = 'offboarding_return'
// ============================================================
// Agent 1 sets airtable_record.status = 'pending_wipe'
// Agent 3 fires on 'pending_wipe' status change
jamf_wipe = Jamf.POST /api/v1/preview/mdm/commands
body: {
commandType: 'ERASE_DEVICE',
managementIds: [jamf_lookup.managementId]
}
// Returns: { commandUuid, status: 'Pending' }
Airtable.PATCH -> wipe_status: jamf_wipe.status // 'Pending'
// Status remains 'pending_wipe' until IT Coordinator manually sets to 'available'
// That manual update triggers Comms Agent restock alert to #it-ops
// ============================================================
// QUARTERLY AUDIT PATH
// Trigger: scheduled cron every 90 days at 08:00 UTC
// ============================================================
// Agent 2 reads Airtable view 'all_active_assignments'
// Sends 'quarterly_audit_request' template to each assigned_to_email
// Embeds Airtable form URL for structured confirmation response
// Form responses write directly to Airtable audit_confirmed field
// Non-responses after 5 days trigger 'overdue_audit_reminder' email
// ============================================================
// ERROR LOGGING (all agents)
// ============================================================
Supabase.INSERT INTO automation_error_log {
agent_name: string,
error_type: string,
serial_number: string | null,
payload: jsonb,
ts: timestamptz
}04Recommended build stack
More documents for this process
Every document generated for Device & Hardware Management.