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
Competitor Monitoring Automation
[YourCompany.com] · Marketing Department · Prepared by FullSpec · [Today's Date]
This document gives the FullSpec build team everything needed to construct, wire, and ship the Competitor Monitoring automation end to end. It covers the current manual process that is being replaced, full specifications for both agents, the complete data flow from trigger to final output, and the recommended build stack with total time estimates. The owner side of this process is handled entirely by [YourCompany.com]'s marketing team. FullSpec owns all build, configuration, testing, and deployment work described here.
01Process snapshot
02Agent specifications
This agent runs on a fixed weekly schedule and is responsible for pulling structured competitor intelligence from three external sources: SEMrush (keyword rankings, traffic estimates, paid ad keywords), Ahrefs (new backlinks, recently published content, domain rating changes), and Google Alerts (news mentions and press items via RSS). Outputs from all three sources are normalised into a single consistent JSON dataset keyed by competitor identifier before being staged for the Intelligence Summariser Agent. The agent must handle partial failures gracefully: if one source returns an error, it logs the failure and continues with available data rather than aborting the full run.
// Input
trigger: { schedule: 'CRON 0 7 * * 1', timezone: 'America/New_York' }
config: {
competitors: GoogleSheets.range('CompetitorList!A2:E') -> [
{ competitor_id: string, name: string, domain: string, semrush_domain: string, ahrefs_target: string }
],
alerts_rss_urls: GoogleSheets.range('AlertFeeds!A2:B') -> [
{ competitor_id: string, rss_url: string }
],
lookback_days: 7
}
// Output
staged_dataset: {
run_id: uuid,
run_timestamp: ISO8601,
competitors: [
{
competitor_id: string,
name: string,
domain: string,
semrush: {
organic_keywords_delta: integer,
estimated_traffic_delta: integer,
new_paid_keywords: [ { keyword: string, position: integer } ],
data_status: 'ok' | 'error' | 'partial'
},
ahrefs: {
new_backlinks: [ { source_domain: string, target_url: string, dr: integer } ],
new_content_urls: [ { url: string, title: string, published_date: date } ],
dr_change: integer,
data_status: 'ok' | 'error' | 'partial'
},
alerts: [
{ title: string, url: string, source: string, published_date: date }
],
source_errors: [ { source: string, error_message: string } ]
}
]
}- SEMrush API access requires a Guru plan or above for full API access including domain analytics endpoints. Confirm the current subscription tier before scoping; the Business plan increases the daily API unit cap from 10,000 to 50,000, which matters if tracking more than 5 competitors. Use the /domain/organic/competitors endpoint for ranking data and /domain/adwords/keywords for paid keywords.
- Ahrefs API access is only available on the Advanced plan or above. Confirm this with the client before build start. Use the /v3/site-explorer/new-backlinks endpoint with the mode=subdomains parameter. Set the date_from field to exactly 7 days before the run timestamp to avoid duplicates across cycles.
- Google Alerts RSS feeds must be manually created and stored in the AlertFeeds tab of the Google Sheet. The automation reads these URLs at runtime. The build team must verify each URL returns a valid RSS 2.0 response before marking the agent complete.
- Normalisation: all competitor data must be keyed by a stable competitor_id (e.g. 'comp_001') that matches the value in the CompetitorList sheet. This ID is the join key for the downstream agent and the Sheets tracker.
- Deduplication: backlink entries should be deduplicated against the previous run's output using source_domain + target_url as the composite key. Store the previous run's ahrefs backlink list in a persistent cache (Supabase table: competitor_backlink_cache) keyed by run_week.
- Fallback behaviour: if SEMrush or Ahrefs returns a non-2xx response after two retries, set data_status to 'error', populate source_errors, and continue. The summariser must handle missing source blocks gracefully and note the gap in the digest.
- Confirm with the marketing manager before build: what is the full list of competitors to track, and are all required Google Alert RSS feeds already configured? This is a hard dependency before the agent can be fully wired.
This agent receives the staged JSON dataset produced by the Data Collection Agent and uses an AI language model to write a structured weekly intelligence digest in plain English. The digest includes a highlights section covering the most significant changes across all competitors, followed by one section per tracked competitor. The agent also applies a significance threshold: if any single metric change exceeds a defined threshold (e.g. DR change greater than or equal to 5, organic traffic delta greater than or equal to 15%, or more than 3 new high-DR backlinks), the run is flagged for human review by the marketing manager before distribution. Regardless of the flag outcome, the digest is always appended to the Google Sheet tracker and posted to Slack. A summary note is also logged in HubSpot against the designated marketing campaign record.
// Input
staged_dataset: { run_id, run_timestamp, competitors: [ ... ] } // from Data Collection Agent
prompt_config: {
digest_format: 'section_per_competitor_with_highlights',
highlight_count: 5,
significance_rules: {
dr_change_threshold: 5,
traffic_delta_pct_threshold: 15,
new_high_dr_backlinks_threshold: 3,
high_dr_minimum: 60
},
tone: 'concise, factual, actionable',
max_tokens_per_competitor: 300
}
// Output
digest: {
run_id: uuid,
week_ending: date,
significant_change_flagged: boolean,
flag_reason: string | null,
highlights_markdown: string,
competitor_sections: [
{
competitor_id: string,
name: string,
summary_markdown: string,
key_metrics: {
organic_keywords_delta: integer,
traffic_delta: integer,
new_backlinks_count: integer,
new_content_count: integer,
alert_count: integer
}
}
]
}
// On approval (significant_change_flagged = true)
// Marketing manager receives a Slack DM with the flag reason and a prompt to review
// The digest is held in a 'pending_review' state in Supabase for up to 4 hours
// If no response is received within 4 hours, the digest is published automatically
// and the flag is logged in the competitor_runs audit table with status 'auto_released'
slack_dm_payload: {
channel: '@marketing_manager_slack_id',
text: 'Significant change flagged in this week\'s competitor digest. Reason: {flag_reason}. Please review before 11 a.m. or it will publish automatically.',
action_buttons: [ 'Approve and publish', 'Edit before publishing' ]
}
google_sheets_append: {
sheet: 'WeeklyLog',
row: [ week_ending, competitor_name, organic_keywords_delta, traffic_delta,
new_backlinks_count, new_content_count, alert_count, significant_change_flagged ]
}
slack_channel_post: {
channel: '#competitor-intelligence',
main_message: highlights_markdown,
thread_replies: [ competitor_sections[0..n].summary_markdown ]
}
hubspot_note: {
object_type: 'company' | 'deal',
object_id: configured_hubspot_record_id,
note_body: 'Weekly competitor digest logged. Week ending {week_ending}. Significant flag: {significant_change_flagged}.'
}- The AI prompt template must be stored as a versioned string in the build configuration, not hardcoded. This allows the marketing manager to request prompt adjustments without a redeploy. Store the active prompt version in a Supabase config table (table: agent_config, key: summariser_prompt_v).
- The Slack integration requires a bot token with the chat:write and im:write OAuth scopes. The Slack channel '#competitor-intelligence' must exist and the bot must be invited before the first live run. The marketing manager's Slack user ID must be confirmed and stored in the credential store before build.
- Google Sheets append uses the Sheets API v4 with an OAuth 2.0 service account. The service account must be granted Editor access to the tracker spreadsheet. Sheet name is 'WeeklyLog'; confirm the exact spreadsheet ID with the client before wiring.
- HubSpot note logging uses the CRM Notes API (POST /crm/v3/objects/notes). The associated object type (company or deal) and the specific record ID must be confirmed with the marketing manager before build. Use a private app token; do not use OAuth for this integration.
- The significance flagging logic must be implemented in the orchestration layer before the LLM call, using the raw numeric fields from the staged dataset. Do not rely on the LLM to determine whether a flag is warranted.
- If significant_change_flagged is true, the Slack DM must fire before the channel post. The 4-hour auto-release window is a hard requirement confirmed with the process owner.
- Confirm before build: the HubSpot record ID to log notes against, the exact Slack channel name, the Google Sheets spreadsheet ID, and whether the LLM provider preference is OpenAI GPT-4o or an alternative.
03End-to-end data flow
// ── TRIGGER ────────────────────────────────────────────────────────────────
schedule_trigger: CRON '0 7 * * 1' // Monday 7:00 a.m.
-> emit: { run_id: uuid, run_timestamp: ISO8601 }
// ── DATA COLLECTION AGENT ──────────────────────────────────────────────────
// Step 1: Load competitor config from Google Sheets
GoogleSheets.get('CompetitorList!A2:E') -> competitors[]
fields: [ competitor_id, name, domain, semrush_domain, ahrefs_target ]
GoogleSheets.get('AlertFeeds!A2:B') -> alert_feeds[]
fields: [ competitor_id, rss_url ]
// Step 2: Fetch SEMrush data (per competitor, parallelised)
FOR each competitor IN competitors[]:
SEMrush.GET /domain/organic/competitors
params: { domain: competitor.semrush_domain, date_from: run_date-7d }
-> semrush_raw: { keywords_delta, traffic_delta, paid_keywords[] }
on_error: set data_status='error', log to source_errors[], continue
// Step 3: Fetch Ahrefs data (per competitor, parallelised)
FOR each competitor IN competitors[]:
Ahrefs.GET /v3/site-explorer/new-backlinks
params: { target: competitor.ahrefs_target, mode: 'subdomains', date_from: run_date-7d }
-> ahrefs_raw: { backlinks[], new_content[], dr_change }
dedup: backlinks[] AGAINST Supabase.competitor_backlink_cache
key: source_domain + target_url
on_error: set data_status='error', log to source_errors[], continue
// Step 4: Fetch Google Alerts RSS (per alert feed URL)
FOR each feed IN alert_feeds[]:
HTTP.GET feed.rss_url
-> rss_raw: RSS2.0 XML
parse: items[] -> { title, url, source, published_date }
filter: published_date >= run_date-7d
-> alerts[competitor_id]
// Step 5: Normalise and stage
normalise(semrush_raw, ahrefs_raw, alerts[]) ->
staged_dataset: {
run_id, run_timestamp,
competitors[]: {
competitor_id, name, domain,
semrush: { organic_keywords_delta, estimated_traffic_delta, new_paid_keywords[], data_status },
ahrefs: { new_backlinks[], new_content_urls[], dr_change, data_status },
alerts: [ { title, url, source, published_date } ],
source_errors[]
}
}
Supabase.INSERT competitor_runs:
{ run_id, run_timestamp, status: 'staged', competitor_count: n }
// ── AGENT HANDOFF: Data Collection Agent -> Intelligence Summariser Agent ──
// Trigger condition: Supabase.competitor_runs WHERE run_id = current AND status = 'staged'
// ── INTELLIGENCE SUMMARISER AGENT ─────────────────────────────────────────
// Step 6: Evaluate significance thresholds (pre-LLM, in orchestration layer)
FOR each competitor IN staged_dataset.competitors[]:
IF competitor.ahrefs.dr_change >= 5
OR competitor.semrush.estimated_traffic_delta >= 15%
OR COUNT(competitor.ahrefs.new_backlinks WHERE dr >= 60) >= 3:
significant_change_flagged = true
flag_reason = '<competitor.name>: <reason>'
// Step 7: LLM digest generation
LLM.complete(
model: 'gpt-4o',
prompt: Supabase.agent_config['summariser_prompt_v'],
input: staged_dataset,
max_tokens_per_competitor: 300
) ->
digest: {
highlights_markdown,
competitor_sections[]: { competitor_id, name, summary_markdown, key_metrics{} }
}
// Step 8: Conditional flag handling
IF significant_change_flagged:
Slack.postDirectMessage(
to: config.marketing_manager_slack_id,
text: 'Significant change flagged: {flag_reason}. Approve by 11 a.m. or auto-publishes.',
buttons: ['Approve and publish', 'Edit before publishing']
)
Supabase.UPDATE competitor_runs SET status='pending_review'
WAIT max 4 hours for approval webhook
on_approval: continue to Step 9
on_timeout: continue to Step 9, log status='auto_released'
ELSE:
continue to Step 9
// ── OUTPUTS ───────────────────────────────────────────────────────────────
// Step 9a: Append to Google Sheets tracker
GoogleSheets.append('WeeklyLog', row: [
week_ending, competitor_id, competitor_name,
organic_keywords_delta, traffic_delta,
new_backlinks_count, new_content_count, alert_count,
significant_change_flagged
])
// Step 9b: Post digest to Slack channel
Slack.postMessage(
channel: '#competitor-intelligence',
text: digest.highlights_markdown,
thread: digest.competitor_sections[].summary_markdown
)
// Step 9c: Log note in HubSpot
HubSpot.POST /crm/v3/objects/notes
body: {
properties: {
hs_note_body: 'Competitor digest week ending {week_ending}. Flag: {significant_change_flagged}.',
hs_timestamp: run_timestamp
},
associations: [ { objectType: config.hubspot_object_type, id: config.hubspot_record_id } ]
}
// Step 10: Finalise run record
Supabase.UPDATE competitor_runs
SET status='complete', completed_at=now()
WHERE run_id = current_run_id04Recommended build stack
More documents for this process
Every document generated for Competitor Monitoring.