# Next Session: WhatsApp Agent — Phase 5 (Hardening + Full Pipeline)

**Date:** 2026-04-07
**Status:** PLANNED — adversarial review complete (18 findings, all addressed)

## What

Harden the WhatsApp silent observer agent deployed in Phases 1-4. Fix three bugs (sender extraction hardcoded, compaction LLM unreachable, trigger/responder untested), add dashboard observability (creature "jackal"), and implement heartbeat/watchdog hardening. All changes informed by 2 parallel adversarial reviews (architecture + code quality).

## Plan

```
~/.claude/plans/noble-wandering-catmull.md
```

Read the plan first — it has the full implementation, all 18 review findings with resolutions, state machines for concurrent workflows, and pre-mortem failure analysis.

## Key Design Decisions (from adversarial review)

1. **Standalone `wa_observability.py`** — Do NOT use PYTHONPATH to import annie-voice's observability.py. It pulls GPU dependencies (Kokoro, Whisper, Pipecat) that aren't installed on Panda. Instead, create a 50-line standalone HTTP emitter in `services/whatsapp-agent/wa_observability.py` that POSTs directly to CE.

2. **Shared `LLMCircuitBreaker`** — Circuit breaker must be shared across ALL 3 LLM call sites (compaction, trigger, responder), not just compaction. During a Titan outage, trigger/responder each burn 30s timeouts per message without a shared breaker. Class goes in `config.py`, threshold=3, auto-reset after 300s.

3. **Mark-and-sweep compaction** — `await compact_hourly()` yields to the event loop. The drain loop can add messages to raw_messages during that yield. `clear_raw_messages()` then deletes them — never compacted, gone forever. Fix: `clear_raw_messages(up_to=snapshot_count)` only clears messages that were in the snapshot.

4. **Emergency trim preserves raw messages** — When circuit breaker is open, create a minimal digest but do NOT clear raw_messages. They persist until the LLM recovers and the next compaction processes them properly.

5. **Remove "re" from sender blocklist** — It collides with Indian name "Re" (Rekha). Apply blocklist only to single-word prefixes, case-insensitive.

6. **Sanitize extracted sender** — Strip non-alphanumeric (keep spaces/hyphens/apostrophes), limit 30 chars. The extracted sender goes directly into LLM format strings (`{sender}` in trigger + responder prompts) — without sanitization, it's a 50-char prompt injection window.

7. **ADB watchdog must use async subprocess** — `subprocess.run()` blocks the entire event loop. Use `asyncio.create_subprocess_exec` with `asyncio.wait_for(timeout=10)`.

8. **Creature count tests** — Adding jackal (creature 50) breaks 6+ hardcoded `49` count assertions. Must update: test_chronicler.py, test_chronicler_llm.py, creatureRegistryCompleteness.test.ts, creatures.test.ts.

9. **Async I/O wrappers** — `is_duplicate()` (sync disk read+write in async handler) and `write_ce_segment()` (blocking fcntl.flock in async context) must be wrapped in `asyncio.to_thread()`.

10. **First trigger per batch only** — If multiple "annie" messages arrive in one drain batch, process only the first trigger. Subsequent ones hit cadence cooldown anyway, but without this guard they each burn 30s of LLM+u2 blocking time.

## Files to Modify (16 total, ordered by step)

### Step 1: Sender Extraction
1. `services/whatsapp-agent/receiver.py` — Add `_extract_sender()`, async dedup wrapper
2. `services/whatsapp-agent/config.py` — Add `SENDER_EXTRACT_BLOCKLIST` (without "re")
3. `services/whatsapp-agent/tests/test_receiver.py` — 8+ new tests
4. **Tasker on Pixel** — Change body to `{"group":"%antitle","sender":"%antitle","text":"%antext"}`

### Step 2: Compaction LLM Fix
5. `services/whatsapp-agent/config.py` — `LLMCircuitBreaker` class + singleton
6. `services/whatsapp-agent/compaction.py` — Error classification, circuit breaker, async CE write
7. `services/whatsapp-agent/trigger.py` — Error classification + circuit breaker check
8. `services/whatsapp-agent/responder.py` — Error classification + circuit breaker check
9. `services/whatsapp-agent/context_mgr.py` — `clear_raw_messages(up_to=None)` param
10. `services/whatsapp-agent/agent.py` — Startup check, mark-and-sweep in compaction loop

### Step 4: Dashboard Creature "jackal"
11. `services/whatsapp-agent/wa_observability.py` — NEW standalone emitter
12. `services/context-engine/chronicler.py` — Creature + event type registration
13. `services/annie-voice/observability.py` — Sub-registry entry
14. `services/context-engine/dashboard/src/types.ts` — CreatureId + EventType
15. `services/context-engine/dashboard/src/creatures/registry.ts` — Accent, entry, layout
16. `services/context-engine/dashboard/src/creatures/silhouettes.ts` — drawJackal()

### Test files (creature count fix)
17. `services/context-engine/tests/test_chronicler.py` — Count 49→50
18. `services/context-engine/tests/test_chronicler_llm.py` — Count 49→50
19. `services/context-engine/dashboard/tests/creatureRegistryCompleteness.test.ts` — Add 'jackal'

### Step 5: Hardening
20. `services/whatsapp-agent/agent.py` — Heartbeat, ADB watchdog (async), first-trigger-per-batch, emit events

## Pre-Implementation Checklist

```bash
# 1. Read the full plan (all adversarial findings are in it):
cat ~/.claude/plans/noble-wandering-catmull.md

# 2. Read the WhatsApp agent memory:
cat ~/.claude/projects/-home-rajesh-workplace-her-her-os/memory/project_whatsapp_observer.md

# 3. Verify Panda .env has LLAMACPP_BASE_URL (root cause of compaction failure):
ssh panda "grep LLAMACPP ~/workplace/her/her-os/.env"

# 4. Implement Steps 1+2 (can be parallel), then Step 3 (live test), then Steps 4+5 (parallel)
```

## Verification

1. `pytest services/whatsapp-agent/tests/` — all pass
2. `npm test` in dashboard/ — creature count 50, jackal in registry
3. `./start.sh whatsapp` — startup log shows "LLM connectivity OK"
4. Send DM + group message — correct sender in logs
5. Send "Annie, what time is it?" — Annie responds in WhatsApp
6. Stop Titan LLM — circuit breaker activates, compaction falls back, raw preserved
7. Dashboard — jackal creature appears, events flow on message receipt
8. Health endpoint — shows last_message_at + llm_circuit_breaker state

## Environment

- **Panda** (192.168.68.57): WhatsApp agent on port 8780, phone services
- **Titan** (192.168.68.55): Gemma 4 on port 8003, Context Engine on port 8100
- **Pixel** (192.168.68.60): WhatsApp + Tasker, ADB-connected to Panda
