Summary
An email watch (and whole-inbox triage) is a forward-only monitor. On the first (seeding) tick it baselines the cursor at the newest matching email and drafts at most that one; every older matching email already sitting in the inbox falls below the after:<watermark> clause and is never listed again. So if you set up "watch investor inbound" and several unanswered investor emails are already in your inbox, the watch processes at most the single newest one and silently skips the rest.
Accepted for now: picking up the latest matching email on seed is fine. This issue tracks adding a one-time backlog sweep so a watch can also work through mail that was already waiting when it was created.
Why this happens
skills/google/gmail-watch/scripts/detect.ts, seeding regime (~detect.ts:733-781):
- Seeding sets
cursor = newest.internalDate and drafts only window.ids[0] (the newest), recording boundary-second siblings as seen.
- Steady-state then bounds every list with
after:<cursorSec> (detect.ts:717-725), so anything older than the seed cursor can never be re-listed — newness is defined by the forward-only watermark, not by read/unread state.
- The only acknowledgement of a backlog today is the >1000-row truncation notice, which punts: "Ask me to triage this inbox if you want me to work through them" (
detect.ts:796-799).
Net: the existing-backlog case is unhandled by design — the watch only ever sees mail that arrives after it was created.
Proposed approach
Add a one-time, bounded backlog sweep as a distinct mode from the forward watch:
- Classify + act over the existing inbox when a watch/rule is created (oldest-first, paginated across ticks, hard-capped so it can't run away).
- Independent of the forward
after: watermark — the sweep walks the current inbox once; the forward watch takes over for new mail.
- Honest reporting: surface "swept N, acted on M" rather than silently advancing the cursor.
- Scope is configurable (all
in:inbox / unread only / last N days) and opt-in per watch.
This composes with the general criterion→action primitive (see linked issue) — a backlog sweep is what makes batch actions (e.g. "move all existing marketing to spam") meaningful, not just future mail.
Acceptance criteria
- Creating a watch can optionally run a one-time backlog sweep over the existing inbox, bounded and paginated, that classifies + acts on every qualifying email (not just the newest).
- The sweep cannot run away on a huge inbox (hard cap + paging), and reports what it covered and what it dropped.
- The forward watch continues unchanged for newly-arrived mail (no double-processing across the sweep/forward boundary).
Notes / pointers
- Relevant code:
skills/google/gmail-watch/scripts/detect.ts (seeding regime, after: watermark, the truncated-window notice), src/state/email-watchers.ts, ADR docs/adr/email-watch.md.
- Related: the general mail-rule (criterion → action) primitive issue — the sweep is the batch-action enabler for it.
Summary
An email watch (and whole-inbox triage) is a forward-only monitor. On the first (seeding) tick it baselines the cursor at the newest matching email and drafts at most that one; every older matching email already sitting in the inbox falls below the
after:<watermark>clause and is never listed again. So if you set up "watch investor inbound" and several unanswered investor emails are already in your inbox, the watch processes at most the single newest one and silently skips the rest.Accepted for now: picking up the latest matching email on seed is fine. This issue tracks adding a one-time backlog sweep so a watch can also work through mail that was already waiting when it was created.
Why this happens
skills/google/gmail-watch/scripts/detect.ts, seeding regime (~detect.ts:733-781):cursor = newest.internalDateand drafts onlywindow.ids[0](the newest), recording boundary-second siblings asseen.after:<cursorSec>(detect.ts:717-725), so anything older than the seed cursor can never be re-listed — newness is defined by the forward-only watermark, not by read/unread state.detect.ts:796-799).Net: the existing-backlog case is unhandled by design — the watch only ever sees mail that arrives after it was created.
Proposed approach
Add a one-time, bounded backlog sweep as a distinct mode from the forward watch:
after:watermark — the sweep walks the current inbox once; the forward watch takes over for new mail.in:inbox/ unread only / last N days) and opt-in per watch.This composes with the general criterion→action primitive (see linked issue) — a backlog sweep is what makes batch actions (e.g. "move all existing marketing to spam") meaningful, not just future mail.
Acceptance criteria
Notes / pointers
skills/google/gmail-watch/scripts/detect.ts(seeding regime,after:watermark, the truncated-window notice),src/state/email-watchers.ts, ADRdocs/adr/email-watch.md.