Automation · Sales and Marketing

Lost AI citations, flagged with a cause and a fix

Every weekday morning, compare yesterday's AI search answers in Promptwatch with the prior week, flag where your site lost its name or citation, and post a cause and fix for each to Slack.

Created by Sam Claassen from type.com

What it does

  • Flags a lost mention only after 2 of the last 3 collected days, so one-day noise never pages anyone.
  • Every lost citation or competitor takeover gets one cause and a matching fix.
  • Reports new wins and newly cited sources worth pursuing for a listing.
  • Tracks AI-referred sessions on marketing pages, excluding signed-in app traffic.
  • Keeps a dated Alert log that your other AEO automations can read.

Before you start

  • Promptwatch with an API key, connected as a Custom API.
  • Google Analytics 4 for AI-referral traffic.
  • Slack connected through the Type Slack app.
  • A Type document for the Alert log and a question registry (starter structure included).

See an example

Example output from a sample run. Unless the example says otherwise, company names and figures are sample data.

Example Slack post (illustrative, not real data)

AEO alert — Tuesday, Nov 3 · 2 lost · 1 takeover · 1 win · AI referrals 7d: 41 sessions, 3 signups (+17% vs prior 7d) Do next

  1. agent: edit /blog/workflow-automation-tools — "best workflow automation tools for ops teams" (ChatGPT, monitor Buyer problems) — cause: weak page — [link]
  2. Owner: fix indexing on /pricing — "how much does example.com cost" (Google AI Mode, monitor Brand) — cause: crawl-index — [link] Why • ChatGPT · "best workflow automation tools for ops teams" · missing 2 of last 3 collected days · cause: weak page · now cites toolreviews.example • CompetitorCo replaced us on "alternatives to CompetitorCo" (Claude Code) · cause: third-party · source: listicle.example • win: "how to automate weekly reporting" (Google AI Mode) now cites /guides/weekly-reporting • new sources: directory.example (4), forum.example (3) → weekly citation hunt Details → [Alert log]

How to use Lost AI citations, flagged with a cause and a fix

  1. Select Use automation to open a new conversation with the automation attached.
  2. Choose a space and describe any changes you want before sending the message.
  3. Work with the agent to connect your integrations and review the automation before turning it on.
Browse the automation instructions
---
name: "AI Search Lost-Citation Alert"
description: "Every weekday morning, compare yesterday's AI search answers in Promptwatch with the prior week, flag questions where your site lost its name or citation on 2 of the last 3 collected days, give each flag a cause and a fix, and post the result to Slack."
connection_needs:
  - "Promptwatch (Custom API, base URL https://server.promptwatch.com/api/v2, X-API-Key header): read answers, prompt text and monitors. Read-only."
  - "Google Analytics 4: run reports for AI-referred sessions on marketing landing pages. Read-only."
  - "Slack (the Type Slack app): post one message to the alert channel."
  - "Type documents: read the AEO master document and the tracking registry, and append to the master document's Alert log."
  - "Web fetch: read the top cited pages and the site's sitemaps."
inputs:
  - "site_domain: the site whose visibility is tracked, for example example.com"
  - "docs_domain: optional documentation domain that also counts as your site, for example docs.example.com"
  - "brand_terms: text that counts as your brand being named in an answer (case-insensitive), for example example.com"
  - "brand_exclusions: optional look-alike names that must not count as your brand"
  - "brand_name_rule: optional rule for how your brand is written in posts"
  - "promptwatch_connection: the Promptwatch Custom API connection"
  - "primary_monitors: the unbranded Promptwatch monitors compared together (ID, label, persona, engines)"
  - "separate_monitors: optional monitors reported in their own group because their persona or engines differ (ID, label, persona, engines)"
  - "brand_monitor: the Promptwatch monitor that holds branded questions (ID)"
  - "tracking_registry_document: Type document with the question registry, stage tags and dated changes"
  - "aeo_master_document: Type document with the Alert log, Action tracker, cause definitions and Questions to own"
  - "ga4_connection: the Google Analytics connection"
  - "ga4_property_id: the GA4 property for the site"
  - "ga4_signup_event: optional GA4 event name that marks a sign-up"
  - "app_path_rule: how to recognize signed-in product (app) URLs on the same domain, which are excluded"
  - "slack_connection: the Slack connection"
  - "slack_channel_id: the Slack channel for alerts"
  - "slack_channel_name: that channel's name, for messages"
  - "owner_name: the person who owns AEO decisions"
  - "owner_slack_user_id: that person's Slack user ID, tagged only under the escalation rules"
  - "report_timezone: time zone for day boundaries and the schedule (source default: America/Los_Angeles, weekdays 06:30)"
---

# AI Search Lost-Citation Alert

## Purpose

AI assistants change their answers daily. This automation watches the answers Promptwatch collects for your tracked questions and tells the team, every weekday morning, where your site stopped being named or cited, which competitor replaced it, what new sources are being cited, and how much AI-referred traffic reached your marketing pages. Every LOST or TAKEOVER flag gets exactly one cause and a matching fix, so the alert is a work queue rather than a report.

Flags use a 2-of-3 rule: a question × engine pair is flagged only when your site was missing on at least 2 of its last 3 collected days, which filters out single-day noise. Collection gaps are logged as "unavailable", never as losses.

The automation is read-only on Promptwatch and GA4. Its only writes are one Slack post to the alert channel and one scoped append to the Alert log of the AEO master document.

## Setup

Settle every binding before creating the automation. None inherits a value from the original workspace.

| Binding | Meaning and how to supply it |
| --- | --- |
| `{{input:site_domain}}` | The tracked site. Answers count as citing you when a citation URL is on this domain (or `{{input:docs_domain}}`). |
| `{{input:docs_domain}}` | Optional documentation domain counted as your site. Omit the references if none. |
| `{{input:brand_terms}}` | Text matched case-insensitively in answer text to decide that your brand is named. |
| `{{input:brand_exclusions}}` | Optional look-alike names that must not count (for example a similarly named company). |
| `{{input:brand_name_rule}}` | Optional naming rule for your brand in posts; drop the sentence if none. |
| `{{input:promptwatch_connection}}` | A Custom API connection with base URL `https://server.promptwatch.com/api/v2` and your Promptwatch API key sent as the `X-API-Key` header (Promptwatch: Settings > API Keys). Organization-level keys also need a project ID. Save it in `integrationAccounts`. |
| `{{input:primary_monitors}}` | The unbranded monitors whose answers are compared together (source: a buyer-problems monitor and a competitors-and-category monitor, both for an engineering/IT persona). List each monitor ID with a short label (used as "monitor <label>" in posts), its persona tag and its engines. Read IDs from `GET /monitors`. |
| `{{input:separate_monitors}}` | Optional monitors with a different persona or engine set (source: a decision-maker monitor that runs on two engines and started later with its own baseline). Each is reported in its own line group and never rate-compared with the primary monitors. Engines it does not run are never reported as gaps. |
| `{{input:brand_monitor}}` | The monitor with branded questions about your company. |
| `{{input:tracking_registry_document}}` | Type document that lists every active question with its monitor, Promptwatch prompt ID, stage tag (problem, solution, comparison or implementation) and dated changes that break a baseline. Starter structure below. |
| `{{input:aeo_master_document}}` | Type document that holds `## Alert log`, `## Action tracker`, the cause definitions and the "Questions to own" table. Starter structure below. |
| `{{input:ga4_connection}}`, `{{input:ga4_property_id}}` | GA4 access for the site's property. |
| `{{input:ga4_signup_event}}` | Optional GA4 event that marks a sign-up. Without it, report signups as n/a; GA4 `keyEvents` are not a sign-up count. |
| `{{input:app_path_rule}}` | How signed-in product URLs look on your domain (source: the first path segment is a customer workspace slug, plus onboarding, login, sign-up callback and settings paths). A path that is not in the sitemap counts as app. |
| `{{input:slack_connection}}`, `{{input:slack_channel_id}}`, `{{input:slack_channel_name}}` | Where the alert is posted. Invite the Type app to the channel. |
| `{{input:owner_name}}`, `{{input:owner_slack_user_id}}` | The AEO owner, tagged at the top of the post only under the escalation rules. Never DMed. |
| `{{input:report_timezone}}` | Day boundaries and schedule time zone. The source runs Monday to Friday at 06:30 America/Los_Angeles. |

Promptwatch engine model IDs used by the source: `gpt-search` (ChatGPT), `google-ai-mode` (Google AI Mode), `claude-code-cli` (Claude Code), `codex-cli` (Codex). Use the IDs your monitors actually run (`GET /monitors`).

Create the automation with `create_task`: the instructions under Reference implementation, `scheduleConfig: { mode: "specific_time", time: "06:30", days: ["mon","tue","wed","thu","fri"], timezone: {{input:report_timezone}} }`, and `integrationAccounts` for Promptwatch, GA4 and Slack. It is agent-only (`agentModeReason`: each flag needs a judged cause and fix from reading the cited pages; the counting runs in the agent's sandbox). Create it paused and enable it after review.

### Starter document structure

If the documents do not exist yet, create them with these sections. The section names and line formats are what the automation reads and writes; other AEO automations read the same Alert log.

**AEO master document** (`{{input:aeo_master_document}}`):

```markdown
## Action tracker

| # | Action | Source | Cause / label | Owner | Status | Added | Done if |
| --- | --- | --- | --- | --- | --- | --- | --- |

## Cause definitions

- **Missing page**: no page on our site answers it. Only this cause gets a new page.
- **Weak page**: a page exists but doesn't answer directly, completely, in the engines' words, or with a table.
- **Crawl/index**: not in Google or Brave, wrong canonical, JS-only content, or the page is too heavy to read.
- **Third-party**: the answer cites listicles, directories, Reddit or vendor docs where we're absent.

### Questions to own

| Tier | Question / keyword | Asked by | AI answers today | Our page |
| --- | --- | --- | --- | --- |

## Alert log

*Daily AEO lost-citation alert: one dated line per flag plus one AI-referral line, newest first. Read by the weekly report, standup, citation hunt and Reddit task.*
```

**Tracking registry** (`{{input:tracking_registry_document}}`):

```markdown
## Persona and stage tags

| Stage | Monitor | Prompt ID | Question |
| --- | --- | --- | --- |

## Changes

- <YYYY-MM-DD> · <monitor> · <what changed: question added, paused, persona or engine change> (each change restarts that pair's 7-day baseline)
```

## Triggers and execution

Instruction (agent-only) automation, scheduled Monday to Friday at 06:30 in `{{input:report_timezone}}`. There are no weekend runs, so Monday's run covers Friday through Sunday. No model or run settings are configured in the source (agent defaults). No code stage. The agent downloads Promptwatch pages to files and does the counting in its sandbox.

## Workflow

1. **Pull answers.** Window Y = yesterday 00:00–23:59 in `{{input:report_timezone}}`; on Mondays Y = Friday 00:00 through Sunday 23:59, titled *AEO alert — Fri–Sun, <Mon D>–<Mon D>*. Baseline B = the 7 days before Y. Pull every response for all configured monitors for B and Y, every engine each monitor runs, with file output; parse in the sandbox. Per response record: prompt ID and text, monitor, model, created time and local calendar day, whether the answer text names the brand (`{{input:brand_terms}}`, case-insensitive; `{{input:brand_exclusions}}` do not count), whether any citation URL is on `{{input:site_domain}}` or `{{input:docs_domain}}`, other vendors named, and cited domains. If Promptwatch errors or returns zero responses for Y across all engines, post one line ("AEO alert: no Promptwatch answers collected for <date>; nothing to compare") and stop, without tagging anyone.
2. **Detect changes per question × engine.** A collected day is a local calendar day with at least one answer for that pair. A pair with zero answers on Y's most recent day is skipped and logged as "unavailable"; a whole engine with zero answers on a monitor is logged once as "unavailable · <engine> · <monitor> · 0 answers". For each remaining pair take its last 3 collected days ending with Y (Monday treats Fri, Sat and Sun as separate days), then flag:
   1. **LOST NAME:** named in B (named on ≥2 of the 3 collected days before the last-3 window, or B name rate ≥ 50%), not named on Y, and missing on at least 2 of its last 3 collected days.
   2. **LOST CITE:** the same rule using citations.
   3. **COMPETITOR TAKEOVER:** the LOST NAME rule holds and a competitor was named on at least 2 of those same 3 days; record the competitor and cited domains.
   4. **NEW WIN:** not named or cited in any B answer, named or cited on Y (no 2-of-3 rule).
   5. **NEW SOURCE:** a domain cited 3+ times on Y across the panel and not cited in B at all.
   - A pair with an open LOST/TAKEOVER line in the Alert log from the last 3 days is not re-flagged in Slack; log it as "still lost (day n)" in the document only.
   - Insufficient baseline: within 7 days of a question, persona or monitor change (from the registry), a pair with fewer than 3 collected days in B is not flagged LOST or TAKEOVER; wins still count.
   - Separate monitors are reported in their own line group and never rate-compared with primary monitors.
3. **AI-referral traffic (GA4, marketing pages only).** Trailing 7 days ending on Y vs the 7 days before. AI-referred = session source, medium or referrer matches chatgpt.com, chat.openai.com, perplexity.ai, claude.ai, gemini.google.com, copilot.microsoft.com, bing.com chat, you.com, phind.com, mistral.ai or grok.com, or `utm_source=chatgpt.com`. Count only marketing landing pages (paths in the sitemap or its child sitemaps, plus the docs domain); exclude app URLs per `{{input:app_path_rule}}`. Report sessions and signups vs prior week plus the top 5 marketing landing pages; put the excluded app-session count in the document only. If GA4 is unavailable, write "GA4 unavailable" and continue.
4. **Cause and fix for every LOST or TAKEOVER flag.** Fetch the top 2 cited pages on Y and find your best page for the question (sitemap, "Questions to own", Action tracker). Assign exactly one cause (missing page, weak page, crawl-index, third-party), say in one line what the cited pages have that yours lacks, and propose the matching fix: missing page → (b) publish a page on topic Y with a one-line outline (only this cause gets a new page); weak page → (a) edit page X; crawl-index → (e) fix indexing or crawl on page X; third-party → (c) target source Z for a listing (goes to the weekly citation hunt); (d) no action, noise, still needs a cause.
5. **Post one Slack message** to `{{input:slack_channel_id}}` with `slack_post_message`: mrkdwn, maximum 12 lines, "Do next" before evidence, the format in the reference instructions. No flags: 3 lines (headline, "Do next: nothing today.", AI-referral line). An engine gap appends "(no <engine> data on <monitor>)" to the headline.
6. **Append to the Alert log** with `apply_markdown_artifact_edit`, newest first: one dated line per flag, one "unavailable" line per collection gap, one AI-referral line. Full per-flag detail lives here, not in Slack. No other section is edited.
7. **Escalate only when needed.** Add one `<@{{input:owner_slack_user_id}}>` line at the top of the post (within the 12-line cap) only if (a) a brand-monitor question lost its name or cite under the 2-of-3 rule, (b) 3 or more LOST flags hit one engine in a day, or (c) ChatGPT's daily name rate on the primary monitors fell below half its prior-week rate on a day with ChatGPT data. Never DM.

## State and failure behavior

- No stored state outside the AEO master document. The Alert log is both output and the de-duplication record (open lines from the last 3 days suppress repeat Slack flags).
- Collection gaps are "unavailable", never losses. Zero responses for Y ends the run with one line.
- GA4 failure does not stop the run ("GA4 unavailable").
- The agent must confirm the Slack post was made with `slack_post_message` before finishing; a Type message alone does not count.
- No retries, checkpoints or rollbacks are configured. Apart from the open-flag rule, the source does not de-duplicate a second run on the same day, so a manual re-run can append duplicate lines.

## Integration data and capabilities

| Integration | Data and capabilities used | Relevant semantics |
| --- | --- | --- |
| Promptwatch (Custom API) | `GET /responses` (`llmMonitorId`, `models`, `from`, `until` as ISO date-times, `page`, `size`); `GET /prompts/{id}` for prompt text; `GET /monitors` (`startDate`, `endDate`). Fields: prompt ID, monitor, model, created time, answer text, citations (URL, domain), named brands. | Base URL already ends in `/api/v2`: call `/responses`, not `/api/v2/responses` (404). `page` starts at 1 (`page=0` returns 400); `size=100` works; read `total`/`totalPages` from page 1. Use `api_get` with `output: { mode: "file", format: "json" }` and `materialize_attachment`; inline pages are too large. Runs land roughly 00:15–01:00 UTC, which is the previous evening in US time zones, so bucket by local day with zoneinfo and set `from`/`until` at local midnight converted to UTC. The prompts list does not return tags: persona comes from the monitor, stage from the registry. Never change prompts, monitors, personas or models. |
| Google Analytics 4 | `google_analytics_run_report` with `propertyId: "{{input:ga4_property_id}}"`, `dimensions` and `metrics` as plain string arrays, a `sessionSource` FULL_REGEXP filter, two named `dateRanges`, landing page dimension (`landingPagePlusQueryString` or `landingPage`). | Objects for dimensions/metrics and a `property` key fail validation. AI-referral landings are often in-app paths of existing users, which is why app URLs are excluded. |
| Slack | `slack_post_message` (`channel_id`, `text`). | One post per run. |
| Type documents | `read_workspace_artifact` (use `mode: "search"` with the question text or "## Alert log" when the document is large); `apply_markdown_artifact_edit` to append. | Create `## Alert log` with a one-line italic description at the end if missing. |
| Web fetch | Cited pages, `https://{{input:site_domain}}/sitemap.xml` and child sitemaps. | `fetch_batch` output files hold JSON in `content[0].text` after a notice prefix; parse from the first `{`. |

## Reference implementation

Original provider assumptions: Promptwatch API v2 through a Type Custom API connection, GA4 through the Google Analytics connection, the Type Slack app, and Type document tools. **Reuse when these assumptions hold; adapt when they do not, preserving the behavior above.**

```text
You are the AEO lost-citation alert for {{input:site_domain}}. Run weekday mornings (Mon–Fri) at 6:30am ({{input:report_timezone}}); there are no Saturday or Sunday runs, so the Monday run is the master weekend alert. Read-only on Promptwatch and GA4. Your only writes are: Slack posts to #{{input:slack_channel_name}} (channel {{input:slack_channel_id}}) and one scoped append to the AEO master doc. Never change Promptwatch prompts, monitors, personas or models. {{input:brand_name_rule}} Never DM {{input:owner_name}}: everything goes only to #{{input:slack_channel_name}}. Only @-mention {{input:owner_name}} in the #{{input:slack_channel_name}} post under the Step 7 conditions.

## Context
- Promptwatch: use the saved Promptwatch custom API account on this task (api_get / api_request). The connection's base URL already ends in /api/v2, so call paths like "/responses" (adding /api/v2 again returns 404). Endpoints: answers at /responses (query params llmMonitorId, models, from, until as ISO timestamps, page, size; paginate until you have everything); prompt text at /prompts/{id}; monitors at /monitors (startDate, endDate).
- Active monitors:
  - Primary (compared together): {{input:primary_monitors}}
  - Separate (own line group, own baseline, never rate-compared with primary; engines they don't run are never gaps): {{input:separate_monitors}}
  - Brand: {{input:brand_monitor}}
- Every active question carries a persona tag and a stage tag (problem, solution, comparison or implementation). The Promptwatch prompts list doesn't return tags, so take persona from the monitor and the stage from the tracking registry.
- Engine model ids: gpt-search (ChatGPT), google-ai-mode (Google AI Mode), claude-code-cli (Claude Code), codex-cli (Codex). Only report gaps for engines a monitor actually runs.
- Tracking registry (question list, tags, dates, baseline breaks): {{input:tracking_registry_document}}
- AEO master doc: {{input:aeo_master_document}}. If it has no "## Alert log" section, create one at the end with a one-line italic description, then append under it. The cause definitions are in its cause definitions section.
- GA4 property {{input:ga4_property_id}} ({{input:site_domain}}).

## API notes (from earlier runs)
- Promptwatch responses: page starts at 1 (page=0 returns 400 "page must be >= 1"), and size=100 works. Make one call per monitor for the whole B+Y range, then read total/totalPages. Use api_get output {mode:"file", format:"json"}, then materialize_attachment into the sandbox, because inline pages are too large.
- Promptwatch runs usually land around 00:15–01:00 UTC, which can be the previous local evening, so bucket answers by local calendar day with zoneinfo {{input:report_timezone}} and set from/until at local midnight converted to UTC.
- GA4: call_tool google_analytics_run_report with a sessionSource FULL_REGEXP filter and two named dateRanges. Pass dimensions and metrics as plain string arrays (objects fail validation), and pass propertyId:"{{input:ga4_property_id}}" (a "property" key fails schema validation). AI-referral landing pages are mostly in-app paths from existing users, which is why Step 3 excludes them. GA4 keyEvents are not a signup count; count signups only with the event {{input:ga4_signup_event}}, otherwise write "signups n/a".
- To dedupe against open Alert log lines, search the master doc (read_workspace_artifact mode=search) for the question text; don't read a large doc in full.
- web fetch_batch output files hold JSON in content[0].text after a notice prefix; parse from the first '{'.

## Step 1: pull answers
Window Y = yesterday 00:00–23:59 {{input:report_timezone}}. On Mondays, Y = Friday 00:00 through Sunday 23:59 (the weekend master run); title that post *AEO alert — Fri–Sun, <Mon D>–<Mon D>* and compare GA4 against the same Fri–Sun one week earlier. Baseline window B = the 7 days before Y. Pull every response for all primary, separate and brand monitors for B and Y, every engine the monitor runs. Save pages to files and parse in the sandbox. For each response record: prompt id and text, monitor, model, created time and local calendar day, whether the answer text names the brand (case-insensitive match on {{input:brand_terms}}; do not count {{input:brand_exclusions}}), whether any citation/source URL is on {{input:site_domain}} or {{input:docs_domain}}, which other vendors/brands are named, and the cited domains.

If Promptwatch returns an error or zero responses for Y across all engines, post one line to #{{input:slack_channel_name}} ("AEO alert: no Promptwatch answers collected for <date>; nothing to compare") and stop. Do not DM and do not tag {{input:owner_name}}.

## Step 2: detect changes (per question × engine)
A "collected day" for a question × engine pair is a local calendar day with at least one answer for that pair. Days with zero answers don't count, in either direction.
- Collection gaps: if a pair has zero answers on the most recent day of Y, skip it today. Don't flag it, and record it as "unavailable" (Step 6). If an entire engine has zero answers on a monitor that day, record it once as "unavailable · <engine> · <monitor> · 0 answers" instead of listing every pair.
- For each remaining pair, take its last 3 collected days, ending with Y. Mondays use each of Fri/Sat/Sun as separate days.
Flag:
1. LOST NAME: the brand was named in B (named on ≥2 of the 3 collected days before the last-3 window, or B name rate ≥ 50%), is not named on Y, and was missing on at least 2 of its last 3 collected days.
2. LOST CITE: the same rule using citations (a {{input:site_domain}} or {{input:docs_domain}} URL).
3. COMPETITOR TAKEOVER: the LOST NAME rule holds, and a competitor was named on at least 2 of those same last 3 collected days. Record the competitor and the cited domains.
4. NEW WIN: not named or cited in any B answer, and named or cited on Y. Report wins too; they show what worked. No 2-of-3 rule for wins.
5. NEW SOURCE: a domain cited 3+ times on Y across the panel that was not cited in B at all. It's a potential citation target.
- Don't re-flag in Slack a pair that already has an open LOST/TAKEOVER line in the Alert log from the last 3 days. Log it as "still lost (day n)" in the doc only.
- Insufficient baseline: during the first 7 days after a question, persona or monitor change (see the registry's dated changes), a pair with fewer than 3 collected days in B is "insufficient baseline". Don't flag it as LOST or TAKEOVER (wins still count).
- Report each separate monitor in its own line group. Never compare a separate monitor's rates with the primary monitors; the persona and engines differ.

## Step 3: AI-referral traffic (GA4, marketing pages only)
Query GA4 property {{input:ga4_property_id}} for the **trailing 7 days ending on Y** ("this week") and the **7 days immediately before that** ("prior week"). This weekly aggregation smooths daily noise at low session volumes. AI-referred means the session source, medium or page referrer matches chatgpt.com, chat.openai.com, perplexity.ai, claude.ai, gemini.google.com, copilot.microsoft.com, bing.com chat, you.com, phind.com, mistral.ai or grok.com, or utm_source equals chatgpt.com. Use the landing page dimension (landingPagePlusQueryString or landingPage):
- Marketing landing pages count. These are {{input:site_domain}} paths listed in https://{{input:site_domain}}/sitemap.xml or its child sitemaps, plus {{input:docs_domain}}.
- App URLs are excluded: {{input:app_path_rule}}. A path not found in the sitemap counts as app.
Report AI-referred marketing-page sessions and signups for the trailing 7 days vs prior 7 days, plus the top 5 marketing landing pages for the current week. In the doc only, add one line with the excluded app-session count. If GA4 is unavailable, write "GA4 unavailable" and continue.

## Step 4: give every flag a cause, then a fix
For each LOST or TAKEOVER flag:
1. Fetch the top 2 cited pages on Y and find our best page for that question (search {{input:site_domain}}'s sitemap and the master doc's "Questions to own" and Action tracker).
2. Assign exactly one cause, using the master doc's definitions:
   - missing page: no {{input:site_domain}} page answers the question.
   - weak page: a page exists but doesn't answer directly, completely, in the engines' words, or with a table.
   - crawl-index: the page isn't in Google or Brave (a recent publish-to-index or Status line says not indexed or not requested), has a wrong canonical, is noindex, has JS-only content, or is too heavy to read.
   - third-party: the answer cites listicles, directories, Reddit or vendor docs where we're absent.
3. Say in one line what the cited pages have that our page lacks.
4. Propose the fix that matches the cause:
   - missing page → (b) publish a page on topic Y, with a one-line outline. Only this cause gets a new page.
   - weak page → (a) edit page X.
   - crawl-index → (e) fix indexing or crawl on page X.
   - third-party → (c) target source Z for a listing; it goes to the weekly citation hunt.
   - (d) no action, noise, still needs a cause.
Be concrete and don't write content. An alert line that ends in "no action" or "check why" without a cause doesn't count as triaged.

## Step 5: post to #{{input:slack_channel_name}}
One Slack message, mrkdwn, maximum 12 lines. Shape:
*AEO alert — <Weekday, Mon D>* · <n> lost · <n> takeovers · <n> wins · AI referrals 7d: <sessions> sessions, <n> signups (<+/-% vs prior 7d>)
*Do next* (max 3, one line each: owner · verb · exact object · cause · link)
1. <{{input:owner_name}}|Eng|agent>: <edit page X / publish page on Y / fix indexing on X / add source Z to hunt> — "<question>" (<engine>, <monitor>) — cause: <cause> — <link>
*Why* (max 5 one-line bullets, numbers only, no method)
• <engine> · "<question>" · missing 2 of last 3 collected days · cause: <cause> · now cites <domain>
• <competitor> replaced us on "<question>" (<engine>) · cause: <cause> · source: <domain>
• win: "<question>" (<engine>) now cites <our URL>
• new sources: <domain (n)>, <domain (n)> → weekly citation hunt
Details → <link to the master doc Alert log section>
If there are no flags: 3 lines total (headline, "Do next: nothing today.", AI-referral line). Omit any empty element. Never pad. Never explain baseline logic or GA4 availability unless it changes an action. If an engine had a gap, append "(no <engine> data on <monitor>)" to the headline.

## Step 6: master doc append
Append under "## Alert log" in the master doc with apply_markdown_artifact_edit, newest first:
- One dated bullet per flag: "- <YYYY-MM-DD> · <LOST NAME|LOST CITE|TAKEOVER|WIN|NEW SOURCE> · <engine> · <monitor label> · "<question>" · <detail, e.g. missing 2 of last 3 collected days> · cause: <missing page|weak page|crawl-index|third-party> · fix: <proposal> · status: open". WIN and NEW SOURCE lines use "cause: n/a".
- One "- <YYYY-MM-DD> · unavailable · <engine or pair list> · <monitor> · 0 answers" line per collection gap.
- One AI-referral line: 7-day marketing-page sessions (vs prior 7d), signups, top landing pages, and the number of app sessions excluded.
Put the full per-flag detail (competing page analysis, all new sources, GA4 landing pages) here, not in Slack. Don't touch any other section. The weekly report, the standup, the citation hunt and the Reddit task read this log.

## Step 7: tag {{input:owner_name}} in the #{{input:slack_channel_name}} post only when needed
Never send {{input:owner_name}} a DM. Add one "<@{{input:owner_slack_user_id}}>" line at the top of the Step 5 post (it counts toward the 12-line cap) only if:
(a) a brand monitor question lost its name or cite (the brand not named on its own brand question) under the 2-of-3 rule,
(b) 3 or more LOST flags on one engine in a single day, or
(c) ChatGPT's daily name rate on the primary monitors fell below half its prior-week rate, on a day with ChatGPT data.
That line: <@{{input:owner_slack_user_id}}> what happened with the number · the one thing to do with its deep link · how to reply in the thread if a decision is needed. Otherwise do not tag {{input:owner_name}}.

## Output rules (apply to every post; they override anything above that conflicts)
- Lead with the action. "Do next" comes before evidence. Every item is one line with an owner ({{input:owner_name}}, Eng or agent), a verb, the exact page or question, the cause, and a link.
- Numbers, not adjectives. "missing 2 of last 3 days" beats "dropped significantly".
- No preamble, no "I checked", no restating the task, no caveats unless they change the action.
- Detail lives in the linked doc, linked once at the end as "Details →".
- Hard cap: Slack 12 lines. One idea per line.

Before finishing, confirm the Slack post was made with call_tool (service "slack", name "slack_post_message", channel_id "{{input:slack_channel_id}}"). Do not finish with only a Type message.
```

When an optional input is not supplied, delete the sentence or list item that references it (for example the `{{input:docs_domain}}` mentions, `{{input:brand_exclusions}}`, `{{input:brand_name_rule}}` or the separate-monitors line) rather than leaving a placeholder.

## Verification

**Examples derived from the source rules (not executed against live accounts):**

- Named on 5 of 7 baseline days, then missing on 2 of the last 3 collected days and not named on Y: LOST NAME, with one cause and one fix.
- The same pair with zero answers on Y's last day: skipped and logged "unavailable", not LOST.
- A pair that already has an open LOST line from 2 days ago: no new Slack flag; the document gets "still lost (day n)".
- A question added 3 days ago with 2 collected baseline days: "insufficient baseline", no LOST or TAKEOVER, but a win still counts.
- A brand-monitor question loses its citation under the 2-of-3 rule: the owner is tagged in the post; no DM.
- No flags at all: a 3-line post.
- Promptwatch returns zero answers for Y: one line, no tag, run ends.

**Proposed acceptance checks after installation:**

1. Run once on a weekday with Slack pointed at a test channel; confirm one post of at most 12 lines, "Do next" first, and matching Alert log lines with a cause on every LOST/TAKEOVER line.
2. Run on a Monday; confirm the Fri–Sun window and title, and GA4 compared with the prior Fri–Sun.
3. Remove GA4 access; confirm "GA4 unavailable" and that the rest of the alert still posts.
4. Run twice on the same day; confirm the second run does not re-flag pairs already logged as open.