| name | cro-backlog-monitor |
| description | Twice-daily scan of idle pages (6 AM & 6 PM UTC): dispatch builds, notify when pages need prioritization, trigger research for empty backlogs. Runs as the first pass of the cro-monitor cron. |
Backlog Monitor
Configuration
Read .warp/skills/cro/references/config.md for Notion IDs and data access.
Read .warp/skills/cro/references/config-slack.md for Slack posting config (token, channels, Thread TS protocol).
Operating Rules
- One experiment per page. Never dispatch a build on a page that has an active experiment (Status in
3-building, 4-ready for review, 5-live:validating, 6-live:running, 7-analyzing). Also treat a page as blocked if it has a recently analyzed final-status row (8-no-ship, 8-inconclusive, or 9-shipped) with a non-empty Ship/Kill PR that has not merged yet; that is a lifecycle inconsistency, not an idle page.
- CRO-only scope. Never dispatch SEO-only tests. Read
references/config.md → Experiment Scope. If a 1-proposed, 2-prioritized, or 3-building Roadmap entry is clearly SEO-only, set Status → 0-archived, log the reason in the agent session, and do not post to Slack.
- Only process experiment-eligible pages. Skip Page Registry entries whose
Research Status is low-traffic, cro-excluded, or needs-primary-metric; do not dispatch builds, request prioritization, trigger research, or post Slack messages for them.
- Agents never prioritize. Never change status to
2-prioritized.
- No duplicate idle reminders. Do not post a new page-level idle notification if an existing top-level idle notification for the same page is already present in #your-notifications-channel. The existing message is the reminder; repeated cron runs must stay silent.
- Parallel dispatches are fine. Multiple pages can have builds dispatched in the same run — each triggers its own agent.
- Follow the Slack Thread TS protocol from
config-slack.md for all posts.
- Slack token: use ONLY
$EXPERIMENT_BUD_SLACK_BOT_TOKEN. See the Token section in config-slack.md for the full rule.
Procedure
Step 1: Read pipeline state
Query the Roadmap & Backlog for all non-archived entries:
curl -s -X POST "https://api.notion.com/v1/databases/NOTION_ROADMAP_DB_ID/query" \
-H "Authorization: Bearer $NOTION_INTEGRATION_TOKEN" \
-H "Notion-Version: 2022-06-28" \
-H "Content-Type: application/json" \
-d '{"filter": {"property": "Status", "select": {"does_not_equal": "0-archived"}}}'
Before grouping, classify Roadmap entries with the SEO-only scope rules in references/config.md. Archive clearly SEO-only entries in 1-proposed, 2-prioritized, or 3-building by setting Status → 0-archived; exclude them from active/prioritized/proposed counts. Do not post a Slack notification for this cleanup.
Group by Page path. For each page, note:
- Active experiments: any entry with Status in
3-building, 4-ready for review, 5-live:validating, 6-live:running, 7-analyzing
- Pending cleanup experiments: any entry with Status in
8-no-ship, 8-inconclusive, or 9-shipped and a non-empty Ship/Kill PR. Check the PR state with gh pr view. If the PR is not merged, count the page as active/blocked, log Lifecycle inconsistency: final status but Ship/Kill PR unmerged for {page_path}: {pr_url}, and do not dispatch another build or post an idle-page notification for that page.
- Prioritized hypotheses: Status =
2-prioritized, sorted by RICE Score descending
- Proposed hypotheses: Status =
1-proposed
Also query the Page Registry for all pages where:
CRO Excluded = false
Research Status is NOT low-traffic
Research Status is NOT cro-excluded
Research Status is NOT needs-primary-metric
For each eligible page, also read:
Owner — determines who to @-mention in page-specific Slack posts (see references/config.md → Page Owners)
Daily Traffic — used only as a defensive cross-check; if it is present and < 50, skip the page silently even if Research Status is stale
Research Status — log skip reasons in agent output if an ineligible page appears in the result set unexpectedly
Pages with ineligible research statuses are waiting on traffic or human input — don't auto-dispatch builds, request prioritization, trigger research, or post Slack messages for them. page_registry_sync will auto-advance them once they become eligible. If Daily Traffic is present and < 50, skip the page silently even if Research Status is stale.
Step 2: Process each idle page
For each experiment-eligible page with no active experiment:
2a. Has 2-prioritized hypotheses → dispatch build
Pick the highest RICE score entry. Update its status to 3-building:
curl -s -X PATCH "https://api.notion.com/v1/pages/{{HYPOTHESIS_PAGE_ID}}" \
-H "Authorization: Bearer $NOTION_INTEGRATION_TOKEN" \
-H "Notion-Version: 2022-06-28" \
-H "Content-Type: application/json" \
-d '{"properties": {"Status": {"select": {"name": "3-building"}}}}'
This triggers the build agent via Notion automation → bud_server.
The build agent handles the Slack notification for this experiment.
2b. No 2-prioritized but has 1-proposed → request prioritization
Before posting, dedupe against existing page-level idle reminders:
- Call Slack
conversations.history for #your-notifications-channel (SLACK_NOTIFICATIONS_CHANNEL_ID) and scan recent top-level messages from the experiment-bud app, paginating at least 30 days back or 1,000 messages, whichever comes first.
- Treat messages from either
app_id = SLACK_APP_ID, bot_id = SLACK_BOT_ID, or user = SLACK_BOT_USER_ID as Experiment Bud messages. Do not rely on username.
- For each candidate idle reminder, parse the page path with this exact regex so both rendered emoji and Slack emoji aliases are handled:
^(?:⏳|:hourglass_flowing_sand:)\s+(.*?)\s+is idle(?:\s+with|:)
Compare that parsed path to the current page_path using exact string equality after trimming whitespace. Do not use substring matching because the homepage path / would match every page path.
- If any candidate has the same parsed page path and the idle-reminder phrase
is idle plus proposed hypothes plus none prioritized, skip posting for that page. Log Skipping duplicate idle reminder for {page_path}: existing Slack message {ts} in the agent output.
- Only post if no existing idle reminder for that page is found.
Post to #your-notifications-channel (as a new message, not threaded — this is a page-level notification). Use the canonical snippet from config-slack.md with channel='SLACK_NOTIFICATIONS_CHANNEL_ID' and no thread_ts. Resolve {owner_mention} from the page's Owner (per config-slack.md → Page Owners); if Owner is empty, omit the mention entirely.
Do not post this notification for pages whose Research Status is low-traffic, cro-excluded, or needs-primary-metric, or whose Daily Traffic is present and < 50. These pages are not currently experiment-eligible, so asking humans to prioritize hypotheses creates noise.
⏳ {page_path} is idle: {n} proposed {hypothesis/hypotheses}, none prioritized.
{owner_mention} — choose the next experiment in <{roadmap_url}|Roadmap>: prioritize an existing idea or add a new one with status 2-prioritized.
Use hypothesis when n=1; otherwise use hypotheses.
2c. No hypotheses at all (no 1-proposed, no 2-prioritized) → trigger research
Update the Page Registry entry's Research Status to generate-more-hypotheses:
curl -s -X PATCH "https://api.notion.com/v1/pages/{{PAGE_REGISTRY_ENTRY_ID}}" \
-H "Authorization: Bearer $NOTION_INTEGRATION_TOKEN" \
-H "Notion-Version: 2022-06-28" \
-H "Content-Type: application/json" \
-d '{"properties": {"Research Status": {"select": {"name": "generate-more-hypotheses"}}}}'
This triggers the research agent via Notion automation → bud_server.
Step 3: Done
This skill's only Slack output is the ⏳ idle page notification from Step 2b.