MalloryEstate Briefing
M·028Approved
← All briefings

Notifications & Nudges

Situation Board

One merged, self-draining board — Mallory's editorial judgement about what genuinely needs Alex, with enough context that he never opens Slack to triage.

Dated 2026-07-18Programme Notifications & Nudges

Situation

An audit against live prod D1 found the Response-SLA engine working but invisible: channel_pending held 142 ball-in-court items, 130 auto-resolved the moment Alex replied, 12 aging 24–99h — and 0 of 142 had ever been nudged. The signal was excellent; the delivery was nil.

Meanwhile Mallory's pushes were a slower echo of the phone's own notifications — competing on the one axis he loses (speed) instead of his strengths (judgement, memory, cross-source curation). Three overlapping surfaces (Updates feed, WaitingCard, /waiting) fragmented the experience, two producers watched the same Slack/Gmail, calendar nudges had never fired, and all 283 reminders had no feed UI at all.

Key decisions

One merged board

A single self-draining situation board replaces the three-surface split. Two folds: a tiny primary fold (editor's note + *On your court*), and a secondary fold (*Coming up*, *Remember*, *Handled/FYI*) that is context you can check, not work you must do.

Hero = judgement + context

Hard triage plus one line of *why it matters* per item. Visible auto-resolution was explicitly considered and *not* chosen as the hero — the drain stays a quiet background truth.

Live board + editor's note

Hybrid build: a deterministic live layer assembles from the durable stores with no model call (instant, drains live), overlaid by a periodic LLM ranking/voice pass 2–3× a day. Never stale-blank, just sharper at each tick.

Ambient by default, breach escalates

Nothing routine pushes — the native Slack/Gmail notification already did the real-time job. This retires the 2-hourly mention digest. Only a genuine breach fires unscheduled; warnings stay ambient on the board.

One court source of truth

channel_pending absorbs the mention monitor's intelligence via the existing-but-unused triage: 'llm' hook, cached once per item at capture. The parallel nudge producer retires.

Configurable composer model

The composer's model is a slot in the Model Settings surface — default a cheap tier, swappable to OpenRouter later. Explicitly not a hardcoded constant.

Architecture

Two layers. The live layer assembles zones deterministically from D1 on every request; the editorial layer writes a ranked shortlist + voice line to KV a few times a day, which the live board overlays on its rule-based order.

src/lib/board.ts
Pure assembleBoard() — zone assembly plus the editorial rank/park overlay. No I/O, fully unit-tested.
src/routes/board.ts
GET /board aggregator: one call returns note, court, events, reminders, handled.
src/cron/boardCompose.ts
The composer pass at briefing / EOD / a new midday tick — writes board:editorial (note + rankedIds + parkedIds) to KV.
migration 024
channel_pending.summary + wants_action (the cached triage verdict); new calendar_events cache table for the *Coming up* zone.
responseSlaPoll / responseSlaEval
Poll gains once-per-item LLM triage; eval tightens its push gate to breach-only.
SituationBoard.svelte
Replaces UpdatesView; WaitingCard + ChatPointer collapse to one pointer; /waiting deep-links into the court zone.

Risks & guardrails

Ruthless curation hiding something

*Handled / FYI* keeps the quiet record of what auto-resolved and what Mallory decided didn't need Alex — collapsed by default, but present so the judgement is auditable. Auditability is what earns trust in the curation.

Triage cost runaway

Triage runs once per item — at first capture or when the snippet changes — never on an unchanged 15-minute tick. Cost stays near the old 4×/weekday level, not 96×/day.

Breach-gate regression

The highest-risk behavioural change: most test cases live here. Quiet hours and the runaway hard cap still apply on top.

Nudge history churn

nudges is demoted to a push ledger — no longer a display source — so the 3-day prune and the broken response_sla rendering both become non-issues.

Delivery

Phase 1 — Data foundation

Migration 024, calendar_events + board-reminder db helpers, triage-aware upsertPending, pure zone assembly, GET /board + GET /reminders.

Phase 2 — Judgement + delivery

Composer prompt/parse with a configurable model, the composer cron at briefing/EOD/midday, breach-only push gate, once-per-item LLM triage on the poll.

Phase 3 — Runtime producers

POST /ingest/calendar upserting calendar_events (Access-gated); retire the VM mention-nudge path.

Phase 4 — Web surfaces

loadBoard() + boardAction() API client, SituationBoard replacing UpdatesView, one chat pointer, /waiting redirecting into the board.

Out of scope

The ntfy availability channel — parked for a future discussionHats / mode-based board framing — considered and not chosen; the board is not organised by life-area in v1Any change to the investigations/Digs or Loops trays
spec · docs/superpowers/specs/2026-07-18-situation-board-design.md
plan · docs/superpowers/plans/2026-07-18-situation-board.md
Mallory · Estate briefing