Status: built, backtested, published. Cron and email still OFF. Nothing in any ad account was changed.
Live example (Pott): https://pott-fatigue-watch.pages.dev/ (19 Oct run) and https://pott-fatigue-watch.pages.dev/2026-08-31.html (the Ad 707 case). De-indexed but public.
One daily watcher, scripts/meta-fatigue-watch/, that judges every ad still taking real money and puts it on one of five stages, each tied to one action:
| Stage | Action |
|---|---|
| Fatigued | Cut today |
| Close to fatiguing | Refresh this week |
| Highly likely | Brief the replacement now |
| Monitor | Watch (also: pulled back, over-scaled, check site, audience-not-creative) |
| None | Nothing |
Too new and too small are labelled separately.
Three fatigue systems existed. The June weekly scoring (raw week on week, age as a trigger), a September daily agent on an unmerged branch that was never recorded anywhere, and the October watcher. Today the best of September was folded into October:
breakevenRoas in a client config the bar is only a campaign average, so Fatigued is capped at Close with a note. Pott 2.46x from the margin sheet; H&H 4.0x, Pairs 2.0x, SHF 3.0x from Casey (20 Oct).state/refresh-feed.json with each flagged ad's copy, headline, video id and preview link, so the replacement brief starts from the live creative.backtest.cjs: judges past dates and checks what every judged ad did over the following 14 days, per stage.Kept from October over September: breakeven line, material-only and spend-held gates, over-scaled and pulled-back as separate outcomes, own-best-week slow decay, signal families, five action-defined stages, account-wide guard, persistence against the ledger a week earlier (never yesterday's overlapping run).
The four hand-picked cases all land where they should:
| Case | Result |
|---|---|
| Pott Ad 707, 31 Aug | Fatigued (23% of spend, 1.79x against 2.46x, click rate down) |
| Pott 18 Jul (site dip) | Account-wide guard fired (audience family 4 of 7), nothing cut |
| H&H Ad 260, 14 Oct | Close, with the no-breakeven note |
| Pairs Ad 446, 14 Oct | Monitor (was a misfire at Close); Ad 264 Highly likely on hook rate |
Pott live run for 13 to 19 Oct: nothing above Monitor. Ad 940 is pulled back (Meta halved its spend), 11 Christmas ads become judgeable from about 21 Oct.
Next-14-day backtest. 113 judged ads across 30 Jul, 13 Aug, 27 Aug, 10 Sep and 24 Sep for Pott, H&H, Pairs and SHF. For each stage, the median of each ad's ROAS over the following 14 days as a share of its own baseline:
| Stage | Calls | Next 14 days vs baseline | Fell under 0.8x baseline |
|---|---|---|---|
| Fatigued | 1 | 0.60x | 100% |
| Close | 5 | 1.07x | 50% |
| Highly likely | 13 | 1.03x | 15% |
| Monitor | 59 | 0.86x | 44% |
| None | 35 | 1.18x | 30% |
Read: Fatigued was right when it fired (Pott 707 on 10 Sep) and None ads kept earning. Highly likely ads kept earning too, which is what an early warning should do. Close is mixed and the sample is tiny. One thing worth watching: Monitor ads whose only signal was an unconfirmed best week went on to earn 0.76x of baseline (17 calls), so that signal has some predictive power even when the week looks fine. Only Pott has a breakeven set, so only Pott could reach Fatigued in this test. Full rows in scripts/meta-fatigue-watch/backtests/.
run-daily.sh; Casey adds it himself. Keep per-client email false, the combined morning email reads state/last-result.json.scripts/meta-fatigue-watch/watch.cjs judging, shared.json thresholds, report.cjs the page, backtest.cjs harness, run-daily.sh cron wrapper.clients/<slug>/meta-fatigue-watch/config.json per client; state/ ledger, last result, refresh feed; reports/<date>.html.--dry-run, --as-of=YYYY-MM-DD, --no-report, --json=<path>, --force-email.worktree-ad-fatigue-agent, dir .claude/worktrees/ad-fatigue-agent/ad-fatigue/.Point the weekly optimizer's step 5 at this watcher's output instead of keeping a second scoring system. Pott's Christmas ads are the first live read of the merged rules.