# Enriched Shadow Mode — Architecture Spec **Version:** 2.0 **Status:** Approved for Implementation **Date:** 2026-05-02 --- ## Parameter Lock (Director Constraints) | Constraint | Value | Enforcement | |------------|-------|-------------| | **Speak Policy** | Strict Mute | `SPEAK_ENABLED=False` hard-coded, never overridden | | **Calendar Access** | Read-Only | `calendar.readonly` scope at API credential level | | **Query Trigger** | Event-Driven | Only on Tier 1 tripwire fire | | **Auto-Write** | Prohibited | Even confirmed actions only log to staging.db | | **Exhaust Channel** | Admin DM | Proposed actions sent to Matt's private Telegram DM | --- ## Flow Diagram ``` Family Chat Message │ ▼ ┌───────────────────┐ │ Tier 1: Tripwire │── Score ≥ 0.4? ──► Log silently (done) │ (regex/keywords) │ └───────────────────┘ │ Yes ▼ ┌───────────────────┐ │ Tier 2: LLM │── Extract who/what/when/where/confidence │ Extraction │ └───────────────────┘ │ ▼ ┌───────────────────┐ │ [NEW] Calendar │── Query Google Calendar (readonly) │ Validation │── Fuzzy match event title │ (Event-Driven) │── Check person/time/location overlap └───────────────────┘ │ ├─ MATCH ───► Log: "Would have confirmed existing event" │ (No DM needed, silent) │ ├─ NO_MATCH ─► Admin DM: "I would have added [Sully Pickup] │ to calendar for tomorrow 8 AM. Accurate? [Yes] [No]" │ ► [Yes] → Log to staging.db (not real calendar) │ ► [No] → Log to staging.db with rejection flag │ └─ CONFLICT ─► Admin DM: "Calendar shows 'Sully haircut' at 9 AM, but message says 8 AM. Which is correct? [9 AM] [8 AM]" ► Either answer → Log to staging.db for tuning ``` --- ## Google Calendar Integration ### API Scope - **Required:** `https://www.googleapis.com/auth/calendar.readonly` - **Forbidden:** `calendar` (read-write), `calendar.events` - **Enforcement:** OAuth consent screen + API key restrictions ### Query Strategy ```python # Event-driven only — called from shadow_bot.py after Tier 2 extraction calendar.check_event( date_range=(extracted_date - 1 day, extracted_date + 1 day), query_text=extracted_what, # "Sully haircut" family_member=extracted_who, # "Sully" ) ``` ### Fuzzy Matching - Event title similarity ≥ 0.6 (fuzzy string match) - Date overlap (same day) - Optional: time overlap if extracted --- ## Admin DM Exhaust Format ### NO_MATCH Alert ``` 🔍 Shadow Mode — Proposed Calendar Action Message: "I've got Sully tomorrow" Extracted: Pickup Sully, tomorrow, unspecified time Calendar: ❌ No matching event found I would have added: 📅 "Sully Pickup" — tomorrow (all day) Accurate? [Yes] [No] [Edit] ⚠️ This is a simulation. Your calendar has not been modified. ``` ### CONFLICT Alert ``` 🔍 Shadow Mode — Calendar Conflict Detected Message: "Sully's haircut at 8" Extracted: Sully haircut, tomorrow 8:00 AM Calendar: ✅ "Sully Haircut" — tomorrow 9:00 AM Conflict: Time mismatch (8 AM vs 9 AM) Which is correct? [📅 Keep Calendar: 9 AM] [💬 Use Message: 8 AM] [❓ I'm Not Sure] ⚠️ This is a simulation. Your calendar has not been modified. ``` ### MATCH Confirmation (Silent — No DM) ``` Logged silently to staging.db: "Sully pickup" — matched existing event "Sully Pickup" (tomorrow 3 PM) Action: None needed ``` --- ## Staging Database Schema Update ```sql -- Add calendar validation columns to shadow_extractions ALTER TABLE shadow_extractions ADD COLUMN calendar_check_status TEXT; -- match | no_match | conflict ALTER TABLE shadow_extractions ADD COLUMN calendar_event_id TEXT; -- Google Calendar event ID ALTER TABLE shadow_extractions ADD COLUMN calendar_event_title TEXT; -- Matched event title ALTER TABLE shadow_extractions ADD COLUMN calendar_event_time TEXT; -- Matched event time ALTER TABLE shadow_extractions ADD COLUMN admin_dm_sent BOOLEAN DEFAULT FALSE; ALTER TABLE shadow_extractions ADD COLUMN admin_response TEXT; -- yes | no | edit | timeout ALTER TABLE shadow_extractions ADD COLUMN would_have_written BOOLEAN; -- What we would have done ``` --- ## Implementation Plan | Phase | Task | Owner | ETA | |-------|------|-------|-----| | 1 | Read gog skill for calendar.readonly auth | Wadsworth | 15 min | | 2 | Add CalendarValidator class to shadow_bot.py | Socrates | 30 min | | 3 | Implement fuzzy matching logic | Socrates | 45 min | | 4 | Add Admin DM exhaust (Telegram DM routing) | Wadsworth | 30 min | | 5 | Update staging.db schema | Socrates | 15 min | | 6 | Integration test with real calendar queries | Wadsworth | 20 min | | 7 | Deploy and monitor | Wadsworth | 10 min | --- ## Security Safeguards ### API Credential Scoping ```json { "client_id": "...", "scopes": ["https://www.googleapis.com/auth/calendar.readonly"], "restrictions": { "allowed_ips": ["Tailscale IPs only"], "rate_limit": "100 queries/day" } } ``` ### Write Protection - `gog` skill must fail if write scope requested - Code review: No `events.insert()` or `events.update()` calls in shadow mode - Runtime check: Assert `readonly` in token scope before any query ### Rate Limiting - Max 1 calendar query per tripwire fire - Max 50 queries/day total (Google quota protection) - Exponential backoff on 429 errors --- ## Metrics to Track | Metric | Why | |--------|-----| | Calendar match rate | How often extractions match existing events | | False positive rate | How often NO_MATCH alerts are rejected by Matt | | Conflict resolution | Which source Matt trusts more (calendar vs message) | | Admin response time | How quickly Matt replies to DM exhaust | | Daily query volume | Stay under Google's radar | --- ## Failure Modes | Scenario | Behavior | |----------|----------| | Google API 429 | Skip calendar check, log "API rate limited" | | Token expired | Skip calendar check, alert Wadsworth in logs | | Calendar unavailable | Skip check, treat as NO_MATCH (safe default) | | Fuzzy match ambiguous | Log both candidates, treat as CONFLICT | | Admin DM timeout (5 min) | Log "timeout", default to no action | --- ## Files to Modify | File | Change | |------|--------| | `services/icarus/core/observer/shadow_bot.py` | Add CalendarValidator class, integrate into flow | | `services/icarus/core/observer/shadow_database.py` | Add calendar columns to schema | | `services/icarus/core/observer/shadow_telegram.py` | Add Admin DM exhaust method | | `services/icarus/calendar_adapter.py` | [NEW] Google Calendar wrapper (readonly) | | `services/icarus/shadow.env` | Add calendar credentials path | | `services/icarus/requirements-shadow.txt` | Add google-api-python-client | --- **Approved by:** Matt (Director) **Date:** 2026-05-02 **Next Action:** Wadsworth to read gog skill, then dispatch Socrates for implementation