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
New Initiative Scoping and Approval
[YourCompany.com] · Management Department · Prepared by FullSpec · [Today's Date]
This document is the primary technical reference for the FullSpec team building the New Initiative Scoping and Approval automation. It covers the full current-state step map, detailed agent specifications with IO contracts, the end-to-end data flow trace, and the recommended build stack. Everything a builder needs to scope, configure, and validate the three-agent system is contained here. No external parties are involved: FullSpec handles all build, integration, and testing work. Where you or your team have responsibilities, for example supplying API credentials or confirming routing categories, those are called out explicitly.
01Process snapshot
02Agent specifications
Fires immediately when a Google Forms submission is received via webhook. The agent evaluates each response field against a completeness checklist: presence, minimum word count (strategic rationale must be at least 30 words; estimated budget and estimated effort must be non-null numeric values; affected departments must have at least one selection). If any field fails, the agent composes a targeted rejection message listing the specific failing fields by name and sends it to the proposer via Slack DM and Gmail. The initiative is not logged or forwarded until all fields pass. If all fields pass, the agent emits a validated payload to the Scoping and Routing Agent. Estimated build time: 8 hours. Complexity: Moderate.
// Input
form_response: {
proposer_name: string,
proposer_email: string,
initiative_title: string,
strategic_rationale: string, // min 30 words
estimated_effort_days: number,
estimated_budget_usd: number,
affected_departments: string[], // min 1 item
initiative_category: string // must match routing table enum
}
// Validation rules
required_fields: [strategic_rationale, estimated_effort_days,
estimated_budget_usd, affected_departments, initiative_category]
word_count_min: { strategic_rationale: 30 }
numeric_min: { estimated_effort_days: 0.5, estimated_budget_usd: 0 }
// Output (pass)
validated_payload: {
...form_response,
validation_status: 'passed',
validated_at: ISO8601_timestamp
}
// Output (fail)
rejection_message: {
channel: 'slack_dm + gmail',
recipient_email: proposer_email,
failing_fields: string[],
message_template: 'INTAKE_REJECTION_V1'
}- Google Forms must be on a Google Workspace account with Pub/Sub or Apps Script webhook configured. A free personal Gmail account does not support reliable push notifications; confirm Workspace tier before build.
- The initiative_category field in the form must use a dropdown with a fixed enum list. This list must be finalised and locked before build starts because the Scoping and Routing Agent routing table is keyed against it.
- Slack DM delivery requires the Slack bot to be installed in the workspace with chat:write and users:read.email OAuth scopes. The bot must be able to look up the proposer's Slack user ID by email address.
- Gmail send from the validation agent requires a dedicated service account or an OAuth2 credential scoped to gmail.send. Do not reuse the digest sender credential to avoid rate limit collisions.
- Dedupe: if the same proposer_email submits the same initiative_title within a 24-hour window, suppress the second trigger and log a duplicate_suppressed event to the error log table. Do not send a second rejection or pass.
- Fallback: if the Slack DM fails to deliver (user not found by email), fall back to Gmail only and log a slack_dm_failed event.
Fires after the Intake Validation Agent emits a validated payload. This agent performs three sequential actions: it creates a structured Notion scoping page using the locked page template, populating all fields from the validated payload and stamping a unique reference ID in the format INIT-YYYY-MM-NNN; it appends a new row to the Google Sheets initiative tracker; and it determines the correct reviewer from the routing table keyed on initiative_category, calculates a review deadline of three business days from submission_date (Monday to Friday calendar, no public holidays adjustment by default), and sends a Slack channel message to the assigned reviewer's DM with the Notion page URL and the deadline date. Estimated build time: 14 hours. Complexity: Complex.
// Input
validated_payload: {
proposer_name: string,
proposer_email: string,
initiative_title: string,
strategic_rationale: string,
estimated_effort_days: number,
estimated_budget_usd: number,
affected_departments: string[],
initiative_category: string,
validation_status: 'passed',
validated_at: ISO8601_timestamp
}
// Notion page created
notion_page: {
reference_id: 'INIT-YYYY-MM-NNN',
title: initiative_title,
proposer: proposer_name,
strategic_rationale: strategic_rationale,
estimated_effort_days: estimated_effort_days,
estimated_budget_usd: estimated_budget_usd,
affected_departments: affected_departments,
category: initiative_category,
status: 'Pending Review',
submitted_at: validated_at,
reviewer_name: string, // resolved from routing table
review_deadline: ISO8601_date
}
// Google Sheets row appended
sheets_row: {
reference_id, proposer_name, proposer_email,
initiative_title, initiative_category,
estimated_budget_usd, submission_date,
reviewer_name, review_deadline,
status: 'Pending Review',
notion_page_url: string
}
// Slack notification sent
slack_message: {
recipient: reviewer_slack_id,
notion_url: string,
review_deadline: ISO8601_date,
template: 'REVIEWER_NOTIFY_V1'
}- Notion integration requires an internal integration token with insert content and read content permissions on the parent database. The locked scoping page template must be duplicated as a database template in Notion, not a standalone page, so the API can create instances from it.
- The Notion database property names used in the API call must match the template exactly (case-sensitive). Confirm the final template field names before writing the page creation step. If the template is updated post-launch, field mappings must be updated in the agent configuration.
- The routing table (initiative_category to reviewer_name and reviewer_slack_id) must be stored in a separate Google Sheets tab named ROUTING_TABLE with columns: category, reviewer_name, reviewer_email, reviewer_slack_id. The agent reads this tab at runtime.
- Business day calculation for the three-day review deadline must use a helper function that skips Saturday and Sunday. If the business operates non-standard calendars, the public holiday list must be supplied as a configuration array before build.
- Google Sheets API requires the sheets service account to have Editor access on the tracker spreadsheet. Confirm the spreadsheet ID and the exact tab name (suggested: INITIATIVE_TRACKER) before build.
- Fallback: if Notion page creation fails, the agent must not write the Sheets row or send the Slack message. Log a notion_create_failed event with the full payload so the run can be retried without data duplication.
Runs on a scheduled trigger every weekday at 08:00 AM in the business's local timezone. The agent queries the Google Sheets tracker for all rows where the status column equals 'Awaiting Leadership Decision'. If the result set is empty, the agent exits silently. If one or more rows are found, the agent fetches the corresponding Notion page for each to retrieve the reviewer recommendation, reviewer notes, and estimated budget. It compiles a formatted HTML digest email listing each initiative with its reference ID, title, reviewer recommendation, reviewer notes, estimated budget, and a direct link to the Notion page. It calculates the total pipeline budget from the estimated_budget_usd values of all pending rows and includes this figure in the digest header. The digest is sent to the configured leadership recipient (founder or CEO) via Gmail. Estimated build time: 10 hours. Complexity: Moderate.
// Input (from Google Sheets query)
pending_rows: [
{
reference_id: string,
initiative_title: string,
proposer_name: string,
estimated_budget_usd: number,
reviewer_name: string,
notion_page_url: string,
status: 'Awaiting Leadership Decision'
}
]
// Input (from Notion per page, fetched at runtime)
notion_review_data: {
reviewer_recommendation: 'Approve' | 'Revise' | 'Decline',
reviewer_notes: string,
review_completed_at: ISO8601_timestamp
}
// Output: Gmail digest
gmail_send: {
to: leadership_email, // configured at build time
subject: 'Initiative Digest: [N] decisions pending — [Date]',
body_format: 'HTML',
template: 'LEADERSHIP_DIGEST_V1',
fields_per_item: [reference_id, initiative_title, proposer_name,
reviewer_recommendation, reviewer_notes,
estimated_budget_usd, notion_page_url],
digest_header: { total_pending: number, total_pipeline_budget_usd: number }
}
// On leadership decision recorded in Notion
// (triggered by Notion database automation or webhook)
decision_writeback: {
sheets_row_update: { reference_id, status: 'Approved|Revised|Declined',
decision_date: ISO8601_date, decision_notes: string },
slack_notify_proposer: {
recipient: proposer_slack_id,
outcome: string,
conditions: string,
next_steps: string,
template: 'PROPOSER_OUTCOME_V1'
}
}- The digest Gmail sender must be a shared mailbox or service account, not a personal Gmail. Configure an OAuth2 credential with gmail.send scope. Confirm the leadership recipient email address before build and store it as an environment variable, not hardcoded.
- The Notion query to retrieve reviewer_recommendation and reviewer_notes requires that these fields exist as select and text properties respectively on the scoping page database. Confirm property names during template finalisation.
- The decision writeback to Google Sheets and Slack must be triggered by a Notion database property change event (status field moving to 'Approved', 'Revised', or 'Declined'). Configure a Notion webhook or poll on a 15-minute interval as a fallback if webhooks are not available on the current Notion plan.
- If the pending_rows result set is empty at 08:00 AM, the agent must exit without sending any email. Do not send a 'nothing pending' digest to leadership.
- Gmail send rate limit is 100 messages per 100 seconds per user. The digest is a single message so rate limits are not a concern, but confirm the service account is not shared with other automation sends.
- Timezone configuration: the 08:00 AM schedule must be set in the automation platform using the business's local timezone (IANA format, e.g. America/New_York). Confirm with the process owner before build.
03End-to-end data flow
// ============================================================
// NEW INITIATIVE SCOPING AND APPROVAL — END-TO-END DATA FLOW
// ============================================================
// TRIGGER
// Proposer submits Google Forms intake form
GOOGLE_FORMS.submission -> {
proposer_name: string,
proposer_email: string,
initiative_title: string,
strategic_rationale: string,
estimated_effort_days: number,
estimated_budget_usd: number,
affected_departments: string[],
initiative_category: string,
submitted_at: ISO8601_timestamp
}
// ============================================================
// AGENT 1: INTAKE VALIDATION AGENT
// ============================================================
// Receives raw form payload via webhook
// Validates completeness against required field checklist
INTAKE_VALIDATION_AGENT.run(
input: GOOGLE_FORMS.submission
)
// Branch A: validation fails
if validation_status == 'failed' {
failing_fields: string[] // e.g. ['strategic_rationale', 'estimated_budget_usd']
SLACK.send_dm(recipient: proposer_slack_id,
template: 'INTAKE_REJECTION_V1',
fields: failing_fields)
GMAIL.send(to: proposer_email,
template: 'INTAKE_REJECTION_V1',
fields: failing_fields)
-> EXIT (no downstream agents triggered)
}
// Branch B: validation passes
if validation_status == 'passed' {
validated_payload: {
...GOOGLE_FORMS.submission,
validation_status: 'passed',
validated_at: ISO8601_timestamp
}
-> HANDOFF TO AGENT 2
}
// ============================================================
// AGENT 2: SCOPING AND ROUTING AGENT
// ============================================================
// Receives validated_payload from Agent 1
SCOPING_AND_ROUTING_AGENT.run(
input: validated_payload
)
// Step 2a: Generate unique reference ID
reference_id = 'INIT-' + YYYY + '-' + MM + '-' + NNN // NNN = zero-padded sequence
// Step 2b: Create Notion scoping page
NOTION.pages.create({
database_id: INITIATIVE_DB_ID,
properties: {
'Reference ID': reference_id,
'Title': initiative_title,
'Proposer': proposer_name,
'Proposer Email': proposer_email,
'Strategic Rationale': strategic_rationale,
'Estimated Effort (Days)': estimated_effort_days,
'Estimated Budget (USD)': estimated_budget_usd,
'Affected Departments': affected_departments,
'Category': initiative_category,
'Status': 'Pending Review',
'Submitted At': validated_at,
'Reviewer': reviewer_name, // resolved from ROUTING_TABLE
'Review Deadline': review_deadline // validated_at + 3 business days
}
}) -> notion_page_url: string
// Step 2c: Append row to Google Sheets tracker
GOOGLE_SHEETS.rows.append({
spreadsheet_id: TRACKER_SPREADSHEET_ID,
sheet_name: 'INITIATIVE_TRACKER',
row: {
reference_id, proposer_name, proposer_email,
initiative_title, initiative_category,
estimated_budget_usd, submission_date: validated_at,
reviewer_name, review_deadline,
status: 'Pending Review',
notion_page_url
}
})
// Step 2d: Resolve reviewer from routing table
GOOGLE_SHEETS.rows.query({
sheet_name: 'ROUTING_TABLE',
filter: { category: initiative_category }
}) -> { reviewer_name, reviewer_email, reviewer_slack_id }
// Step 2e: Notify reviewer via Slack
SLACK.send_dm({
recipient: reviewer_slack_id,
template: 'REVIEWER_NOTIFY_V1',
fields: { reference_id, initiative_title, notion_page_url, review_deadline }
})
// -> HUMAN STEP: Reviewer completes structured Google Form assessment
// (Google Form linked from Notion page; no agent involvement until submission)
// On reviewer form submission:
GOOGLE_FORMS.reviewer_submission -> {
reference_id: string,
reviewer_recommendation: 'Approve' | 'Revise' | 'Decline',
reviewer_notes: string,
resource_feasibility_flag: boolean,
review_completed_at: ISO8601_timestamp
}
// Step 2f: Write reviewer outcome back to Notion
NOTION.pages.update({
page_id: resolved_from_reference_id,
properties: {
'Reviewer Recommendation': reviewer_recommendation,
'Reviewer Notes': reviewer_notes,
'Resource Feasibility': resource_feasibility_flag,
'Review Completed At': review_completed_at,
'Status': 'Awaiting Leadership Decision' // if Approve or Revise
}
})
// Step 2g: Update Google Sheets status
GOOGLE_SHEETS.rows.update({
filter: { reference_id },
fields: { status: 'Awaiting Leadership Decision' }
})
// ============================================================
// AGENT 3: APPROVAL DIGEST AGENT
// ============================================================
// Scheduled trigger: Monday to Friday, 08:00 AM local timezone
APPROVAL_DIGEST_AGENT.run()
// Step 3a: Query tracker for pending leadership decisions
GOOGLE_SHEETS.rows.query({
sheet_name: 'INITIATIVE_TRACKER',
filter: { status: 'Awaiting Leadership Decision' }
}) -> pending_rows: [{ reference_id, initiative_title, proposer_name,
estimated_budget_usd, reviewer_name, notion_page_url }]
// Step 3b: Fetch reviewer data from Notion per initiative
for each row in pending_rows:
NOTION.pages.retrieve({ page_id: resolved_from_reference_id })
-> { reviewer_recommendation, reviewer_notes, review_completed_at }
// Step 3c: Compile and send Gmail digest
GMAIL.send({
to: LEADERSHIP_EMAIL,
subject: 'Initiative Digest: [N] decisions pending — [Date]',
body: HTML compiled from LEADERSHIP_DIGEST_V1 template,
digest_header: {
total_pending: pending_rows.length,
total_pipeline_budget_usd: SUM(pending_rows.estimated_budget_usd)
}
})
// -> HUMAN STEP: Leadership records decision in Notion
// (Status field updated to 'Approved', 'Revised', or 'Declined')
// Step 3d: On Notion status change -> decision writeback
NOTION.webhook(event: 'page.property_updated', property: 'Status') -> {
reference_id, new_status, decision_notes, decision_date
}
// Step 3e: Update Google Sheets tracker
GOOGLE_SHEETS.rows.update({
filter: { reference_id },
fields: { status: new_status, decision_date, decision_notes }
})
// Step 3f: Notify proposer via Slack
SLACK.send_dm({
recipient: proposer_slack_id, // looked up from proposer_email
template: 'PROPOSER_OUTCOME_V1',
fields: { initiative_title, new_status, decision_notes, next_steps }
})
// ============================================================
// END OF FLOW
// ============================================================04Recommended build stack
More documents for this process
Every document generated for New Initiative Scoping & Approval.