šŸ“„ SCOPE.md 15,072 bytes Apr 21, 2026 šŸ“‹ Raw

Family Assistant v1.5 — Scope Document

Status: Draft
Date: 2026-04-21
Owner: Socrates (Backend) + Daedalus (Frontend)
Approver: Matt (pending)
Target Release: Post v1.0 stabilization


Executive Summary

Family Assistant v1.0 is feature-frozen and operational. v1.5 addresses trust and reliability gaps identified in the April 2026 audit — human error recovery, persistent state management, and graceful degradation.

This document follows contract-first protocol: API specs are binding on backend; UI/UX requirements are binding on frontend.


Goals

Priority Goal Success Metric
P0 Eliminate silent data loss scenarios User can undo any calendar mutation within 5 minutes
P0 Make conflicts actionable Conflicts don't disappear after first alert; nag until resolved
P1 Ensure Family Brain availability when Gaming PC sleeps Degrade to keyword search; no hard failures
P1 Give Aundrea phone-less calendar access Web dashboard with read-only family calendar view
P2 Improve newsletter signal-to-noise Formatted digest, not raw item dumps
P2 Proactive travel notifications "Leave in 15min" alerts based on cached travel times

Features

1. Undo Stack (P0)

Problem: Intent engine mutations (move, cancel, rename) are destructive and irreversible. Aundrea has no "oops" path.

Solution: Persist last 5 operations with reversible snapshots.

API Contract (Socrates)

# POST /api/v1/undo
# Authorization: Bearer token (same auth as webhook)
# Request:
{
  "action": "list" | "undo" | "redo",
  "operation_id": "uuid"  # required for undo/redo
}

# Response (list):
{
  "operations": [
    {
      "id": "uuid",
      "type": "move" | "cancel" | "rename" | "add",
      "summary": "Sullivan Soccer",
      "executed_at": "2026-04-21T14:30:00-05:00",
      "reversible_until": "2026-04-21T14:35:00-05:00",  # 5 min TTL
      "can_undo": true,
      "snapshot": {
        "event_id": "caldav-uuid",
        "previous_state": { ... },  # full iCal VEVENT snapshot
        "new_state": { ... }
      }
    }
  ]
}

# Response (undo):
{
  "status": "undone" | "expired" | "not_found",
  "restored_event": { ... },
  "message": "Sullivan Soccer restored to original time"
}

Storage: ~/.family_assistant/undo_stack.jsonl — append-only, capped at 5 entries, 5-min TTL per entry.

Security: Operations reversible only by same Telegram user who initiated (check sender_id).

Daedalus Requirements

  • Telegram Integration: Add "ā†©ļø Undo" button to Hermes confirmation messages for 5-minute window
  • Visual State: Show "Recently modified" list in dashboard with undo affordance
  • UX Pattern: Toast notification style: "Moved Sullivan Soccer to Wednesday [Undo]"

2. Persistent Conflict Registry (P0)

Problem: Conflict alerts fire once and disappear. No persistent "outstanding conflicts" list.

Solution: SQLite-backed conflict registry with nag escalation.

API Contract (Socrates)

# GET /api/v1/conflicts
{
  "outstanding": [
    {
      "id": "uuid",
      "detected_at": "2026-04-21T08:00:00-05:00",
      "event1": { "summary": "Sullivan Soccer", "start": "...", "end": "..." },
      "event2": { "summary": "Harper Dance", "start": "...", "end": "..." },
      "overlap_minutes": 30,
      "status": "unacknowledged" | "acknowledged" | "resolved",
      "nag_count": 2,  # incremented on each heartbeat if unacknowledged
      "resolution": null | { "action": "split", "choice": 1, "executed_at": "..." }
    }
  ]
}

# POST /api/v1/conflicts/{id}/acknowledge
{ "status": "acknowledged" }  # Stops nagging, keeps in registry

# POST /api/v1/conflicts/{id}/resolve
{
  "resolution_action": "split" | "reassign" | "reschedule",
  "choice_index": 0,  # which option was selected
  "executed": true | false  # whether calendar was actually modified
}

Storage: SQLite at ~/.family_assistant/conflicts.db — single table, indexed on status and detected_at.

Nag Logic:
- Heartbeat:check runs every 30m
- If unacknowledged and nag_count < 3: send alert to family group
- If nag_count >= 3: escalate to DM with "āš ļø UNRESOLVED CONFLICT" urgency

Daedalus Requirements

  • Conflict Badge: Dashboard shows conflict count in header
  • Conflict Detail View: Side-by-side event comparison with resolution options
  • Mobile Optimization: Swipe-to-acknowledge, tap-to-resolve pattern
  • Visual Priority: Red accent for unacknowledged, yellow for acknowledged, green for resolved

3. Brain Offline Fallback (P1)

Problem: If Gaming PC sleeps or Tailscale is down, nomic-embed-text fails. Family Brain throws exception.

Solution: Try embeddings → if fail, fall back to keyword search against raw email chunks.

API Contract (Socrates)

# POST /api/v1/brain/query
{
  "question": "What do the kids need for the field trip?",
  "force_keyword": false  # optional: bypass embeddings entirely
}

# Response:
{
  "answer": "...",
  "mode": "semantic" | "keyword_fallback",  # transparency to user
  "sources": [...],
  "confidence": "high" | "medium" | "low",
  "embeddings_available": true | false
}

Fallback Logic:
1. Try nomic-embed-text on Gaming PC (timeout: 5s)
2. If timeout/error: search ChromaDB metadata (subject, sender, date) with keyword matching
3. If still no results: search raw email body text (cached in ~/.family_assistant/email_cache/)

Health Check: Add embeddings_reachable to /health endpoint.

Daedalus Requirements

  • Mode Indicator: Show subtle "offline mode" badge when using keyword fallback
  • Confidence Display: Lower confidence → suggest rephrasing question
  • No User Action Required: Should be transparent, but honesty about quality is important

4. Family Dashboard — Read-Only Calendar View (P1)

Problem: Aundrea doesn't want to install CalDAV clients. Needs phone-less access to family calendar.

Solution: Web dashboard served from hoffdesk-api, read-only CalDAV view.

API Contract (Socrates)

# GET /api/v1/calendar/events
# Query params:
#   start=2026-04-01T00:00:00-05:00
#   end=2026-04-30T23:59:59-05:00
#   include_travel=true  # enrich with travel time from location cache

{
  "events": [
    {
      "id": "caldav-uuid",
      "summary": "Sullivan Soccer",
      "start": "2026-04-21T15:00:00-05:00",
      "end": "2026-04-21T16:00:00-05:00",
      "location": "Golrusk Pet Center",
      "travel_time_minutes": 12,
      "travel_from_home": true,
      "status": "confirmed",
      "who": ["Sullivan"],
      "color": "#3b82f6"  # auto-assigned per-person
    }
  ],
  "conflicts": [  // include outstanding conflicts in same response
    { "id": "...", "event1_id": "...", "event2_id": "...", "overlap_minutes": 30 }
  ]
}

# GET /api/v1/calendar/today
# Shortcut for today's events

# GET /api/v1/calendar/week
# Query param: week_offset=0 (current), -1, +1, etc.

Caching: 5-minute cache on Radicale reads to reduce load.

Auth: Same TELEGRAM_BOT_TOKEN validation as webhook (token in header, IP allowlist optional).

Daedalus Requirements

  • Calendar Views: Day, week, month (month optional for MVP)
  • Week View Default: Family planning happens at weekly granularity
  • Person Colors: Sullivan = blue, Harper = pink, Family = green, etc. (see design-tokens/)
  • Conflict Highlighting: Red stripe on conflicting events
  • Travel Time: Small text under location: "12 min from home"
  • Mobile-First: Touch-friendly, swipe between days/weeks
  • No Login Required: Shareable URL with token (time-limited, revokeable)

5. Newsletter Digest v2 (P2)

Problem: Info items dump raw text to Telegram. Low signal-to-noise.

Solution: LLM-summarized digest with categories.

API Contract (Socrates)

# POST /api/v1/digest/generate (internal, called by pipeline)
{
  "items": [...],  // raw items from newsletter parser
  "style": "brief" | "detailed"  // brief for daily, detailed for weekly
}

# Response:
{
  "sections": [
    {
      "category": "School",
      "icon": "šŸŽ’",
      "items": [
        {
          "summary": "Field trip permission due Friday",
          "who": ["Sullivan"],
          "action_required": true,
          "deadline": "2026-04-25"
        }
      ]
    },
    {
      "category": "Sports",
      "icon": "⚽",
      "items": [...]
    }
  ],
  "markdown": "šŸŽ’ **School**\n• Field trip...\n\n⚽ **Sports**..."  // formatted for Telegram
}

Categories: School, Sports, Medical, Community, Other (configurable in family.yaml)

Prompt: Single LLM call with category definitions, returns structured JSON.

Daedalus Requirements

  • Digest View: Dashboard card with collapsible sections
  • Category Icons: Consistent emoji/icon mapping (design-tokens/)
  • Action Badges: "Needs action" indicator on items with deadlines
  • Mark Done: Inline button to dismiss (triggers intent engine)

6. Proactive Travel Notifications (P2 — Deferred to v2.0)

Status: Deferred — requires sub-30-minute precision, out of scope for v1.5 heartbeat-based system.

Note: Travel time data remains in calendar API responses (travel_time_minutes) for UI display.


Data Model Changes

New Tables/Files

Resource Location Schema Purpose
undo_stack ~/.family_assistant/undo_stack.jsonl JSON Lines, max 5 entries, 5min TTL Reversible operations
conflicts ~/.family_assistant/conflicts.db SQLite: id, event1_id, event2_id, detected_at, status, nag_count, resolution Persistent conflict registry
notification_state ~/.family_assistant/notification_state.json JSON: leave_by_notified: {event_id: timestamp} Deduplication for travel alerts

API Endpoints Summary

Method Path Auth Description
POST /api/v1/undo Bearer List/undo/redo operations
GET /api/v1/conflicts Bearer List outstanding conflicts
POST /api/v1/conflicts/{id}/ack Bearer Acknowledge conflict
POST /api/v1/conflicts/{id}/resolve Bearer Resolve with action
POST /api/v1/brain/query Bearer Query with fallback mode
GET /api/v1/calendar/events Bearer Read-only events
GET /api/v1/calendar/today Bearer Today's events
GET /api/v1/calendar/week Bearer Weekly view
POST /api/v1/digest/generate Internal Newsletter summarization
GET /api/v1/notifications/leave-by Internal Travel alert candidates
POST /api/v1/notifications/mark-sent Internal Mark travel alert sent

Daedalus Deliverables

UI Components Required

  1. Undo Toast — Floating notification with countdown timer
  2. Conflict Badge — Header indicator with count
  3. Conflict Detail Panel — Side-by-side event comparison
  4. Calendar Week View — Grid layout with person colors
  5. Calendar Day View — Timeline with travel markers
  6. Digest Card — Collapsible category sections
  7. Offline Badge — Subtle mode indicator

Design Token Requirements

Token Value Usage
--color-person-sullivan #3b82f6 (blue-500) Sullivan events
--color-person-harper #ec4899 (pink-500) Harper events
--color-person-family #22c55e (green-500) Family events
--color-conflict-unack #ef4444 (red-500) Unacknowledged conflicts
--color-conflict-ack #eab308 (yellow-500) Acknowledged conflicts
--color-travel-marker #6b7280 (gray-500) Leave-by indicators
--undo-toast-ttl 600s Undo button visibility duration (10 min)

Shared Assets

Place in shared/design-tokens/family-assistant/:
- colors.json — Person assignments, conflict states
- icons.json — Category icons for digest
- spacing.json — Calendar grid dimensions


Socrates Deliverables

Backend Modules

Module Purpose
undo_stack.py Persistent operation log with snapshots
conflict_registry.py SQLite CRUD for conflicts
brain_fallback.py Keyword search when embeddings fail
travel_alerts.py Leave-by calculation and notification
digest_generator.py LLM-powered newsletter summarization

Cron/Heartbeat Changes

  • heartbeat:check: Add conflict nag escalation (travel alert scanning deferred to v2.0)
  • family:daily-brief: Include digest v2 format

Prompts Required

  • digest_summarize.txt — Single-shot LLM prompt for categorizing newsletter items

Dependencies

Dependency Status Notes
Radicale CalDAV āœ… Ready v1.5 uses existing connection
ChromaDB āœ… Ready No schema changes
SQLite āœ… Ready Part of Python stdlib
Daedalus Dashboard šŸ”„ Blocked Awaiting design tokens
Gaming PC Online āš ļø Partial Fallback mode handles offline

Testing Strategy

Unit Tests (Socrates)

  • Undo/redo cycle with TTL expiration
  • Conflict registry CRUD with concurrent access
  • Brain fallback accuracy vs. semantic search
  • Travel time calculation edge cases (DST, traffic)

Integration Tests

  • End-to-end: Intent → Calendar → Undo → Restore
  • Conflict detection → Registry → Nag → Resolution

UI Tests (Daedalus)

  • Undo toast visibility and interaction
  • Conflict badge state transitions
  • Calendar responsive breakpoints

Security Considerations

  1. Undo Authorization: Only original sender_id can undo their own operations. TTL: 10 minutes (resolved 2026-04-21).
  2. Conflict Data: No PII in registry beyond event summaries (already in CalDAV). Nag escalation: Matt DM → family group if EOD unaddressed (resolved 2026-04-21).
  3. Dashboard Auth: Passwordless token link per standard dashboard protocols. Time-limited, revokeable, IP allowlist optional (resolved 2026-04-21).
  4. Travel Alerts: Deferred to v2.0.

Decisions (Resolved 2026-04-21)

# Decision Choice Rationale
1 Undo TTL 10 minutes Safer for 7 AM family chaos
2 Conflict Nag Matt DM after 3 alerts Escalate to group chat if unaddressed by end of day
3 Dashboard Auth Passwordless token link Standard dashboard access protocols, sovereign, no account mgmt
4 Travel Buffer Fixed 5 minutes Per-type config deferred to v2.0; keeping cron/heartbeat ≤ 30 min

Open Questions

None — all decisions resolved by Matt on 2026-04-21.


Appendix: API Spec Location

Machine-readable OpenAPI spec: shared/api-specs/family-assistant-v1.5.yaml (to be written after scope approval)


Document version: 1.0-draft
Last updated: 2026-04-21T21:34:00Z