MalloryEstate Briefing
M·026Designed
← All briefings

Notifications & Nudges

Notification Memory & Reminders

Mining captured Android notifications into durable facts (via the #90 review queue) and time-bound reminders (via a dormant reminders table) — reusing nearly everything already built.

Dated 2026-06-21Programme Notifications & Nudges

Situation

Android notifications have been captured as events since Wave 2a but used only passively for recall/briefing. This is the P3 "memory" slice of the Notification Intelligence track (Events→Reactions: P0 typed events, P2 triggers, P3 memory, P4 acting) — chosen first for highest immediate value and heaviest reuse of dormant infrastructure: the reminders table + helpers already exist with insertReminder having no caller, the briefing already surfaces reminders, Pebble timeline delivery is already wired, and the #90 facts pipeline and extractor pattern (runMemoryExtract) are proven.

A single new extraction pass (runNotificationExtract) splits each notification into durable facts (→ the reviewed #90 queue) and time-bound reminders (→ the reminders table, auto-active); the only genuinely new mechanism is a category-gated push cron firing at due time, since no due-time fire path exists today.

Key decisions

Facts stay reviewed; reminders go auto-active

Facts keep the #90 gate (proposed → Alex confirms) since they're durable and sensitive; reminders bypass manual review because a time-critical reminder can't wait for the app to be opened, and a wrong one is low-harm (one briefing line or push, dismissable).

category is orthogonal to the existing type column

reminders.type stays the legacy delivery-channel marker (alarm/timeline/notification, kept for the dormant helpers + Pebble path); a new category column (appointment/travel/delivery/payment/follow-up/other) is what actually gates push.

Category→delivery config, not per-item review

A KV-backed map defaults appointment/travel/payment to push and delivery/follow-up/other to brief-only, editable in /settings — this is what makes automatic delivery coherent without per-item friction.

Conservative due-date resolution

The model resolves relative/absolute times to an absolute Europe/London due_at; a reminder with no resolvable due is dropped (or surfaced as a fact instead) rather than guessed.

P0 typed events deliberately not built first

The extractor is LLM-driven and reasons over raw title/text directly; deterministic typing only pays for itself for rule-based trigger matching (P2), so it stays out of this slice.

Architecture

A new best-effort cron mines notification events into facts and reminders through existing pipelines; a second new cron adds the missing due-time push fire path.

runNotificationExtract (src/cron/notificationExtract.ts)
New cron mirroring runMemoryExtract — reads notification events since a high-water mark, calls a fast model for strict JSON {facts, reminders}, writes facts via insertMemory and reminders via insertReminder; never throws.
reminders.category column (migration 022)
Nullable content-category plus a source marker distinguishing notification-derived reminders; back-compat safe.
Category→delivery config (CONTEXT_KV + /settings editor)
Map of category to push|brief with a code default; six categories editable.
runReminderPush (src/cron/reminderPush.ts)
New due-poll cron selecting pending reminders in the push-set within a lead window, firing via notify() and marking them fired — the status transition is the dedupe.
runMorningBriefing (existing, reused)
getUpcomingReminders already surfaces all upcoming reminders — notification-derived ones appear automatically, no change needed.
pushTimelinePin (Pebble, existing, reused)
Already wired into the briefing; no change required for this slice.

Risks & guardrails

Guessed due dates

Mitigated by dropping any reminder whose time can't be confidently resolved, rather than inventing a date.

Push noise from over-eager extraction

Mitigated by a conservative extraction prompt (mirrors runMemoryExtract's "skip transient/trivial/uncertain") and by defaulting most categories to brief-only rather than push.

Re-processing the same notifications repeatedly

A processed-event high-water mark (KV) prevents the extractor from re-mining notifications it has already seen each run.

Silent reminder writes without review

Accepted risk, explicitly reasoned through: reminders are time-bound and low-harm to get wrong (dismissable, one push/briefing line), unlike durable facts which keep the #90 gate.

Delivery

Migration + db layer

Migration 022 adds reminders.category (+ source); pure extraction/config/gate logic built under tests first.

Extract + push crons

runNotificationExtract and runReminderPush built and registered in the scheduled() dispatch.

Config route + settings UI

GET/POST reminder-config route plus a six-category push/brief editor in web /settings.

Verification + deploy

Full-suite + type-check verification; PR-only, D1 migration 022 required at deploy, held for Alex.

Out of scope

Dedicated in-app reminders UI (briefing surfaces them in v1)Snooze/recurring reminders or per-item lead-time tuningTriggers (P2), typed-event foundation (P0), and notification-acting (P4) — separate brainstormsRicher notification capture (typed category/person/actionable on ingest)
spec · docs/superpowers/specs/2026-06-21-notification-memory-reminders-design.md
plan · docs/superpowers/plans/2026-06-21-notification-memory-reminders.md
Mallory · Estate briefing