Notifications & Nudges
Mention Nudges
Rewriting the last prose-only Wave 4 job into structured fetch → deterministic jq, so unanswered Slack/Gmail threads become permanent-dedupe batch nudges.
Situation
jobs/slack-email-monitor.sh today only writes prose to the daily note and emits no nudges. This design replaces it with the calendar-sync.sh pattern: quarantine the LLM to a single structured fetch (Slack + Gmail ball-in-court items as a JSON array), then run every downstream step — normalise, dedupe, emit, render — as deterministic jq. It's the last open slice of Wave 4 (Proactive & Notifications).
Unlike calendar-sync, mention-nudges needs no local membership store: the Worker's nudges table is already a permanent dedupe ledger, so each unique mention/thread id nudges at most once, ever, and the Hub just emits current candidates on every run.
Key decisions
Ball-in-court rule, LLM-judged
Include a thread only if the latest message isn't from Alex (by Slack id / GMAIL_SELF_ADDRS) within a 7-day lookback; wants_action is the LLM's secondary judgment of whether it's a real ask vs FYI.
Always batch, never urgent
Every mention nudge is batch urgency, held for the existing 2h flush cron — no keyword-triggered now escalation.
Permanent dedupe, asymmetric keying
Slack keys on channelId:messageTs (a new @mention re-nudges); Gmail keys on threadId (a same-thread follow-up won't re-nudge) — a documented, intentional limitation.
Overwritten artifact, not appended
_generated/needs-response.md is fully overwritten each run rather than appended, avoiding 4x/day daily-note clutter; empty results render an 'all clear' body and emit no nudges.
Multi-account ready but not built
GMAIL_SELF_ADDRS is a comma-list from day one so a second Google account (MK Community Energy) is a config-only addition later, not a rearchitecture.
Architecture
A single LLM structured pull feeds a deterministic jq pipeline, dedupe against a permanent Worker-side ledger, and an overwritten vault artifact.
claude -p structured pull → tolerant JSON extraction → jq normalise/dedupe → post_nudge → render → vault_commit.claude -p call outputs only a JSON array (source/id/from/channel/subject/summary/wants_action/permalink/last_ts) — no prose, no fences.nudgeSeen(dedupeKey) makes every id fire at most once across all runs.last_ts, committed only when content changes.MENTION_LOOKBACK_DAYS (default 7), SLACK_SELF_ID, GMAIL_SELF_ADDRS drive the ball-in-court check without hardcoding identity.Risks & guardrails
Gmail follow-ups go silent
Keying Gmail dedupe on threadId means a second ask in an already-nudged thread never re-fires — accepted as a documented limitation, not fixed in this build.
Unparseable LLM output kills the run safely
A non-array or unparseable pull triggers log+alert+record_status fail and exit 1 — no nudges, no render, artifact left untouched, mirroring calendar-sync's error handling.
Wide lookback is safe only because dedupe is permanent
A 7-day lookback would double-nudge under a diff-based model; it's safe here specifically because nudgeSeen never forgets an id.
Delivery
Config + pure jq transforms (TDD)
Config vars added, then lib/mention.jq and a tolerant JSON-array extractor built test-first against fixtures.
Orchestrator rewrite
jobs/slack-email-monitor.sh replaced end-to-end with the fetch → jq → emit → render pipeline; README schedule updated (08/11/14/17 weekdays).
PR + VM deploy
Merged via the alexhansford identity, then deployed to the VM crontab — both authorized steps for this wave, distinct from the usual 'held for Alex' pattern.
Out of scope
docs/superpowers/specs/2026-06-13-mention-nudges-design.mdplan ·
docs/superpowers/plans/2026-06-13-mention-nudges.md