Back to OKR & Goal Tracking

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

OKR & Goal Tracking Automation

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

This document is the authoritative technical reference for the FullSpec team building and deploying the OKR & Goal Tracking automation. It covers the full current-state process map with bottlenecks identified, precise specifications for each automation agent, the end-to-end data flow across all integrated tools, and the recommended build stack with estimated effort. Everything the build team needs to start, validate, and hand over the completed automation is contained here. The process owner does not need to action this document directly; it is written for the FullSpec specialist assigned to this build.

01Process snapshot

Step
Name
Description
1
Determine Update Cycle Timing
Ops manager checks the planning calendar to confirm which teams owe updates and which key results are in scope. Time cost: 15 min/cycle.
2
Send Manual Update Requests
Individual Slack messages or emails sent to each team lead with no standard format provided. Time cost: 25 min/cycle.
3 BOTTLENECK
Chase Non-Responders
Ops manager follows up with non-responders after 24 to 48 hours, often multiple times before the deadline. Time cost: 30 min/cycle.
4 BOTTLENECK
Collect and Interpret Replies
Free-text Slack messages and emails in varying formats are read manually and numbers and statuses are extracted by hand. Time cost: 35 min/cycle.
5
Enter Data into Master Spreadsheet
Each key result value is typed into the shared Google Sheet with columns for current value, target, owner, and RAG status. Time cost: 40 min/cycle.
6
Calculate Progress Percentages
Progress percentages calculated manually or via formula; RAG status assigned using rough mental thresholds. Time cost: 20 min/cycle.
7
Identify At-Risk Objectives
Ops manager reviews the completed sheet to spot objectives below threshold and prepares a short flags section. Time cost: 20 min/cycle.
8
Prepare Leadership Summary
Written summary of progress, red flags, and notable wins drafted in a Notion page ahead of the leadership sync. Time cost: 30 min/cycle.
9
Share Update with Leadership
Summary pasted into a Slack channel or emailed to the leadership group, often on the same day as the meeting. Time cost: 10 min/cycle.
10
Archive Prior Cycle Data
Previous cycle's sheet tab duplicated and renamed; backup consistency is unreliable. Time cost: 10 min/cycle.
Time cost summary: Total manual time per cycle is 235 minutes (approximately 3 hours 55 minutes). At a weekly cadence this equates to approximately 5 hours/week and 250 hours/year. Steps 1, 2, and 3 are replaced by the OKR Collection Agent. Steps 5, 6, 7, 8, 9, and 10 are replaced by the OKR Analysis and Reporting Agent. Step 4 (collect and interpret replies) is eliminated entirely because structured form inputs remove the need for free-text interpretation. One human step remains: reviewing at-risk flags and deciding on corrective action (approximately 20 minutes/cycle, retained by design).
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

OKR Collection Agent

Sends structured OKR update requests to each configured team lead via Slack on the agreed cycle schedule (weekly or fortnightly). Tracks submission status per team lead in Google Sheets in real time. Fires a first follow-up reminder at 24 hours and a second at 48 hours for any team lead who has not submitted. Closes the collection window at the configured deadline and logs all responses plus a non-responder list to the master Google Sheet. Eliminates all manual outreach, chasing, and free-text transcription from the current process.

Trigger
Scheduled cron trigger on the automation platform fires at the configured day and time each week or fortnight. No manual action required.
Tools
Slack (structured message delivery, form collection, reminder dispatch), Google Sheets (response logging, submission status tracking, non-responder list).
Replaces steps
Steps 1, 2, and 3 (cycle timing check, manual update requests, non-responder chasing).
Estimated build
14 hours. Complexity: Moderate.
// Input
schedule_trigger: { cadence: 'weekly' | 'fortnightly', day_of_week: string, time_utc: string }
team_leads: [ { slack_user_id: string, name: string, key_results: string[] } ]
collection_window_hours: number  // default 48

// Output (written to Google Sheets master OKR tab)
cycle_responses: [
  {
    cycle_date: string,           // ISO date of cycle trigger
    team_lead_id: string,
    team_lead_name: string,
    key_result_id: string,
    key_result_label: string,
    current_value: number,
    target_value: number,
    submitted_at: string,         // ISO timestamp
    submission_status: 'submitted' | 'missing'
  }
]
non_responder_log: [ { slack_user_id: string, name: string, reminders_sent: number } ]

// On reminder (24 h and 48 h)
slack_reminder_payload: {
  channel: slack_user_id,
  text: string,                  // templated follow-up message
  reminder_number: 1 | 2
}
  • Slack app tier requirement: the workspace must have a plan that supports the Slack Workflow Builder or allows incoming webhooks and bot message posting via the Slack Web API. Free Slack plans may not support interactive message components; if confirmed on free tier, fall back to a Google Form link posted via bot message and parse form responses via the Google Sheets API instead.
  • The Slack app must be installed with the following OAuth scopes: chat:write, chat:write.public, users:read, users:read.email, channels:read, and im:write. Confirm these scopes are approved before build begins (workspace admin approval required per lifecycle data: Slack app permissions granted May 8).
  • Team lead roster must be maintained as a named configuration table (tab: CONFIG_TeamLeads) in the master Google Sheet with columns: slack_user_id, name, active (boolean), key_result_ids (comma-separated). The agent reads this table at trigger time; adding or removing a team lead only requires a row edit.
  • Dedupe logic: if a team lead submits more than once within the same cycle window, only the most recent submission (by submitted_at timestamp) is written to the active cycle row. Previous submissions are appended to a separate audit log tab (tab: AUDIT_Submissions).
  • If a team lead has no Slack account or is in a different channel structure, a manual exception row must be pre-populated in the master sheet by the ops manager before the collection window opens. The agent must detect a pre-populated row and skip that user's Slack message, logging them as 'manual-entry' in submission_status.
  • The collection window close time must be configurable without a code change. Store it as a named range (CONFIG.collection_window_hours) in the Google Sheet so the ops manager can update it directly.
  • Confirm before build: agreed cycle cadence (weekly vs fortnightly), exact Slack channel IDs for each team lead DM, Google Sheet ID and tab names, and whether Google Workspace calendar integration is needed for the trigger or whether a fixed cron schedule is sufficient.
OKR Analysis and Reporting Agent

Reads the completed cycle response rows from the master Google Sheet once the collection window closes (or all team leads have submitted, whichever is earlier). Calculates progress percentage for each key result against its target value. Applies the configured RAG thresholds to assign a status of Green, Amber, or Red to each key result. Identifies objectives where one or more key results are Red or where the aggregate objective score falls below the at-risk threshold. Creates a new Notion page for the cycle using the standard cycle summary template. Posts a formatted digest to the designated leadership Slack channel, summarising overall OKR health, listing at-risk items, and linking to the Notion page. Archives the cycle data to a timestamped tab in the master Google Sheet.

Trigger
All responses logged in Google Sheets OR the submission window closes, whichever comes first. Triggered by a status check on the master sheet (all rows have submission_status = 'submitted' or 'missing', and cycle is closed).
Tools
Google Sheets (read cycle responses, write RAG status, archive tab), Notion (create cycle summary page), Slack (post leadership digest to channel).
Replaces steps
Steps 5, 6, 7, 8, 9, and 10 (data entry, progress calculation, at-risk identification, leadership summary preparation, sharing update, and archiving prior cycle data).
Estimated build
14 hours. Complexity: Moderate.
// Input (read from Google Sheets master OKR tab on trigger)
cycle_responses: [
  {
    cycle_date: string,
    team_lead_id: string,
    key_result_id: string,
    key_result_label: string,
    current_value: number,
    target_value: number,
    submission_status: 'submitted' | 'missing'
  }
]
rag_thresholds: {
  green_min_pct: number,         // e.g. 70 (>= 70% = Green)
  amber_min_pct: number,         // e.g. 40 (>= 40% = Amber, < 70%)
  red_below_pct: number          // e.g. 40 (< 40% = Red)
}
notion_template_id: string       // ID of the cycle summary template page in Notion
leadership_slack_channel_id: string

// Output: Google Sheets (calculated fields written back to master tab)
per_key_result: {
  progress_pct: number,          // (current_value / target_value) * 100
  rag_status: 'Green' | 'Amber' | 'Red',
  at_risk_flag: boolean
}

// Output: Notion page created
notion_page: {
  parent_database_id: string,
  title: string,                 // 'OKR Cycle Summary - {cycle_date}'
  properties: {
    cycle_date: string,
    overall_rag: 'Green' | 'Amber' | 'Red',
    at_risk_count: number,
    submission_rate_pct: number
  },
  content_blocks: [              // progress table + at-risk list + auto-commentary
    'progress_table',
    'at_risk_objectives_list',
    'auto_generated_commentary'
  ]
}

// Output: Slack digest posted to leadership channel
slack_digest: {
  channel: leadership_slack_channel_id,
  blocks: [
    { type: 'header', text: 'OKR Cycle Summary - {cycle_date}' },
    { type: 'section', text: 'Overall health: {overall_rag}' },
    { type: 'section', text: 'Submission rate: {submission_rate_pct}%' },
    { type: 'section', text: 'At-risk objectives: {at_risk_count}' },
    { type: 'actions', elements: [ { type: 'button', text: 'View full report', url: notion_page_url } ] }
  ]
}

// Archive output
archived_tab_name: string        // format: 'ARCHIVE_{cycle_date}' e.g. 'ARCHIVE_2025-05-19'
  • RAG thresholds must be agreed and signed off by the ops manager and founder before the Analysis Agent build begins. Store thresholds as named ranges (CONFIG.green_min_pct, CONFIG.amber_min_pct, CONFIG.red_below_pct) in the Google Sheet so they can be updated without a code change.
  • Notion integration requires a Notion integration token with the following capabilities: read content, insert content, and update content on the OKR workspace. The integration must be explicitly shared with the parent database or page that will contain cycle summary pages. Confirm the Notion workspace tier supports API access (free Notion plans have API access; confirm no enterprise SSO restrictions apply).
  • The Notion cycle summary template page must be created and its page ID confirmed before build. The agent duplicates this template page for each cycle rather than building page content from scratch, ensuring formatting consistency.
  • The leadership Slack channel ID must be confirmed and the Slack bot must be added as a member of that channel before the first test run. Use the Slack API channels.info endpoint to validate the channel ID during build.
  • Progress percentage calculation: progress_pct = (current_value / target_value) * 100, capped at 100% for display purposes. Key results where target_value is 0 must be flagged as a configuration error and skipped rather than producing a divide-by-zero exception.
  • The auto-generated commentary in the Notion page uses a simple template approach: three fixed sections (On Track, At Risk, Missing Data) populated from the calculated RAG rows. Do not use an LLM for commentary generation unless explicitly scoped later. Keep it deterministic and auditable.
  • Archive tab creation must be idempotent: if a tab with the same cycle_date name already exists (e.g. from a rerun), append a suffix (_v2) rather than overwriting. Log the action to the AUDIT_Submissions tab.
  • Confirm before build: Notion integration token and parent database ID, leadership Slack channel ID, agreed RAG threshold values, and whether submission_rate_pct below a configurable threshold should block digest delivery or include a warning banner instead.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow: OKR & Goal Tracking Automation
// ─────────────────────────────────────────────────────────────────
// TRIGGER: Scheduled cron fires on automation platform
// ─────────────────────────────────────────────────────────────────
trigger_event: {
  cadence: 'weekly' | 'fortnightly',
  fired_at: ISO_timestamp,
  cycle_date: ISO_date          // used as primary key for this cycle
}

// ────────────────────────────────────��────────────────────────────
// AGENT 1: OKR Collection Agent
// ─────────────────────────────────────────────────────────────────

// Step 1: Read team lead roster from Google Sheets
GoogleSheets.read(sheet_id, tab='CONFIG_TeamLeads') -> team_leads: [
  { slack_user_id, name, active: true, key_result_ids: string[] }
]

// Step 2: Send structured Slack message to each active team lead
for each team_lead in team_leads where active == true:
  Slack.chat_postMessage({
    channel: team_lead.slack_user_id,
    text: 'OKR update due for cycle {cycle_date}',
    blocks: [
      { type: 'input', label: 'Key result: {kr_label}',
        element: { type: 'number_input', action_id: 'kr_{key_result_id}_value' } },
      ...(one input block per key_result_id)
      { type: 'actions', elements: [ { type: 'button', text: 'Submit', action_id: 'submit_okr' } ] }
    ]
  })
  GoogleSheets.writeRow(tab='LIVE_Cycle', {
    cycle_date, team_lead_id, submission_status: 'pending',
    reminder_count: 0, last_action_at: fired_at
  })

// Step 3: Wait loop — poll submission status at 24 h intervals
at T+24h:
  missing = GoogleSheets.query(tab='LIVE_Cycle',
    filter={ cycle_date, submission_status: 'pending' })
  for each m in missing:
    Slack.chat_postMessage({ channel: m.slack_user_id,
      text: 'Reminder 1: OKR update still needed for {cycle_date}' })
    GoogleSheets.updateRow(tab='LIVE_Cycle',
      { reminder_count: 1, last_action_at: now() })

at T+48h (or collection_window_close, whichever is first):
  missing = GoogleSheets.query(tab='LIVE_Cycle',
    filter={ cycle_date, submission_status: 'pending' })
  for each m in missing:
    Slack.chat_postMessage({ channel: m.slack_user_id,
      text: 'Final reminder: window closes in 1 hour for {cycle_date}' })
    GoogleSheets.updateRow(tab='LIVE_Cycle',
      { reminder_count: 2, last_action_at: now() })

// Step 4: Handle Slack form submission callback
Slack.interactivity_callback -> payload: {
  user: { id: slack_user_id },
  actions: [ { action_id: 'submit_okr', ... } ],
  state: { values: { kr_{key_result_id}_value: { value: number } } }
}
parse payload ->
  for each key_result_id in team_lead.key_result_ids:
    GoogleSheets.writeRow(tab='MASTER_OKR', {
      cycle_date,
      team_lead_id:    payload.user.id,
      team_lead_name:  lookup(team_leads, slack_user_id).name,
      key_result_id:   key_result_id,
      key_result_label: lookup(CONFIG_KRs, key_result_id).label,
      current_value:   payload.state.values[kr_{key_result_id}_value],
      target_value:    lookup(CONFIG_KRs, key_result_id).target_value,
      submitted_at:    now(),
      submission_status: 'submitted'
    })
  GoogleSheets.updateRow(tab='LIVE_Cycle',
    { submission_status: 'submitted', submitted_at: now() })

// Step 5: Collection window close — mark remaining as 'missing'
GoogleSheets.updateRows(tab='LIVE_Cycle',
  filter={ cycle_date, submission_status: 'pending' },
  set={ submission_status: 'missing' })
GoogleSheets.writeTab(tab='AUDIT_NonResponders', {
  cycle_date, non_responders: [ { slack_user_id, name, reminders_sent } ]
})

// ─────────────────────────────────────────────────────────────────
// AGENT HANDOFF: OKR Collection Agent -> OKR Analysis and Reporting Agent
// Condition: all LIVE_Cycle rows for cycle_date have status != 'pending'
// Handoff payload: cycle_date (string) passed to Analysis Agent trigger
// ─────────────────────────────────────────────────────────────────

// ─────────────────────────────────────────────────────────────────
// AGENT 2: OKR Analysis and Reporting Agent
// ─────────────────────────────────────────────────────────────────

// Step 6: Read submitted key result values for the cycle
GoogleSheets.query(tab='MASTER_OKR',
  filter={ cycle_date }) -> cycle_rows: [
  { key_result_id, key_result_label, current_value,
    target_value, team_lead_name, submission_status }
]

// Step 7: Calculate progress and assign RAG status
rag_config = GoogleSheets.readNamedRanges([
  'CONFIG.green_min_pct',   // e.g. 70
  'CONFIG.amber_min_pct',   // e.g. 40
  'CONFIG.red_below_pct'    // e.g. 40
])

for each row in cycle_rows where submission_status == 'submitted':
  if target_value == 0: flag as CONFIG_ERROR, skip
  progress_pct = min((current_value / target_value) * 100, 100)
  rag_status = progress_pct >= rag_config.green_min_pct ? 'Green'
             : progress_pct >= rag_config.amber_min_pct ? 'Amber'
             : 'Red'
  at_risk_flag = rag_status == 'Red'
  GoogleSheets.updateRow(tab='MASTER_OKR', {
    key_result_id, cycle_date,
    progress_pct, rag_status, at_risk_flag
  })

// Step 8: Aggregate cycle-level summary
at_risk_krs    = cycle_rows.filter(at_risk_flag == true)
submitted_count = cycle_rows.filter(submission_status == 'submitted').length
total_count     = cycle_rows.length
submission_rate_pct = (submitted_count / total_count) * 100
overall_rag     = at_risk_krs.length > 0 ? 'Red' : 'Green'  // simplified

// Step 9: Create Notion cycle summary page
Notion.duplicatePage(template_id: notion_template_id) -> new_page_id
Notion.updatePage(new_page_id, {
  title: 'OKR Cycle Summary - {cycle_date}',
  properties: {
    cycle_date, overall_rag,
    at_risk_count: at_risk_krs.length,
    submission_rate_pct
  }
})
Notion.appendBlocks(new_page_id, [
  progress_table_block(cycle_rows),    // table: kr_label, progress_pct, rag_status, team_lead
  at_risk_list_block(at_risk_krs),     // bulleted list of Red key results
  commentary_block(cycle_rows)         // templated text: On Track / At Risk / Missing Data
])
notion_page_url = Notion.getPageUrl(new_page_id)

// Step 10: Post leadership digest to Slack
Slack.chat_postMessage({
  channel: CONFIG.leadership_slack_channel_id,
  blocks: [
    { type: 'header',  text: 'OKR Cycle Summary - {cycle_date}' },
    { type: 'section', text: 'Overall health: {overall_rag} | Submissions: {submission_rate_pct}%' },
    { type: 'section', text: 'At-risk objectives: {at_risk_krs.length}' },
    { type: 'section', text: at_risk_krs.map(kr => '{kr_label} ({team_lead_name}): {progress_pct}%') },
    { type: 'actions', elements: [
        { type: 'button', text: 'View full Notion report', url: notion_page_url }
    ]}
  ]
})

// Step 11: Archive cycle data
archive_tab_name = 'ARCHIVE_{cycle_date}'
if GoogleSheets.tabExists(archive_tab_name):
  archive_tab_name = archive_tab_name + '_v2'
GoogleSheets.duplicateTab(tab='MASTER_OKR', new_name=archive_tab_name)
GoogleSheets.appendRow(tab='AUDIT_Submissions', {
  cycle_date, archive_tab_name, archived_at: now()
})

// ─────────────────────────────────────────────────────────────────
// END: Cycle complete. Human reviews at-risk flags. No auto-action taken.
// ─────────────────────────────────────────────────────────────────
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation tool (platform TBC). Implement one workflow per agent: Workflow 1 (OKR Collection Agent) and Workflow 2 (OKR Analysis and Reporting Agent). Use a shared credential store so Slack, Google Sheets, and Notion credentials are defined once and referenced across both workflows. No credentials are hardcoded in workflow logic.
Webhook configuration
A dedicated HTTPS webhook endpoint is registered in the Slack app configuration under Interactivity and Shortcuts to receive form submission callbacks. The orchestration layer exposes this endpoint. Validate the Slack request signature (X-Slack-Signature header using the app signing secret) on every incoming payload before processing. A second webhook endpoint handles Slack shortcut or slash command events if an on-demand trigger is required outside the scheduled cycle.
Templating approach
All user-facing Slack message text (initial request, reminder 1, reminder 2, leadership digest) is stored as configurable string templates in the workflow, not hardcoded. Notion page content is generated by duplicating a pre-built Notion template page (notion_template_id stored in config) and appending structured blocks via the Notion API. No LLM or AI text generation is used; all commentary is deterministic and template-driven, ensuring auditability.
Error logging
All exceptions (Slack API errors, Google Sheets write failures, Notion API errors, divide-by-zero on target_value, missing config values) are caught at the workflow level and written to a Supabase errors table with fields: workflow_name, step_name, error_code, error_message, cycle_date, occurred_at. A Slack alert is posted to a designated ops/alerts channel whenever an error row is inserted. The Supabase table is retained for 90 days and reviewed weekly by the FullSpec team during the monitored period post-launch.
Testing approach
All builds begin in sandbox environments. Google Sheets sandbox: a duplicate of the master sheet with a tab prefix of TEST_. Slack sandbox: a private test channel with bot access, sending messages to the FullSpec build team member rather than live team leads. Notion sandbox: a duplicate template page in a Notion test database. RAG threshold logic is unit-tested against a fixed set of known input/output pairs before connecting to live data. End-to-end cycle is run twice with test data before any live cycle runs.
Estimated total build time
OKR Collection Agent: 14 hours. OKR Analysis and Reporting Agent: 14 hours. End-to-end integration testing, sandbox-to-live transition, and documentation: 4 hours. Total: 32 hours. This aligns with the confirmed build effort of 32 hours from the process template.
Pre-build blockers to resolve before any code is written: (1) Slack app OAuth scopes approved by workspace admin and bot installed in the workspace. (2) Google Sheets master OKR sheet ID confirmed, tab naming convention agreed, and CONFIG_TeamLeads and CONFIG_KRs tabs populated with at least one real team lead and key result for testing. (3) Notion integration token created, shared with the parent OKR database, and the cycle summary template page built and its page ID confirmed. (4) RAG thresholds (green/amber/red percentage boundaries) signed off by the ops manager and founder. (5) Leadership Slack channel ID confirmed and bot added as a member. Raise any of these at support@gofullspec.com if they are not resolved before the scheduled build start date.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for OKR & Goal Tracking.

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