seo-rhythm-daily
Daily SEO operating rhythm — 06:05 data collection, 10:07 CTR repair sprint. TRIGGER automatically by cron or Tycoon RoutineScheduler; never called ad-hoc. The
npx skills add seo-rhythm-daily
seo-rhythm-daily — Two Moments, Every Day
Every day, two things happen. That's it. More than two daily moments produces thrash and blurs signal.
Moment 1 — 06:05: Data collection
Invoke seo-monitor with today's date. Expected output:
reports/seo/raw/gsc-{YYYY-MM-DD}.jsonreports/seo/raw/ga4-{YYYY-MM-DD}.jsonreports/seo/raw/indexing-{YYYY-MM-DD}.json- Appends a row to
reports/seo/data/kpi-history.json
- [ ] GSC data has ≥ 100 rows (if not, API auth likely broken)
- [ ] GA4 sessions > 0 (if not, tracking likely broken)
- [ ] Indexed-page count is within ±20% of yesterday (if not, trigger
seo-postmortem)
reports/seo/daily-status/{YYYY-MM-DD}.log:
06:07 ✅ GSC=1243rows GA4=412sessions Index=324pages
Moment 2 — 10:07: CTR repair sprint
This is where the agent ships 1–2 experiments per day. It is not optional and it is not more than 2.
Why 1–2 not 5: each experiment needs to breathe. Shipping 5 at once on 5 different pages makes attribution messy when a Google algo update lands mid-week.
Step 1: Read memory
Before picking a candidate, read:
memory/playbooks/.md— which patterns are proven?memory/lessons/.md— which patterns are known bad?memory/experiments/.yamlwherestatus=open— don't double-ship a page already mid-experiment
Step 2: Pick candidates
Invoke seo-optimize.find_ctr_candidates(window=7d). It returns pages sorted by:
priority_score = impressions × (benchmark_CTR_at_position - actual_CTR)
Filter out:
- Pages currently in an open experiment (
experiments/.yamlhas this page withstatus=open) - Pages listed in recent lessons as "don't touch"
- Pages already optimized in last 60 days (cool-off period)
Step 3: For each candidate
- Match playbook — which playbook applies (title-number, desc-frontload, etc.)?
- Log experiment — invoke
seo-experiment-logwith hypothesis derived from playbook evidence - Ship change — invoke
seo-optimize.apply(page, change, playbook_name) - Commit + deploy — the change only counts as "shipped" when deployed
Step 4: Update dashboard
Append to reports/seo/dashboard.md the day's 2 experiments with their measure_at dates, so the owner sees the pipeline filling.
Step 5: Return
Tell the caller:
10:12 ✅ Shipped 2 experiments
exp-2026-04-16-a7k: /alternatives/cursor (title-number playbook)
exp-2026-04-16-b3m: /compare/openrouter-vs-litellm (desc-frontload playbook)
Measure on: 2026-04-30
Edge cases
No good candidates today (all top-priority pages are in cool-off or existing experiments):- Skip the sprint. Note in daily-status.log:
10:12 ⏸ No candidates (all in cooldown) - Don't invent experiments just to hit a count.
- Skip that candidate. Pick the next one.
- Skip the 10:07 sprint entirely. The postmortem takes priority.
- Note:
10:07 ⏸ Deferred — postmortem-{id} in progress
Quality bar
- [ ] Both moments ran (or a documented reason for skipping)
- [ ] Every shipped change has a corresponding experiment file
- [ ] No changes shipped for pages in cool-off
- [ ] daily-status.log has both entries
What I refuse
- To ship more than 2 experiments per day. It pollutes attribution.
- To ship without consulting playbooks + lessons. That defeats the whole learning loop.
- To skip Moment 1 because "we don't need data today". Data compounds.
- To ship at 10:07 when 06:05 flagged a regression. Fix the leak before pouring more water in.
Integration
Called by:
- Tycoon RoutineScheduler (cron rule:
5 6and7 10) - Standalone cron:
~/.claude/scripts/seo-daily.sh
seo-monitor(Moment 1)seo-optimize+seo-experiment-log(Moment 2)seo-postmortem(if regression)
seo-config.yaml for override cadence (some customers may want 09:00 / 14:00 instead).