MalloryEstate Briefing
M·027Designed
← All briefings

Notifications & Nudges

Notification Triggers

Notifications become nudges when they matter — deterministic app-level overrides fire instantly, everything else is judged on the existing extract cron, all routed through the mode-aware nudge engine.

Dated 2026-06-21Programme Notifications & Nudges

Situation

Building directly on Slice 1's notification-extract cron and the live nudge engine, this is the P2 "trigger" reaction of the Notification Intelligence track: making Mallory react to a notification as it happens, surfacing the ones that matter (a person you care about, an urgent/invoice/delivery-failed alert) while staying quiet about the rest.

The only action triggers take is firing a nudge through the existing engine, which already mode-routes every nudge (interrupt/batch/defer-to-Updates, quiet-hours-aware, high always breaks through) — so triggers add zero new delivery machinery, just two new ways to decide when to fire one.

Key decisions

Hybrid matching: instant overrides plus batched judgment

A hard, user-set app-level allow/mute list fires or blocks deterministically on notification arrival with no model call; everything else is judged by the LLM on the existing 2-hour notification-extract cron.

Action = nudge only

Triggers never invent new delivery machinery — placement (push/batch/Updates) is entirely handled by the existing mode-gated nudge engine.

Shared dedupeKey coordinates both paths

Both the instant-override and batched-judgment paths key on trigger:<eventId> against the existing nudgeSeen ledger, so if judgment later flags an event the override already fired, the engine silently drops the duplicate.

Trigger nudges fire at normal urgency in v1

Even an alwaysNudge app still respects quiet hours and mode-gating — no override implies always-interrupt; per-override urgency and model-flagged high are explicitly deferred.

emitNudge extracted as the one justified refactor

The emit logic inline in handleNudge is pulled into a reusable emitNudge(env, payload) function specifically because two new callers (the override path and the judgment path) now need it, alongside the existing /nudge route.

Architecture

A deterministic on-arrival path and a batched LLM-judgment path both funnel into one shared emit function, deduped by a per-event key.

src/lib/triggers.ts
Pure, closed module: parseTriggerConfig, overrideVerdict (always/mute/null), triggerDedupeKey, alertToNudge — all unit-tested.
triggers:config (KV) + GET/POST /triggers/config
Access-gated route mirroring /reminders/config; backs a two-list (always-nudge, mute) editor in web /settings.
emitNudge (src/lib/nudge.ts or routes/nudge.ts)
Extracted shared emit path (parse → shouldSendNowinsertNudge/notify) now called by /nudge, the override path, and the judgment path.
handleNotificationIngest (src/routes/ingest.ts)
Gains ctx; after storing events, ctx.waitUntil runs the override path per notification — alwaysNudge fires immediately, mute is skipped from trigger consideration (still stored for recall).
runNotificationExtract (src/cron/notificationExtract.ts, extended)
Prompt now numbers input notifications and returns an alerts:[{i,reason}] array alongside facts/reminders; muted apps excluded from candidates.

Risks & guardrails

Double-firing on the same event

Prevented by the shared per-event dedupeKey — an override-fired event is already 'seen' when judgment later reconsiders it, so the engine drops the duplicate rather than firing twice.

Override path blocking or delaying ingest

Runs entirely in ctx.waitUntil, never blocking or failing the ingest response; emitNudge failures are swallowed best-effort like the existing nudge path.

No config means no false positives

Absent triggers:config or KV, overrides are simply empty and behaviour degrades to pure LLM judgment rather than failing closed or open unpredictably.

Delivery

Pure triggers lib + config route

triggers.ts built under tests first, then GET/POST /triggers/config wired and Access-gated.

On-arrival override path

handleNotificationIngest threads ctx and fires the deterministic always/mute path via emitNudge.

Batched judgment path

notificationExtract's prompt and parser extended to emit an indexed alerts array, mapped back to events and emitted via emitNudge.

Settings + full-suite verification

Web settings section for the two app lists; full-suite verification before PR — no D1 migration (KV only), no Android changes, deploy held for Alex.

Out of scope

Per-override urgency or always-interrupt behaviourApp-to-hat taggingModel-flagged high urgencyKeyword/person overrides (already covered by LLM judgment, not needed as explicit config)The P0 typed-event foundation and a dedicated reminders/triggers history UI
spec · docs/superpowers/specs/2026-06-21-notification-triggers-design.md
plan · docs/superpowers/plans/2026-06-21-notification-triggers.md
Mallory · Estate briefing