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
- Undo Toast ā Floating notification with countdown timer
- Conflict Badge ā Header indicator with count
- Conflict Detail Panel ā Side-by-side event comparison
- Calendar Week View ā Grid layout with person colors
- Calendar Day View ā Timeline with travel markers
- Digest Card ā Collapsible category sections
- 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
- Undo Authorization: Only original
sender_idcan undo their own operations. TTL: 10 minutes (resolved 2026-04-21). - 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).
- Dashboard Auth: Passwordless token link per standard dashboard protocols. Time-limited, revokeable, IP allowlist optional (resolved 2026-04-21).
- 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