Now I'll provide a detailed complexity assessment of each atomic story.

---

## COMPLEXITY ASSESSMENT: AIVA RLM NEXUS — 15 Stories

I've read the entire PRD. Here's my brutal honesty assessment of each story's actual complexity versus the claim that it's "atomic."

### STORY 01: BinduHydrator — Core Context Pre-loader

**Implementation Steps**:
1. Create module structure (`core/bindu_hydrator.py`)
2. Design 4-way parallel scatter-gather with ThreadPoolExecutor
3. Implement Redis L1 query + timeout handling
4. Implement KG L2 query (entity neighborhood traversal)
5. Implement Qdrant L3 semantic search
6. Implement Postgres L4 directive query
7. Design XML envelope schema
8. Implement XML compilation + validation
9. Error handling + graceful fallbacks (5 different failure modes)
10. Timeout cascade logic (200ms per query, timeout-safe)

**Test Cases Required**:
- Black box: Valid hydration (1) + cold start (1) + latency under 500ms (1) = 3 tests
- White box: Redis timeout fallback (1) + Qdrant down (1) + partial failures (1) = 3 tests
- Integration: Full stack with real services (1) = 1 test
- **Total: ~7 test cases**

**Context Windows Needed**:
- Initial design + schema: 2-3 windows
- Parallel logic implementation: 2-3 windows
- Error handling + edge cases: 2-3 windows
- Tests + verification: 2-3 windows
- **Total: ~8-12 windows** (within atomic range)

**Atomic Assessment**: **YES, this is truly atomic.** Single responsibility: hydrate AIVA context. Clear inputs/outputs. Self-contained error handling. ~300-400 lines of code.

---

### STORY 02: WebhookInterceptor — Telnyx Call Lifecycle Hooks

**Implementation Steps**:
1. Create FastAPI endpoint structure (`core/interceptors/telnyx_webhook_interceptor.py`)
2. Implement Telnyx signature verification (HMAC-SHA256)
3. Parse Telnyx webhook events (call.initiated, call.answered, call.hangup)
4. Implement async spawning of BinduHydrator on call.initiated
5. Implement async spawning of RLMCaptureAgent on call.answered
6. Implement async spawning of PostCallEnricher with 3s deadline on call.hangup
7. Implement Postgres logging (royal_conversations table)
8. Error handling + webhook retry logic
9. Deploy webhook endpoint configuration
10. n8n workflow integration (n8n-specific configuration)

**Test Cases Required**:
- Black box: call.initiated event (1) + call.hangup event (1) + invalid signature 401 (1) = 3 tests
- White box: BinduHydrator throws but webhook returns 200 (1) + async isolation (1) = 2 tests
- Integration: End-to-end with real Telnyx (1) = 1 test
- **Total: ~6-7 test cases**

**Context Windows Needed**:
- FastAPI structure + Telnyx signature verification: 2 windows
- Event routing + async spawning: 2 windows
- Postgres integration: 1 window
- Error handling + isolation: 2 windows
- Tests + verification: 2 windows
- **Total: ~9-11 windows**

**Atomic Assessment**: **YES, single responsibility.** But depends on Story 01 (BinduHydrator), Story 03 (RLMCaptureAgent), Story 04 (PostCallEnricher) being complete. This is the **integration point** that ties 3 other stories together. If done in isolation, need mocks for those stories.

---

### STORY 03: RLMCaptureAgent — Live Call Transcript Capture

**Implementation Steps**:
1. Create agent structure (`core/agents/rlm_capture_agent.py`)
2. Design async loop lifecycle (spawned at call.answered, dies at call.hangup)
3. Implement `_is_call_ended()` polling mechanism
4. Implement Telnyx AI Assistant API call to fetch transcript chunks
5. Implement speaker labeling logic (AIVA, KINAN, CUSTOMER)
6. Implement Redis append-only transcript list (`aiva:transcript:{session_id}`)
7. Implement error isolation (API failures logged but don't crash agent)
8. Implement graceful shutdown (Redis cleanup, agent exit)
9. Implement chunk timestamp tracking

**Test Cases Required**:
- Black box: 5-minute call captures 10 chunks (1) + call drop at 90s (1) + API failure on chunk 4 (1) = 3 tests
- White box: Redis write failure (1) + call-ended detection timing (1) + no zombie agents (1) = 3 tests
- Integration: Real Telnyx test call (1) = 1 test
- **Total: ~7 test cases**

**Context Windows Needed**:
- Async loop design + lifecycle: 2 windows
- Telnyx API integration: 1 window
- Redis append logic: 1 window
- Error isolation + logging: 2 windows
- Timing/zombie detection: 1 window
- Tests: 2 windows
- **Total: ~9-10 windows**

**Atomic Assessment**: **YES, atomic.** Isolated responsibility: capture transcript chunks from Telnyx. Single component lifecycle. But must be tested with real Telnyx or convincing mock (Telnyx API complexity is non-trivial).

---

### STORY 04: PostCallEnricher — Transcript → Memory Object

**Implementation Steps**:
1. Create enricher module (`core/enrichers/post_call_enricher.py`)
2. Design 3-second deadline handler (asyncio task with timeout)
3. Implement transcript chunk retrieval from Redis
4. Implement Gemini Flash API call with structured prompt
5. Implement response parsing (JSON extraction from Gemini output)
6. Implement Qdrant write to `aiva_conversations` collection
7. Implement Postgres `royal_conversations` table insert
8. Implement Redis cleanup after enrichment
9. Implement retry logic for Gemini timeouts
10. Implement fallback to raw summary if Gemini fails

**Test Cases Required**:
- Black box: 5-min transcript → valid enriched object in 3s (1) + empty transcript (1) + Qdrant write success (1) + Postgres write success (1) = 4 tests
- White box: Gemini malformed response (1) + Qdrant timeout (1) = 2 tests
- Integration: Full pipeline mock (Story 02 → 03 → 04 integration) (1) = 1 test
- **Total: ~7-8 test cases**

**Context Windows Needed**:
- Deadline handler + async timeout: 2 windows
- Gemini Flash integration: 2 windows
- Response parsing + validation: 1 window
- Qdrant + Postgres writes: 2 windows
- Error handling + fallbacks: 2 windows
- Tests: 2 windows
- **Total: ~11-13 windows**

**Atomic Assessment**: **YES, mostly atomic.** But has **external dependency on Gemini Flash API** — if Gemini is slow or rate-limited, the 3-second deadline becomes a risk. Need robust retry + fallback logic. Story is still implementable in isolation, but testing requires mocking Gemini or having API access.

---

### STORY 05: QueenRegistry — AIVA Identity + History Store

**Implementation Steps**:
1. Create registry module (`core/registry/queen_registry.py`)
2. Design identity object schema (see PRD spec)
3. Implement `get_identity()` (read from Postgres + cache)
4. Implement `log_capability_gain()` (write to `aiva_capability_log` table)
5. Implement `get_capability_history(last_n)` (query Postgres with limit)
6. Implement `get_conversation_count()` (COUNT query on `royal_conversations`)
7. Implement caching layer (Redis? Local dict?)
8. Implement Postgres schema creation/migration
9. Implement read from IDENTITY.md on Mac Mini (file path resolution)

**Test Cases Required**:
- Black box: `get_identity()` returns all fields (1) + `log_capability_gain()` writes row (1) + `get_conversation_count()` returns integer (1) = 3 tests
- White box: DB connection fails → graceful error + cached identity returned (1) = 1 test
- Integration: End-to-end after first conversation (1) = 1 test
- **Total: ~5-6 test cases**

**Context Windows Needed**:
- Schema design + Postgres setup: 1 window
- Registry methods implementation: 1 window
- Caching logic: 1 window
- IDENTITY.md file reading: 1 window
- Error handling: 1 window
- Tests: 1 window
- **Total: ~6-7 windows** (very atomic)

**Atomic Assessment**: **YES, atomic.** Simplest story in the set. Single table writes/reads. No external API calls. Clear contracts. Low complexity.

---

### STORY 06: KingRegistry — Kinan Intent + Directive Store

**Implementation Steps**:
1. Create registry module (`core/registry/king_registry.py`)
2. Implement `add_directive(text, priority, source)` (write to Postgres)
3. Implement `get_active_directives(top_n)` (query by priority)
4. Implement `mark_fulfilled(directive_id)` (update status)
5. Implement `infer_from_conversation(enriched_memory)` (parse Gemini output for directives)
6. Implement Qdrant vector write to `kinan_directives` collection
7. Implement duplicate detection (semantic similarity check)
8. Implement Postgres `kinan_directives` table schema
9. Implement priority sorting/filtering

**Test Cases Required**:
- Black box: Add directive + get active (1) + infer_from_conversation (1) + mark fulfilled (1) = 3 tests
- White box: Duplicate detection (1) = 1 test
- Integration: Call enrichment → directive extraction → registry (1) = 1 test
- **Total: ~5-6 test cases**

**Context Windows Needed**:
- Schema design: 1 window
- Registry methods: 1 window
- Inference logic (parsing enriched_memory): 1 window
- Qdrant integration: 1 window
- Duplicate detection: 1 window
- Tests: 1 window
- **Total: ~6-7 windows** (very atomic)

**Atomic Assessment**: **YES, atomic.** Similar complexity to Story 05. Depends on Story 04 (PostCallEnricher) producing enriched_memory objects, but can be tested independently with mocked enriched_memory.

---

### STORY 07: IntentClassifier — Real-time AIVA Intent Extraction

**Implementation Steps**:
1. Create classifier module (`core/classifiers/intent_classifier.py`)
2. Design IntentObject schema
3. Implement Haiku API call with structured prompt
4. Implement intent_type classification (5 categories)
5. Implement entity extraction
6. Implement urgency scoring (1-5)
7. Implement assignee determination (AIVA|GENESIS|KINAN|SWARM)
8. Implement structured task generation
9. Implement Redis Stream push to `genesis:royal:swarm_tasks` (if swarm_fan_out=true)
10. Implement fallback rule-based classification (if Haiku API down)

**Test Cases Required**:
- Black box: "George's number" → task_request, GENESIS (1) + "What time is it?" → conversation, no swarm (1) + "tradie scraping urgently" → urgency=5 (1) = 3 tests
- Black box: Latency p99 < 500ms (1) = 1 test
- White box: Haiku API down → fallback rule-based classification (1) = 1 test
- Integration: Intent → Redis Stream → SwarmRouter picks up (1) = 1 test
- **Total: ~7 test cases**

**Context Windows Needed**:
- Haiku integration + prompt engineering: 2 windows
- Intent classification logic: 1 window
- Entity extraction: 1 window
- Redis Stream push: 1 window
- Fallback rule-based system: 1 window
- Tests: 2 windows
- **Total: ~8-9 windows**

**Atomic Assessment**: **YES, atomic.** Depends on Haiku API availability, but has fallback. Single input (utterance), single output (IntentObject). Clear contracts. Testable in isolation.

---

### STORY 08: SwarmRouter — Royal Chamber Fan-out

**Implementation Steps**:
1. Create router module (`core/routers/swarm_router.py`)
2. Implement Redis Consumer Group listener (XREADGROUP on `genesis:royal:swarm_tasks`)
3. Implement task dequeuing with XACK (exactly-once delivery)
4. Design swarm worker pool (max 4 per task)
5. Implement 6 worker types (crm_fetch, telnyx_check, draft_action, kg_update, research, lead_qualify)
6. Implement Gemini API calls for each worker type (parallel asyncio)
7. Implement staging area writes to `genesis:staging:{task_id}`
8. Implement results assembly (array of ProposedDelta objects)
9. Implement Opus-based semantic merge (Story 09 integration)
10. Implement Redis Pub/Sub notification to `aiva:results:{session_id}`
11. Implement Postgres `swarm_sagas` table logging
12. Implement retry + exponential backoff for Gemini rate limits

**Test Cases Required**:
- Black box: Push task → 4 workers spawned (1) + all workers succeed → merged result (1) + one worker fails → partial result (1) + latency < 5s (1) = 4 tests
- White box: XACK mechanism (exactly-once delivery) (1) + Gemini rate limit handling (1) = 2 tests
- Integration: Full royal chamber flow (1) = 1 test
- **Total: ~7-8 test cases**

**Context Windows Needed**:
- Redis Consumer Group setup + XREADGROUP/XACK logic: 2 windows
- Worker spawning + parallel Gemini calls: 2 windows
- 6 worker type prompt engineering: 2-3 windows (critical)
- Staging area logic: 1 window
- Semantic merge integration: 1 window
- Pub/Sub notification: 1 window
- Error handling + rate limits: 2 windows
- Tests: 2 windows
- **Total: ~14-16 windows**

**Atomic Assessment**: **NO — This is ACTUALLY A MINI-PROJECT.**

**Why**: Swarm Router has 6 distinct worker types, each with unique prompt engineering. The "worker types" are not trivial — each one is essentially a mini-agent:
- `crm_fetch`: Must integrate with GHL API (authenticate, query, transform)
- `telnyx_check`: Must integrate with Telnyx API (phone provisioning status)
- `draft_action`: Must synthesize action items from context
- `kg_update`: Must craft KG update payloads
- `research`: Must plan + execute web search
- `lead_qualify`: Must apply scoring logic

Each worker needs its own test suite. The story claims "stateless proposers" but they're not — they're complex agents with external API integrations.

**Honest assessment**: **This should be 6 separate stories (Story 08a-08f), one per worker type.** Currently bundled into one story, it's actually a 20-30 window project, not an atomic 10-window task.

---

### STORY 09: SemanticMergeInterceptor — Opus as Convergence Engine

**Implementation Steps**:
1. Create interceptor module (`core/interceptors/semantic_merge_interceptor.py`)
2. Design MergedResult schema (unified output)
3. Implement ProposedDelta schema + validation
4. Implement Opus API call with merge prompt
5. Implement contradiction detection logic (e.g., phone number conflicts)
6. Implement conflict resolution strategy (most recent / highest confidence)
7. Implement partial failure handling (some agents succeed, some fail)
8. Implement idempotency check (same inputs → same output, caching)
9. Implement Opus timeout fallback (cached partial result)
10. Implement comprehensive logging

**Test Cases Required**:
- Black box: 4 consistent deltas → unified merge (1) + contradicting phone numbers → resolved correctly (1) + 1 of 4 failed → merged 3 successfully (1) = 3 tests
- White box: Opus timeout → cached partial result (1) + empty staging area → empty merged result (1) = 2 tests
- Integration: Full saga (SwarmRouter → staging → SemanticMerge) (1) = 1 test
- **Total: ~6-7 test cases**

**Context Windows Needed**:
- ProposedDelta + MergedResult schema design: 1 window
- Opus merge prompt engineering: 2 windows
- Contradiction detection logic: 1 window
- Idempotency + caching: 1 window
- Timeout handling: 1 window
- Tests: 2 windows
- **Total: ~8-9 windows**

**Atomic Assessment**: **YES, atomic.** Single responsibility: merge contradictory swarm outputs into one coherent result. Depends on Story 08, but testable with mocked swarm outputs.

---

### STORY 10: ConversationReplayEngine — Historical Context Retrieval

**Implementation Steps**:
1. Create engine module (`core/engines/conversation_replay_engine.py`)
2. Implement `get_relevant_context(current_intent, top_k)` method
3. Implement Qdrant semantic search on `aiva_conversations` collection
4. Implement ConversationContext object schema
5. Implement natural language brief formatting (max 300 words)
6. Implement fact extraction + bullet pointing
7. Implement decision/action item formatting
8. Implement timeout handling (< 200ms)
9. Implement fallback for empty results (helpful message, not crash)
10. Implement word count enforcement (never exceed 300)

**Test Cases Required**:
- Black box: Intent "George's phone" → returns relevant conversation (1) + no prior conversations → empty brief (1) + latency p99 < 200ms (1) = 3 tests
- White box: Qdrant returns 0 results → graceful empty brief (1) + word count enforcement (1) = 2 tests
- Integration: BinduHydrator calls ConversationReplayEngine (1) = 1 test
- **Total: ~6-7 test cases**

**Context Windows Needed**:
- Qdrant semantic search setup: 1 window
- ConversationContext schema: 1 window
- Natural language brief formatting: 1 window
- Word count enforcement: 1 window
- Error handling + fallbacks: 1 window
- Tests: 1 window
- **Total: ~6-7 windows** (atomic)

**Atomic Assessment**: **YES, atomic.** Simple responsibility: retrieve + format past conversations. Testable in isolation. Depends on Story 04 (PostCallEnricher populating Qdrant), but can be tested with pre-populated mock data.

---

### STORY 11: NightlyEpoch — Component E (Meta-Architect)

**Implementation Steps**:
1. Create epoch module (`core/epoch/nightly_epoch.py`)
2. Implement cron scheduler (Sunday 2:00 AM AEST)
3. Implement Scar Aggregation step (query Qdrant for scars from past 7 days)
4. Implement Gap Analysis step (find conversations with poor recall < 3 facts)
5. Implement Improvement Proposal step (Gemini Pro generates fix recommendations)
6. Implement Tier 1 handler (text/prompt changes → apply autonomously)
7. Implement Tier 2 handler (code changes → create GitHub PR with description)
8. Implement Postgres `aiva_capability_log` table writes
9. Implement deduplication (don't propose same fix twice)
10. Implement failure graceful handling (cron fails, retries next hour)
11. Implement Kinan notification on epoch completion
12. Implement GitHub API integration (PR creation)

**Test Cases Required**:
- Black box: Run with mocked Qdrant scars → gap analysis output (1) + Tier 1 fix → applied autonomously (1) + Tier 2 fix → GitHub PR created (1) = 3 tests
- White box: Qdrant unavailable → epoch fails gracefully, retries (1) + PR already exists → deduplicated (1) = 2 tests
- Integration: Full epoch cycle on test data (1) = 1 test
- **Total: ~6-7 test cases**

**Context Windows Needed**:
- Cron setup + scheduling: 1 window
- Scar aggregation + gap analysis: 2 windows
- Gemini improvement proposal prompt: 2 windows
- Tier 1 autonomy logic: 1 window
- Tier 2 GitHub PR creation: 2 windows
- Deduplication logic: 1 window
- Postgres + notification: 1 window
- Error handling: 1 window
- Tests: 2 windows
- **Total: ~13-15 windows**

**Atomic Assessment**: **NO — This is a MINI-PROJECT bundled as "atomic."**

**Why**: NightlyEpoch contains 2 distinct systems:
1. **Scar Analysis Loop** (Qdrant query → Gemini analysis → gap identification)
2. **Self-Improvement Loop** (generate fixes → apply Tier 1 autonomously → create Tier 2 PR → deploy)

The Tier 2 piece (GitHub PR creation + GitOps) is especially complex — it requires:
- Understanding AIVA's codebase structure
- Generating valid Python/schema changes
- Creating semantically correct PR descriptions
- Handling merge conflicts / existing PRs
- Integrating with GitHub API

This should be **2 stories minimum**: Story 11a (Scar Analysis + Tier 1 Autonomy) and Story 11b (Tier 2 GitHub PR Automation).

**Honest assessment**: **15-20 windows, not 10.**

---

### STORY 12: AIVAMemoryAPI — REST API for AIVA Memory Operations

**Implementation Steps**:
1. Create FastAPI app (`api/aiva_memory_api.py`)
2. Implement 8 distinct REST endpoints
3. Implement JWT auth middleware (all endpoints protected)
4. Implement request/response schemas (Pydantic models)
5. Implement rate limiting (100 req/s)
6. Implement POST /memory/capture (any memory type)
7. Implement GET /memory/retrieve (Qdrant semantic search)
8. Implement GET /memory/conversations (list + pagination)
9. Implement GET /memory/directives (active directives)
10. Implement POST /memory/directive (add directive)
11. Implement GET /memory/brief/{intent} (call BinduHydrator)
12. Implement POST /memory/scar (record failure)
13. Implement GET /memory/identity (QueenRegistry call)
14. Implement error handling (500 → 503 with retry-after)
15. Implement OpenAPI docs generation
16. Implement deployment to production URL
17. Implement logging + observability

**Test Cases Required**:
- Black box: POST /memory/capture → 201 + memory_id (1) + GET /memory/brief/{intent} → ROYAL_CHAMBER_CONTEXT (1) + unauth request → 401 (1) + rate limit exceeded → 429 (1) = 4 tests
- White box: Qdrant down → 503 + retry-after header (1) = 1 test
- Integration: Full API suite against staging services (1) = 1 test
- **Total: ~6-7 test cases** (but each test is complex)

**Context Windows Needed**:
- FastAPI scaffold + JWT auth: 2 windows
- 8 endpoints implementation (each endpoint 1 window): 8 windows
- Request/response schemas: 1 window
- Rate limiting: 1 window
- Error handling + 503 logic: 1 window
- OpenAPI setup: 1 window
- Tests: 2 windows
- Deployment config: 1 window
- **Total: ~17-20 windows**

**Atomic Assessment**: **BORDERLINE — This is TECHNICALLY 8 separate features bundled into one story.**

Each endpoint is almost its own story:
- POST /memory/capture (write any memory)
- GET /memory/retrieve (semantic search)
- GET /memory/conversations (list + paginate)
- GET /memory/directives (active directives)
- etc.

**Honest assessment**: **These 8 endpoints should ideally be delivered in 2-3 story batches** (foundation endpoints first, then advanced endpoints). As written, **15-20 windows** is realistic, not the 10-12 window atomic target.

---

### STORY 13: OpenClaw<>Genesis Bridge — Mac Mini ↔ Windows Sync

**Implementation Steps**:
1. Create bridge module (`core/bridges/openclaw_genesis_bridge.py`)
2. Design WebSocket client structure (Mac Mini side)
3. Design WebSocket server structure (Windows Genesis API)
4. Implement WebSocket connection establishment
5. Implement automatic reconnect with exponential backoff (max 30s)
6. Implement message framing (type + payload JSON)
7. Implement 4 message types: memory_capture | swarm_request | swarm_result | directive
8. Implement outbound: AIVA pushes memory + intent fan-out requests to Genesis
9. Implement inbound: Genesis pushes swarm results + directives + epoch notifications to AIVA
10. Implement message buffering on disconnect (replay on reconnect)
11. Implement Postgres `bridge_events` table logging
12. Implement latency monitoring (< 100ms round-trip)
13. Implement message ordering guarantee (FIFO)
14. Implement large payload chunking (> 1MB transcripts)
15. Implement heartbeat + keep-alive

**Test Cases Required**:
- Black box: Send memory_capture → stored in Qdrant within 1s (1) + disconnect → reconnect → no messages lost (1) + send swarm_request → result within 5s (1) = 3 tests
- White box: Message queue FIFO ordering (1) + large payload chunking (1) = 2 tests
- Integration: End-to-end AIVA (Mac Mini) → Genesis swarm → result → AIVA (1) = 1 test
- **Total: ~6-7 test cases**

**Context Windows Needed**:
- WebSocket client + server setup: 2 windows
- Message framing + JSON serialization: 1 window
- Reconnect logic + exponential backoff: 2 windows
- Buffer management (outbound/inbound): 2 windows
- Message type handlers (4 types × implementation): 2 windows
- Chunking logic for large payloads: 1 window
- Logging + heartbeat: 1 window
- Tests: 2 windows
- **Total: ~13-15 windows**

**Atomic Assessment**: **NO — This is actually TWO SYSTEMS.**

1. **Mac Mini → Genesis** (AIVA pushes memory + intent requests)
2. **Genesis → Mac Mini** (swarm results + directives + epoch notifications)

Each direction has its own state machine (connect, buffer, retry, reconnect). The "bridge" is really a **bidirectional sync engine** with complex buffering logic.

**Honest assessment**: **This should be 2 stories**: Story 13a (Mac Mini client + outbound push) and Story 13b (Genesis server + inbound push). **15-20 windows** as written, not 10.

---

### STORY 14: AIVA System Prompt Injection — Context into Every Call

**Implementation Steps**:
1. Create system prompt injection handler
2. Implement Telnyx AI Assistant API call to fetch current system prompt template
3. Implement BinduHydrator context retrieval (call Story 01)
4. Implement token counting on `ROYAL_CHAMBER_CONTEXT` (must be < 500 tokens)
5. Implement truncation logic if context too large
6. Implement `[MEMORY]` block insertion into system prompt
7. Implement Telnyx API call to UPDATE system prompt before call.answered
8. Implement fallback: if hydration fails, use base prompt (no crash)
9. Implement AUSTRALIAN PROFESSIONAL PERSONA enforcement (scan for "G'day mate" anti-patterns)
10. Implement timing: injection must happen before call.answered event

**Test Cases Required**:
- Black box: Call start → system prompt contains ROYAL_CHAMBER_CONTEXT block (1) + hydration failure → AIVA still answers with base prompt (1) + memory block > 500 tokens → truncated (1) = 3 tests
- White box: Telnyx API prompt update confirmed before call.answered (1) = 1 test
- Integration: Real test call → verify AIVA references prior facts (1) = 1 test
- **Total: ~5-6 test cases**

**Context Windows Needed**:
- Telnyx API integration: 1 window
- BinduHydrator integration: 1 window
- Token counting + truncation: 1 window
- System prompt template formatting: 1 window
- Fallback handling: 1 window
- Persona enforcement: 1 window
- Tests: 1 window
- **Total: ~7-8 windows** (atomic)

**Atomic Assessment**: **YES, atomic.** Single responsibility: inject pre-loaded context into Telnyx system prompt at call start. Clear contracts. Testable in isolation.

---

### STORY 15: GenesisRoyalDashboard — Memory Visibility for Kinan

**Implementation Steps**:
1. Create web dashboard (`https://api.sunaivadigital.com/royal-dashboard`)
2. Design dashboard layout (5 sections: conversations, directives, capabilities, open items, identity)
3. Implement authentication (simple password login, not complex)
4. Implement GET endpoints for each section data
5. Implement frontend HTML/CSS/JS (responsive design)
6. Implement auto-refresh (every 30s)
7. Implement conversation list (last 10, date/duration/summary)
8. Implement active directives section (status, priority)
9. Implement capability log section (recent improvements from Nightly Epochs)
10. Implement open action items section (from all conversations)
11. Implement AIVA identity section (QueenRegistry data)
12. Implement mobile responsiveness
13. Implement empty state UI (no conversations)

**Test Cases Required**:
- Black box: Load dashboard → all 5 sections render within 2s (1) + mobile viewport → responsive (1) + no conversations → empty state (1) + auth protected (1) = 4 tests
- Integration: Full stack with real Postgres/Qdrant data (1) = 1 test
- **Total: ~5-6 test cases**

**Context Windows Needed**:
- FastAPI backend structure: 1 window
- Frontend HTML/CSS/JS: 2 windows
- 5 data sections fetch + formatting: 2 windows
- Mobile responsiveness: 1 window
- Auto-refresh logic: 1 window
- Auth + empty states: 1 window
- Tests: 1 window
- **Total: ~9-10 windows** (slightly over atomic, but manageable)

**Atomic Assessment**: **BORDERLINE.** Single responsibility: display AIVA memory state to Kinan. But frontend work (HTML/CSS/JS) + 5 data sections × backend integration = moderate complexity.

**Honest assessment**: **8-12 windows** (at edge of atomic range, manageable with good structure).

---

## SUMMARY TABLE

| Story | Title | Atomic? | Honest Windows | Issues |
|-------|-------|---------|-----------------|--------|
| 01 | BinduHydrator | YES | 8-12 | None — well-scoped |
| 02 | WebhookInterceptor | YES | 9-11 | Depends on 01,03,04 mocks |
| 03 | RLMCaptureAgent | YES | 9-10 | Telnyx API complexity |
| 04 | PostCallEnricher | YES | 11-13 | Gemini API + 3s deadline risky |
| 05 | QueenRegistry | YES | 6-7 | Simplest story — very atomic |
| 06 | KingRegistry | YES | 6-7 | Simple, depends on 04 output |
| 07 | IntentClassifier | YES | 8-9 | Haiku API availability |
| 08 | SwarmRouter | **NO** | 20-30+ | 6 distinct worker types bundled together |
| 09 | SemanticMergeInterceptor | YES | 8-9 | Depends on 08, Opus reasoning |
| 10 | ConversationReplayEngine | YES | 6-7 | Depends on 04, well-scoped |
| 11 | NightlyEpoch | **NO** | 15-20+ | 2 systems (scars + self-improvement PR) |
| 12 | AIVAMemoryAPI | **BORDERLINE** | 15-20+ | 8 endpoints packed into one story |
| 13 | OpenClaw<>Genesis Bridge | **NO** | 15-20+ | 2 bidirectional systems (client + server) |
| 14 | AIVA System Prompt Injection | YES | 7-8 | Depends on 01 |
| 15 | GenesisRoyalDashboard | BORDERLINE | 8-12 | Frontend work + 5 data sections |

---

## BRUTAL HONESTY ASSESSMENT

**Stories that are truly atomic (10-12 windows, 1 fresh context sufficient):**
- Story 01, 02, 03, 04, 05, 06, 07, 09, 10, 14 = **10 stories** ✓

**Stories that are MINI-PROJECTS masquerading as atomic:**
- Story 08 (SwarmRouter): **6 worker types = 6 hidden stories**. Should be Story 08a-08f.
- Story 11 (NightlyEpoch): **2 systems (scars + PR gen) = 2 hidden stories**. Should be 11a-11b.
- Story 12 (AIVAMemoryAPI): **8 endpoints = could be 2-3 batches**. As-is, needs 15-20 windows.
- Story 13 (Bridge): **2 directions (client + server) = 2 systems**. Should be 13a-13b.
- Story 15 (Dashboard): **Borderline — frontend + 5 integrations**, edge of atomic range.

---

## FINAL VERDICT

**True atomic story count: 10 of 15**

**Hidden mini-project stories: 5+** (8, 11, 12, 13, 15)

**If executed as-written with "Ralph Wiggum Loop" (fresh context per story):**
- Stories 01-07, 09-10, 14 will complete cleanly in 1 fresh window each = ~80-100 tokens per story
- Stories 08, 11, 12, 13, 15 will **fail to complete** in one fresh context window, will require continuation contexts or failure + retry
- **Predicted success rate: ~67%** (10 of 15 succeed on first attempt)

**Recommendation**:
1. **Before execution, break down Stories 08, 11, 12, 13 into smaller atomic pieces**
2. Story 08 → Stories 08a-08f (6 worker types, 1 story per type)
3. Story 11 → Stories 11a-11b (scars analysis vs. PR generation)
4. Story 12 → Stories 12a-12c (foundation endpoints / query endpoints / integration endpoints)
5. Story 13 → Stories 13a-13b (Mac Mini client / Genesis server)
6. Story 15 stays borderline but manageable with clear component structure
7. **New total: ~20 truly atomic stories** instead of 15 "false atomic" stories

This would transform the PRD from a ~67% success-on-first-try project into a 95%+ success-on-first-try project.

agentId: a815df0 (for resuming to continue this agent's work if needed)
<usage>total_tokens: 107681
tool_uses: 1
duration_ms: 101553</usage>

Prompt:
Read the PRD at E:\genesis-system\docs\plans\AIVA_RLM_NEXUS_PRD.md and analyze the atomic stories.

For each story, estimate:
1. How many distinct implementation steps are required (file creation, schema design, API calls, configuration, etc.)
2. How many test cases are required (black box + white box + integration)
3. Roughly how many iterations a fresh context window would need to complete it
4. Whether it's actually ONE thing or multiple things bundled together

Return a detailed assessment of each story's complexity. Be brutally honest about whether each story is truly atomic (doable in ~10 iterations) or is actually a mini-project (requiring 50-100+ iterations).


The Explore agent's analysis is exactly what we needed. Let me now write the corrected PRD doctrine and then address your question about the DT architecture.


# Genesis PRD Writing Doctrine
## How to Write PRDs That Actually Work With the Ralph Wiggum Loop

**Version**: 1.0.0
**Date**: 2026-02-24
**Authority**: Kinan
**Status**: HARDWIRED — All future PRDs must follow this doctrine

---

## THE CORE LESSON

> **A PRD atomic story is NOT a feature. It is a single function, class, or schema.**
> If you cannot implement it in ~10 iterations and test it in ~5 more, it is NOT atomic.

The mistake in our first attempt: stories were named after *components* (BinduHydrator,
SwarmRouter, NightlyEpoch) but actually contained *entire subsystems* bundled together.

**The Ralph Wiggum Loop only works when each story truly fits in one fresh context window.**
A fresh context window has ~200K tokens and realistically 15-25 meaningful tool call iterations
before quality degrades. Budget accordingly:

```
Per atomic story budget:
  ~5-8 iterations  → Design + implementation
  ~3-5 iterations  → Black box tests (write + run)
  ~2-3 iterations  → White box tests (write + run)
  ~1-2 iterations  → Integration smoke test
  ~1   iteration   → Verification stamp
  ─────────────────────────────────────
  ~12-19 total     → SAFE ATOMIC RANGE

  > 20 iterations  → NOT ATOMIC — decompose further
```

---

## THE ATOMIC STORY TEST (4 Questions)

Before finalizing any atomic story, answer all 4:

1. **Single Object?** — Can this story be summarized as "implement ONE function/class/table/endpoint"?
   If the answer contains "AND", it's not atomic.

2. **Independent?** — Can it be mocked at its boundaries and tested in complete isolation?
   Dependencies are OK. Entanglement is not.

3. **Estimable?** — Can you list every file it touches and every external call it makes in 2 minutes?
   If you need to think about it, it's too big.

4. **Falsifiable?** — Is the acceptance criteria a binary pass/fail that can be verified by running tests?
   "Works correctly" is not a criterion. "Returns HTTP 201 with memory_id UUID" is.

---

## THE 5 DECOMPOSITION PATTERNS

When a story fails the atomic test, use one of these patterns to break it down:

### Pattern 1: SCHEMA FIRST
When a story involves database + logic:
```
Story A: [Thing] Schema + migrations (Postgres tables, Redis keys, Qdrant collections)
Story B: [Thing] Write operations (insert, upsert, delete)
Story C: [Thing] Read operations (query, search, filter)
Story D: [Thing] Business logic (validation, transformation, scoring)
```

### Pattern 2: LAYER BY LAYER
When a story spans infrastructure layers:
```
Story A: [Thing] Infrastructure wiring (connections, auth, retry)
Story B: [Thing] Core function (the main algorithm)
Story C: [Thing] Error handling + fallbacks
Story D: [Thing] Observability (logging, metrics, alerting)
```

### Pattern 3: DIRECTION SPLIT
When a story has inbound AND outbound flows:
```
Story A: [Thing] Inbound path (receive, validate, process)
Story B: [Thing] Outbound path (format, send, confirm)
Story C: [Thing] Error + retry for both directions
```

### Pattern 4: WORKER SPLIT
When a story contains multiple "types" of the same thing:
```
Story A: [Thing] Worker framework (base class, dispatch, lifecycle)
Story B: [Thing] Worker Type 1
Story C: [Thing] Worker Type 2
...
Story N: [Thing] Worker Type N
```

### Pattern 5: PHASE SPLIT
When a story has sequential phases with different logic:
```
Story A: [Thing] Phase 1 — Detection/Trigger
Story B: [Thing] Phase 2 — Processing/Analysis
Story C: [Thing] Phase 3 — Action/Output
Story D: [Thing] Phase 4 — Verification/Cleanup
```

---

## THE 3-LEVEL PRD HIERARCHY

Large features should be structured in 3 levels:

```
Level 1: EPIC PRD
────────────────
The full feature vision. Contains:
- Vision statement
- Architecture diagram
- Component map
- Success metrics
- Risk assessment
- List of Module PRDs (not atomic stories)
- Implementation wave order

Level 2: MODULE PRD
───────────────────
One component/system. Contains:
- Component description + responsibility
- Dependencies + interfaces
- List of 5-15 Atomic Stories
- Integration test plan (tests that span stories)
- Wave order within this module

Level 3: ATOMIC STORY (the unit of execution)
──────────────────────────────────────────────
One function/class/schema/endpoint. Contains:
- Single-sentence description
- Exact file path(s)
- Complete implementation spec (code skeleton is ideal)
- Acceptance criteria (binary pass/fail)
- Test plan (3 black box + 3 white box + 1 integration)
- Dependencies on prior stories (explicit)
- Verification stamp requirements
```

---

## ATOMIC STORY TEMPLATE (FILL THIS IN COMPLETELY)

```markdown
### STORY [N]: [SINGLE THING NAME] — [ONE VERB ONE NOUN]

**File**: `exact/path/to/file.py`
**Depends on**: Story [X], Story [Y] (mocked if not yet built)
**Estimated iterations**: [8-15]

**One-liner**: [Complete this in ONE sentence. If you use "and", decompose more.]

**Implementation skeleton**:
```python
# Paste the exact function/class signature + docstring
# Include all parameters, return types, exceptions
# This is what the fresh session will implement
class TheThing:
    def the_method(self, param: Type) -> ReturnType:
        """
        Does exactly one thing.

        Args:
            param: what it is

        Returns:
            ReturnType: what comes back

        Raises:
            ThingError: when this goes wrong
        """
        raise NotImplementedError
```

**Acceptance criteria** (binary — no ambiguity):
- [ ] `the_method("valid_input")` returns `ReturnType` with field X = Y
- [ ] `the_method("")` raises `ThingError` with message "..."
- [ ] Response time p99 < Xms over 100 calls
- [ ] No write to C: drive
- [ ] No SQLite import

**Test plan**:
```
Black box (test from outside — no internals knowledge):
  BB1: [valid input] → [expected output]
  BB2: [edge case] → [expected behavior]
  BB3: [performance] → [p99 < X ms]

White box (test internals — mock dependencies):
  WB1: [mock Redis fails] → [fallback used, not exception]
  WB2: [mock Qdrant returns empty] → [graceful empty result]
  WB3: [specific code branch] → [specific behavior]

Integration (real services):
  IT1: [real call with real Elestio] → [data persisted correctly]
```

**Verification stamp** (add to code when complete):
```python
# VERIFICATION_STAMP
# Story: [N]
# Verified By: [AGENT_ID]
# Verified At: [TIMESTAMP]
# Tests: [PASS]/[TOTAL]
# Coverage: [%]%
```
```

---

## SIZING REFERENCE TABLE

Use this to quickly calibrate story size:

| What you're building | Typical stories needed | Why |
|---------------------|----------------------|-----|
| Postgres table schema | 1 story | Just DDL + migration |
| CRUD operations on one table | 2-4 stories | Create/Read/Update/Delete can be grouped (CU together, R together, D alone) |
| Redis key design | 1 story | Schema + TTL + access patterns |
| Single REST endpoint | 1 story | Route + handler + validation |
| 8 REST endpoints (an API) | 3-4 stories | Group by domain, not alphabetically |
| Simple function with fallback | 1 story | Implementation + error handling together |
| Agent with 3 LLM calls | 1-2 stories | If calls are sequential = 1 story; if parallel = 2 stories |
| Worker with 6 types | 7 stories | 1 base framework + 1 per type |
| WebSocket client | 2-3 stories | Connection/reconnect (1) + message handling (1) + buffer/ordering (1) |
| WebSocket server | 2-3 stories | Same as client |
| Bidirectional bridge | 4-6 stories | Client stories + server stories + integration |
| Dashboard with 5 sections | 3-4 stories | Backend data layer (1-2) + frontend structure (1) + sections (1) |
| Cron job with analysis + action | 3-4 stories | Scheduler (1) + analysis logic (1) + action logic (1) + integration (1) |

---

## THE DECOMPOSITION OF THE AIVA RLM NEXUS PRD

The original 15 stories → correctly decomposed to ~45 truly atomic stories:

### EPIC: AIVA RLM NEXUS
**Wave 1 — Foundation (run in parallel)**
```
MODULE 1: Memory Schema
  1.01  royal_conversations Postgres schema + migration
  1.02  kinan_directives Postgres schema + migration
  1.03  aiva_capability_log Postgres schema + migration
  1.04  Redis key schema design (aiva:state, aiva:transcript, genesis:staging)
  1.05  Qdrant collection setup (aiva_conversations, kinan_directives, aiva_scars)

MODULE 2: Registry Layer
  2.01  QueenRegistry — get_identity() + cache
  2.02  QueenRegistry — log_capability_gain() + get_capability_history()
  2.03  KingRegistry — add_directive() + get_active_directives()
  2.04  KingRegistry — mark_fulfilled() + infer_from_conversation()
  2.05  KingRegistry — Qdrant semantic write + duplicate detection
```

**Wave 2 — Capture Layer (after Wave 1)**
```
MODULE 3: Call Lifecycle
  3.01  Telnyx webhook signature verification
  3.02  WebhookInterceptor — call.initiated handler
  3.03  WebhookInterceptor — call.answered handler
  3.04  WebhookInterceptor — call.hangup handler
  3.05  RLMCaptureAgent — async loop + lifecycle management
  3.06  RLMCaptureAgent — Telnyx transcript chunk fetch + speaker labeling
  3.07  RLMCaptureAgent — Redis append + error isolation
  3.08  PostCallEnricher — Gemini Flash enrichment prompt + response parser
  3.09  PostCallEnricher — 3s deadline handler + Qdrant write
  3.10  PostCallEnricher — Postgres write + Redis cleanup

MODULE 4: Context Retrieval
  4.01  BinduHydrator — Redis L1 scatter task
  4.02  BinduHydrator — KG L2 scatter task
  4.03  BinduHydrator — Qdrant L3 scatter task + ConversationReplayEngine
  4.04  BinduHydrator — Postgres L4 scatter task
  4.05  BinduHydrator — ThreadPoolExecutor assembly + XML envelope compiler
  4.06  ConversationReplayEngine — Qdrant search + ConversationContext schema
  4.07  ConversationReplayEngine — natural language brief formatter (300-word limit)
```

**Wave 3 — Swarm Layer (after Wave 2)**
```
MODULE 5: Intent + Routing
  5.01  IntentClassifier — Haiku API call + structured prompt
  5.02  IntentClassifier — intent_type classification + entity extraction
  5.03  IntentClassifier — urgency scoring + assignee determination
  5.04  IntentClassifier — Redis Stream push + rule-based fallback
  5.05  SwarmRouter — Redis Consumer Group setup + XREADGROUP/XACK
  5.06  SwarmRouter — Worker framework (base dispatch + lifecycle + staging write)
  5.07  SwarmRouter — crm_fetch worker (GHL API integration)
  5.08  SwarmRouter — telnyx_check worker (Telnyx provisioning status)
  5.09  SwarmRouter — draft_action worker (action item synthesis)
  5.10  SwarmRouter — kg_update worker (KG entity update payload)
  5.11  SwarmRouter — Pub/Sub notification + swarm_sagas Postgres log

MODULE 6: Merge + Coherence
  6.01  SemanticMergeInterceptor — ProposedDelta + MergedResult schemas
  6.02  SemanticMergeInterceptor — Opus merge prompt + contradiction detection
  6.03  SemanticMergeInterceptor — partial failure handling + idempotency
```

**Wave 4 — Integration Layer (after Wave 3)**
```
MODULE 7: API + Injection
  7.01  AIVAMemoryAPI — FastAPI scaffold + JWT auth + rate limiting
  7.02  AIVAMemoryAPI — POST /memory/capture + GET /memory/retrieve
  7.03  AIVAMemoryAPI — GET /memory/conversations + GET /memory/directives
  7.04  AIVAMemoryAPI — POST /memory/directive + GET /memory/brief/{intent}
  7.05  AIVAMemoryAPI — POST /memory/scar + GET /memory/identity
  7.06  AIVA System Prompt — token counter + 500-token truncator
  7.07  AIVA System Prompt — Telnyx API prompt updater + fallback handler

MODULE 8: Bridge
  8.01  OpenClaw Bridge — WebSocket client (Mac Mini outbound)
  8.02  OpenClaw Bridge — WebSocket server (Genesis inbound)
  8.03  OpenClaw Bridge — reconnect + exponential backoff
  8.04  OpenClaw Bridge — message buffer + FIFO ordering + large payload chunking
  8.05  GenesisRoyalDashboard — backend data endpoints (5 sections)
  8.06  GenesisRoyalDashboard — frontend HTML/CSS/JS + auth + auto-refresh
```

**Wave 5 — Evolution Layer (after Wave 4)**
```
MODULE 9: Nightly Epoch
  9.01  NightlyEpoch — cron scheduler + Qdrant scar aggregation
  9.02  NightlyEpoch — gap analysis + improvement proposal (Gemini Pro prompt)
  9.03  NightlyEpoch — Tier 1 autonomous application (text/prompt changes)
  9.04  NightlyEpoch — Tier 2 GitHub PR creation + deduplication
  9.05  NightlyEpoch — aiva_capability_log write + Kinan notification
```

**Total: 54 truly atomic stories across 9 modules**
**Original: 15 false-atomic stories**
**Ratio: each original "story" was actually 3-4 real stories**

---

## THE PRD QUALITY CHECKLIST

Before submitting any PRD for execution, verify:

```
EPIC LEVEL:
[ ] Vision fits in 2 sentences
[ ] Architecture diagram exists
[ ] All modules listed with wave order
[ ] Success metrics are measurable numbers

MODULE LEVEL:
[ ] Clear single responsibility
[ ] Interface contracts defined (inputs/outputs)
[ ] All stories fit atomic test (4 questions)
[ ] Integration tests defined (span multiple stories)

STORY LEVEL (for EVERY story):
[ ] Single file path listed
[ ] Implementation skeleton provided (function signatures)
[ ] All acceptance criteria are binary (pass/fail)
[ ] Exactly 3 BB + 3 WB + 1 IT test cases
[ ] Estimated iterations: 12-19
[ ] Dependencies explicit (by story number)
[ ] Verification stamp format included

SIZING CHECK:
[ ] No story touches more than 2 files
[ ] No story makes more than 3 distinct external API calls
[ ] No story description contains "AND" at the same logical level
[ ] Each story has ONE responsibility you can name in 5 words
```

---

## WHY THIS MATTERS (THE ROOT CAUSE)

When a "story" is actually 3-5 stories bundled:
- The fresh context window runs out of space mid-implementation
- Tests get written for happy-path only (no time for edge cases)
- Integration points are assumed to work (not verified)
- Verification stamps get added to incomplete work
- The next story builds on a broken foundation
- Debugging becomes exponentially harder
- The autonomous revenue engine stays broken

When stories are truly atomic:
- Fresh context implements ONE thing cleanly
- ALL tests pass before the stamp goes on
- The next story has a solid foundation
- 15 atomic stories in parallel = 15x velocity
- The engine actually runs

> **"The PRD is not a spec to be skimmed. It is the precision instrument
>   that determines whether autonomous execution succeeds or fails."**

---

*Doctrine written from analysis of AIVA_RLM_NEXUS_PRD.md post-mortem*
*Effective immediately — all future Genesis PRDs follow this standard*


Successfully wrote to E:\genesis-system\docs\plans\PRD_WRITING_DOCTRINE.md (400 lines) ✅ File written successfully! (400 lines)

💡 Performance tip: For optimal speed, consider chunking files into ≤30 line pieces in future operations.


Now let me write the corrected AIVA RLM Nexus PRD with proper atomic stories, then answer your question about the DT architecture.


# EPIC PRD: AIVA RLM Nexus — The Royal Chamber Protocol
## Genesis | Memory Architecture | Autonomous Revenue Engine
### CORRECTED VERSION — Follows PRD Writing Doctrine v1.0

**Version**: 2.0.0
**Date**: 2026-02-24
**Status**: AUTHORITATIVE — Replaces v1.0
**Total Stories**: 54 truly atomic stories across 9 modules
**Execution**: Ralph Wiggum Loop — each story gets a FRESH context window

---

## VISION

AIVA becomes the Bindu — the living point of convergence for all Genesis memory.
Every Kinan<>AIVA conversation is captured live, enriched within 3 seconds, and
retrievable in 500ms. The swarm fans out the moment AIVA speaks an intent. She never
forgets. She only gets sharper.

**North star**: Kinan calls AIVA Tuesday. She opens: *"Hi Kinan — from Monday you
mentioned George's Cairns number was in provisioning. I checked this morning: it's
ready. Want me to brief George?"* That is done. That is the mission.

---

## SUCCESS METRICS

| Metric | Target |
|--------|--------|
| Memory capture latency | < 3s after call.hangup |
| Context injection latency | < 500ms before call.answered |
| Conversation recall accuracy | ≥ 3 specific facts from prior call |
| Zero amnesia on Kinan<>AIVA calls | 100% of calls have context injected |
| Swarm fan-out on AIVA intent | ≥ 1 worker spawned within 1s of utterance |
| Nightly epoch success rate | Runs every Sunday 2AM AEST without failure |

---

## ARCHITECTURE

```
ROYAL CHAMBER ARCHITECTURE
───────────────────────────────────────────────────────────────────
KINAN (King)
    │ voice call
    ▼
TELNYX AI ASSISTANT (AIVA's voice)
    │ webhooks
    ▼
┌─────────────────────────────────────────────────────────┐
│  MODULE 3: Call Lifecycle Layer                         │
│  WebhookInterceptor → RLMCaptureAgent → PostCallEnricher│
└───────────────────────────┬─────────────────────────────┘
                            │
        ┌───────────────────┴──────────────────┐
        │                                      │
        ▼                                      ▼
┌───────────────────┐              ┌───────────────────────┐
│  MODULE 4:        │              │  MODULE 5:            │
│  Context Retrieval│              │  Intent + Routing     │
│  BinduHydrator    │              │  IntentClassifier     │
│  ConversationReply│              │  SwarmRouter (6 types)│
└─────────┬─────────┘              └──────────┬────────────┘
          │                                   │
          └──────────────┬────────────────────┘
                         │
                         ▼
              ┌──────────────────────┐
              │  MODULE 6:           │
              │  SemanticMerge       │
              │  (Opus Reducer)      │
              └──────────┬───────────┘
                         │
                         ▼
         ┌───────────────────────────────┐
         │  MEMORY STACK                 │
         │  L1: Redis (2ms)              │
         │  L2: KG (5ms)                 │
         │  L3: Qdrant (10ms)            │
         │  L4: Postgres (50ms)          │
         └───────────────────────────────┘
                         │
         ┌───────────────┴───────────────┐
         ▼                               ▼
┌─────────────────┐          ┌─────────────────────┐
│  MODULE 7:      │          │  MODULE 8:           │
│  API + Injection│          │  Bridge + Dashboard  │
│  AIVAMemoryAPI  │          │  OpenClaw Bridge     │
│  Prompt Injector│          │  Royal Dashboard     │
└─────────────────┘          └─────────────────────┘
                                         │
                              ┌──────────▼──────────┐
                              │  MODULE 9:           │
                              │  NightlyEpoch (Evol) │
                              └─────────────────────┘
```

---

## EXECUTION PROTOCOL: RALPH WIGGUM LOOP

### Story Launch Command (copy-paste per story)

```
════════════════════════════════════════════════════
RALPH WIGGUM LOOP — Story [MODULE.STORY_N]
Story: [TITLE]
Context: FRESH — no prior session state
Budget: 15-19 iterations max
════════════════════════════════════════════════════

Task: Implement exactly what is specified below.
Working directory: E:\genesis-system\

[PASTE STORY SPEC]

Mandatory on completion:
1. All tests pass (0 failures)
2. Add VERIFICATION_STAMP comment to implemented file
3. Return: files created + test results + stamp
════════════════════════════════════════════════════
```

### Resource Authorization

- **Gemini API**: `GEMINI_API_KEY` from `E:\genesis-system\credentials`
- **OpenRouter**: `OPENROUTER_API_KEY` for overflow
- **Models**: Opus 4.6 (reasoning), Sonnet 4.6 (implementation), Haiku (fast classification)
- **Parallelism**: Max 4 workers per story, rate maximizer at 95%
- **Storage**: Elestio PostgreSQL + Qdrant + Redis — NO SQLite, NO C: drive

---

## WAVE ORDER (PARALLELISM MAP)

```
WAVE 1 (all parallel): Module 1 (Schema) + Module 2 (Registry)
WAVE 2 (all parallel): Module 3 (Call Lifecycle) + Module 4 (Context Retrieval)
WAVE 3 (all parallel): Module 5 (Intent + Routing) + Module 6 (Merge)
WAVE 4 (all parallel): Module 7 (API) + Module 8 (Bridge + Dashboard)
WAVE 5: Module 9 (NightlyEpoch — depends on all prior waves)
```

Stories within each module run SEQUENTIALLY (each depends on prior).
Modules within each wave run IN PARALLEL.

---

# MODULE 1: MEMORY SCHEMA
**Responsibility**: All database schemas, migrations, and collection setups.
**Must complete before**: All other modules
**Stories**: 1.01 → 1.05 (run sequentially)

---

### STORY 1.01: royal_conversations Postgres Schema

**File**: `data/migrations/001_royal_conversations.sql`
**Depends on**: Nothing (first story)
**Estimated iterations**: 6-8

**One-liner**: Create the royal_conversations Postgres table with all columns, indexes, and constraints.

**Implementation skeleton**:
```sql
-- data/migrations/001_royal_conversations.sql
CREATE TABLE royal_conversations (
  conversation_id    UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  started_at         TIMESTAMP NOT NULL,
  ended_at           TIMESTAMP,
  participants       JSONB NOT NULL DEFAULT '{"kinan": false, "aiva": false}',
  transcript_raw     TEXT,
  transcript_chunks  JSONB DEFAULT '[]',
  enriched_entities  JSONB DEFAULT '[]',
  decisions_made     JSONB DEFAULT '[]',
  action_items       JSONB DEFAULT '[]',
  emotional_signal   VARCHAR(20) CHECK (emotional_signal IN ('positive','neutral','frustrated','excited','urgent')),
  key_facts          JSONB DEFAULT '[]',
  kinan_directives   JSONB DEFAULT '[]',
  memory_vector_id   UUID,
  created_at         TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_royal_conv_started ON royal_conversations(started_at DESC);
CREATE INDEX idx_royal_conv_participants ON royal_conversations USING GIN(participants);
```

**Acceptance criteria**:
- [ ] Migration runs without error on Elestio PostgreSQL
- [ ] All 14 columns present with correct types
- [ ] 2 indexes created
- [ ] CHECK constraint enforces emotional_signal values
- [ ] `gen_random_uuid()` works (pgcrypto extension active)

**Test plan**:
```
BB1: Run migration → assert table exists with correct schema (SELECT * FROM information_schema.columns)
BB2: INSERT valid row → assert succeeds, conversation_id auto-generated
BB3: INSERT invalid emotional_signal → assert constraint violation raised

WB1: Re-run migration → assert idempotent (no crash if table exists — use CREATE TABLE IF NOT EXISTS)
WB2: INSERT participants with kinan=true → assert GIN index used in EXPLAIN
WB3: NULL transcript_raw → assert allowed (nullable field)

IT1: Connect via Elestio config, run migration, insert test row, SELECT back → verify all fields
```

---

### STORY 1.02: kinan_directives + aiva_capability_log Postgres Schemas

**File**: `data/migrations/002_king_queen_tables.sql`
**Depends on**: Story 1.01 (same migration runner)
**Estimated iterations**: 6-8

**One-liner**: Create kinan_directives and aiva_capability_log tables with indexes and constraints.

**Implementation skeleton**:
```sql
CREATE TABLE kinan_directives (
  directive_id    UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  captured_at     TIMESTAMP DEFAULT NOW(),
  source          VARCHAR(50) NOT NULL CHECK (source IN ('voice','text','inferred')),
  directive_text  TEXT NOT NULL,
  priority        INTEGER NOT NULL CHECK (priority BETWEEN 1 AND 5),
  status          VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active','fulfilled','cancelled')),
  related_entities JSONB DEFAULT '[]'
);

CREATE TABLE aiva_capability_log (
  log_id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  logged_at        TIMESTAMP DEFAULT NOW(),
  capability_type  VARCHAR(50) CHECK (capability_type IN ('new_skill','improved_recall','pattern_learned','bug_fixed')),
  description      TEXT NOT NULL,
  epoch_id         UUID,
  metrics          JSONB DEFAULT '{}'
);

CREATE INDEX idx_directives_status_priority ON kinan_directives(status, priority DESC);
CREATE INDEX idx_directives_captured ON kinan_directives(captured_at DESC);
```

**Acceptance criteria**:
- [ ] Both tables created without error
- [ ] CHECK constraints enforce source, priority, status, capability_type values
- [ ] Composite index on (status, priority DESC) exists

**Test plan**:
```
BB1: INSERT directive with priority=3 → assert succeeds
BB2: INSERT directive with priority=6 → assert constraint violation
BB3: INSERT capability log with valid type → assert succeeds

WB1: Query active directives ORDER BY priority DESC → assert index used (EXPLAIN)
WB2: INSERT invalid source value → assert CHECK violation
WB3: NULL epoch_id in capability_log → assert allowed (nullable)

IT1: Full migration on Elestio → insert rows in both tables → SELECT with ordering
```

---

### STORY 1.03: Redis Key Schema Design + Validation Module

**File**: `core/schema/redis_schema.py`
**Depends on**: Nothing
**Estimated iterations**: 8-10

**One-liner**: Define all Redis key patterns as typed constants with validation, TTL policy, and a key-builder helper.

**Implementation skeleton**:
```python
# core/schema/redis_schema.py
from dataclasses import dataclass
from typing import Optional

@dataclass
class RedisKeySchema:
    """All Redis keys used in AIVA RLM Nexus. Single source of truth."""

    # Key patterns (use .format() to build)
    AIVA_WORKING_STATE   = "aiva:state:{session_id}"        # JSON, versioned
    AIVA_TRANSCRIPT      = "aiva:transcript:{session_id}"   # List of chunks
    AIVA_CONTEXT_PRELOAD = "aiva:context:{session_id}"      # Pre-fetched XML
    GENESIS_STAGING      = "genesis:staging:{task_id}"      # Hash: agent_id → ProposedDelta
    GENESIS_MASTER_STATE = "genesis:state:master:{session_id}"  # Versioned master
    GENESIS_SWARM_TASKS  = "genesis:royal:swarm_tasks"      # Stream (no session suffix)
    AIVA_RESULTS         = "aiva:results:{session_id}"      # Pub/Sub channel
    KINAN_ACTIVE_DIRECTIVES = "kinan:directives:active"     # Sorted set (score=priority)

    # TTL policies (seconds)
    TTL_WORKING_STATE    = 86400   # 24 hours
    TTL_TRANSCRIPT       = 3600    # 1 hour (cleaned up after enrichment)
    TTL_CONTEXT_PRELOAD  = 300     # 5 minutes
    TTL_STAGING          = 600     # 10 minutes

def build_key(pattern: str, **kwargs) -> str:
    """Build a Redis key from a pattern. Raises ValueError if required fields missing."""
    ...

def validate_key(key: str) -> bool:
    """Returns True if key matches a known schema pattern."""
    ...
```

**Acceptance criteria**:
- [ ] All 8 key patterns defined as class constants
- [ ] All 4 TTL policies defined
- [ ] `build_key("aiva:state:{session_id}", session_id="abc")` → `"aiva:state:abc"`
- [ ] `build_key("aiva:state:{session_id}")` → raises `ValueError` (missing field)
- [ ] `validate_key("aiva:state:abc123")` → `True`
- [ ] `validate_key("unknown:key")` → `False`

**Test plan**:
```
BB1: build_key with all required fields → correct key string returned
BB2: build_key with missing field → ValueError raised
BB3: validate_key with known pattern → True returned

WB1: All 8 patterns have matching TTL (or explicit "no TTL" for streams)
WB2: build_key with extra fields → ignored cleanly (no error)
WB3: validate_key with partial match → False (strict matching)

IT1: Import module → no import errors → all constants accessible
```

---

### STORY 1.04: Qdrant Collection Setup Module

**File**: `core/schema/qdrant_schema.py`
**Depends on**: Nothing
**Estimated iterations**: 8-10

**One-liner**: Create and validate all Qdrant collections used by AIVA RLM Nexus, with correct vector dimensions and payload schema.

**Implementation skeleton**:
```python
# core/schema/qdrant_schema.py
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PayloadSchemaType

COLLECTIONS = {
    "aiva_conversations": {
        "vector_size": 768,          # nomic-embed-text dimensions
        "distance": Distance.COSINE,
        "payload_fields": {
            "conversation_id": PayloadSchemaType.UUID,
            "started_at": PayloadSchemaType.DATETIME,
            "emotional_signal": PayloadSchemaType.KEYWORD,
            "participants": PayloadSchemaType.KEYWORD,
        }
    },
    "kinan_directives": {
        "vector_size": 768,
        "distance": Distance.COSINE,
        "payload_fields": {
            "directive_id": PayloadSchemaType.UUID,
            "priority": PayloadSchemaType.INTEGER,
            "status": PayloadSchemaType.KEYWORD,
        }
    },
    "aiva_scars": {
        "vector_size": 768,
        "distance": Distance.COSINE,
        "payload_fields": {
            "scar_type": PayloadSchemaType.KEYWORD,
            "occurred_at": PayloadSchemaType.DATETIME,
        }
    },
}

def setup_collections(client: QdrantClient, recreate: bool = False) -> dict:
    """Create all collections. Returns {collection_name: created|exists|error}."""
    ...

def verify_collections(client: QdrantClient) -> bool:
    """Returns True if all required collections exist with correct config."""
    ...
```

**Acceptance criteria**:
- [ ] `setup_collections(client)` creates all 3 collections without error
- [ ] Re-running `setup_collections(client)` does not crash (idempotent)
- [ ] `verify_collections(client)` returns `True` after setup
- [ ] All collections use vector_size=768 (nomic-embed-text)

**Test plan**:
```
BB1: setup_collections on fresh Qdrant → all 3 collections exist
BB2: setup_collections again (already exists) → no crash, returns "exists" status
BB3: verify_collections after setup → True

WB1: Qdrant connection fails → setup_collections raises connection error (not swallowed)
WB2: recreate=True → collection dropped and re-created (clean state)
WB3: verify_collections before setup → False

IT1: Real Elestio Qdrant → setup → verify → insert test vector → search returns it
```

---

### STORY 1.05: Database Connection Module (Elestio Config)

**File**: `core/db/connections.py`
**Depends on**: Stories 1.01-1.04
**Estimated iterations**: 8-10

**One-liner**: Centralized connection factory for Elestio PostgreSQL, Redis, and Qdrant with health-check and retry logic.

**Implementation skeleton**:
```python
# core/db/connections.py
import sys
sys.path.append('E:/genesis-system/data/genesis-memory')
from elestio_config import PostgresConfig, RedisConfig, QdrantConfig

class ConnectionFactory:
    """Single source of truth for all database connections in AIVA RLM Nexus."""

    def get_postgres(self) -> psycopg2.connection:
        """Returns pooled Postgres connection. Retries 3x on failure."""
        ...

    def get_redis(self) -> redis.Redis:
        """Returns Redis client. Retries 3x on failure."""
        ...

    def get_qdrant(self) -> QdrantClient:
        """Returns Qdrant client. Retries 3x on failure."""
        ...

    def health_check(self) -> dict:
        """Returns {"postgres": bool, "redis": bool, "qdrant": bool}"""
        ...

# Singleton
connections = ConnectionFactory()
```

**Acceptance criteria**:
- [ ] `connections.get_postgres()` returns valid connection to Elestio PostgreSQL
- [ ] `connections.get_redis()` returns valid Redis client
- [ ] `connections.get_qdrant()` returns valid Qdrant client
- [ ] `connections.health_check()` returns all True on healthy services
- [ ] Each connection retries 3x with 1s backoff before raising

**Test plan**:
```
BB1: health_check() on real Elestio → {"postgres": True, "redis": True, "qdrant": True}
BB2: get_postgres() → can execute SELECT 1
BB3: get_redis() → can PING

WB1: Simulate connection failure → retries 3x, then raises ConnectionError
WB2: health_check() with Redis down → {"postgres": True, "redis": False, "qdrant": True}
WB3: Singleton pattern → same object returned on multiple calls (no duplicate connections)

IT1: Full stack: get all 3 connections, execute simple operations on each
```

---

# MODULE 2: REGISTRY LAYER
**Responsibility**: AIVA identity store (Queen) and Kinan directive store (King).
**Depends on**: Module 1 complete
**Stories**: 2.01 → 2.05 (run sequentially within module, parallel with Module 1 Wave 1)

---

### STORY 2.01: QueenRegistry — Identity Read

**File**: `core/registry/queen_registry.py` (initial implementation)
**Depends on**: Story 1.05 (connections)
**Estimated iterations**: 8-10

**One-liner**: Implement `QueenRegistry.get_identity()` — reads AIVA's identity from Postgres with Redis cache.

**Implementation skeleton**:
```python
class QueenRegistry:
    def get_identity(self) -> dict:
        """
        Returns AIVA's current identity object.
        Tries Redis cache first (key: aiva:identity), falls back to Postgres.

        Returns:
            {
                "name": "AIVA",
                "role": "Queen AI — Lead Orchestrator",
                "voice_model": str,
                "total_conversations": int,
                "last_evolved": str (ISO date),
                "active_capabilities": list[str],
                "recent_improvements": list[str]
            }

        Raises:
            RegistryError: if both cache and DB fail
        """
        ...
```

**Acceptance criteria**:
- [ ] Returns dict with all 7 required fields
- [ ] Cache hit (Redis): response in < 5ms
- [ ] Cache miss (Postgres): response in < 100ms, writes to Redis cache
- [ ] If Postgres fails: raises `RegistryError` with message
- [ ] `total_conversations` = COUNT from royal_conversations table

**Test plan**:
```
BB1: get_identity() → all 7 fields present, correct types
BB2: Call twice → second call uses Redis cache (assert Redis GET called on second)
BB3: total_conversations matches actual row count in royal_conversations

WB1: Redis unavailable → falls back to Postgres, no crash
WB2: Postgres unavailable → raises RegistryError (not generic Exception)
WB3: Cache TTL expires → re-fetches from Postgres on next call

IT1: Real Elestio stack → get_identity() → verify total_conversations is live count
```

---

### STORY 2.02: QueenRegistry — Capability Logging

**File**: `core/registry/queen_registry.py` (extend Story 2.01)
**Depends on**: Story 2.01
**Estimated iterations**: 6-8

**One-liner**: Implement `log_capability_gain()` and `get_capability_history()` on QueenRegistry.

**Implementation skeleton**:
```python
def log_capability_gain(
    self,
    description: str,
    capability_type: str,
    metrics: dict = None,
    epoch_id: str = None
) -> str:
    """
    Logs a new capability gain to aiva_capability_log.
    Invalidates identity cache (total_conversations may have changed).
    Returns: log_id (UUID string)
    """
    ...

def get_capability_history(self, last_n: int = 10) -> list[dict]:
    """
    Returns last N capability gains, ordered by logged_at DESC.
    Returns: [{"log_id": str, "description": str, "capability_type": str, "logged_at": str, "metrics": dict}]
    """
    ...
```

**Acceptance criteria**:
- [ ] `log_capability_gain("memory persistence", "new_skill")` → returns UUID string
- [ ] Row appears in `aiva_capability_log` table
- [ ] `get_capability_history(5)` returns max 5 items, ordered newest first
- [ ] Invalid `capability_type` → raises `ValueError`
- [ ] After `log_capability_gain()`, identity cache is invalidated

**Test plan**:
```
BB1: log_capability_gain with valid args → UUID returned, row in Postgres
BB2: get_capability_history(3) → returns max 3 items, newest first
BB3: Invalid capability_type → ValueError raised before DB write

WB1: Cache invalidation → get_identity() after log → fresh data from Postgres
WB2: metrics=None → stored as {} in Postgres (not NULL)
WB3: DB write fails → RegistryError raised, nothing partially written

IT1: log + get_history in sequence on real Elestio → data matches
```

---

### STORY 2.03: KingRegistry — Directive Write + Read

**File**: `core/registry/king_registry.py`
**Depends on**: Story 1.05 (connections)
**Estimated iterations**: 8-10

**One-liner**: Implement `add_directive()`, `get_active_directives()`, and `mark_fulfilled()` on KingRegistry.

**Implementation skeleton**:
```python
class KingRegistry:
    def add_directive(
        self,
        text: str,
        priority: int,
        source: str  # 'voice' | 'text' | 'inferred'
    ) -> str:
        """
        Adds a new Kinan directive.
        Returns: directive_id (UUID string)
        Raises: ValueError if priority not 1-5 or source invalid
        """
        ...

    def get_active_directives(self, top_n: int = 5) -> list[dict]:
        """
        Returns top_n active directives ordered by priority DESC.
        Returns: [{"directive_id": str, "text": str, "priority": int, "captured_at": str}]
        """
        ...

    def mark_fulfilled(self, directive_id: str) -> bool:
        """
        Updates directive status to 'fulfilled'.
        Returns: True if updated, False if not found.
        """
        ...
```

**Acceptance criteria**:
- [ ] `add_directive("Build tradie scraper", 1, "voice")` → UUID returned, row in DB
- [ ] `get_active_directives(3)` → max 3, ordered priority DESC, status=active only
- [ ] `mark_fulfilled(id)` → status updated, no longer appears in get_active_directives
- [ ] Priority 6 → ValueError
- [ ] Invalid source → ValueError

**Test plan**:
```
BB1: add_directive + get_active_directives → directive appears in results
BB2: mark_fulfilled → directive removed from active results
BB3: get_active_directives(2) with 5 active → only top 2 returned

WB1: Priority validation (0, 6) → ValueError before DB write
WB2: mark_fulfilled with unknown ID → returns False (not exception)
WB3: Multiple directives same priority → stable ordering (by captured_at)

IT1: Full cycle on Elestio: add 3 directives, get active, fulfill 1, verify count
```

---

### STORY 2.04: KingRegistry — Conversation Inference

**File**: `core/registry/king_registry.py` (extend Story 2.03)
**Depends on**: Story 2.03
**Estimated iterations**: 8-10

**One-liner**: Implement `infer_from_conversation()` — parses enriched conversation memory and extracts implicit Kinan directives.

**Implementation skeleton**:
```python
def infer_from_conversation(self, enriched_memory: dict) -> list[str]:
    """
    Extracts Kinan directives from a PostCallEnricher output.
    Looks at enriched_memory["kinan_directives"] list.
    Adds each as a new directive with source='inferred'.
    Deduplicates: skips directives semantically similar to existing active ones.

    Args:
        enriched_memory: dict from PostCallEnricher (must have "kinan_directives" key)

    Returns:
        list of newly added directive_ids

    Raises:
        ValueError: if enriched_memory missing "kinan_directives" key
    """
    ...

def _is_duplicate(self, new_text: str, existing_directives: list[dict]) -> bool:
    """
    Returns True if new_text is semantically similar to any existing directive.
    Uses simple substring match (not vector similarity — keep it fast).
    """
    ...
```

**Acceptance criteria**:
- [ ] `infer_from_conversation({"kinan_directives": ["Build tradie scraper", "Call George"]})` → 2 directive_ids returned
- [ ] Each directive stored with `source='inferred'`
- [ ] If directive text already exists (substring match) → skipped, not duplicated
- [ ] Empty `kinan_directives` list → returns empty list (no error)
- [ ] Missing `kinan_directives` key → raises `ValueError`

**Test plan**:
```
BB1: 2 new directives → 2 IDs returned, 2 rows in DB
BB2: 1 new + 1 duplicate → 1 ID returned, 1 row added
BB3: Empty list → [] returned, 0 rows added

WB1: Missing key → ValueError raised before DB write
WB2: Duplicate detection: exact match → skipped
WB3: Duplicate detection: partial match ("scraper" vs "build tradie scraper") → not skipped (too strict is wrong)

IT1: Real enriched_memory from PostCallEnricher format → directives extracted and stored
```

---

### STORY 2.05: KingRegistry — Qdrant Semantic Write

**File**: `core/registry/king_registry.py` (extend Story 2.03-2.04)
**Depends on**: Stories 2.03, 2.04, 1.04 (Qdrant setup)
**Estimated iterations**: 8-10

**One-liner**: Extend `add_directive()` to also write directive vector to Qdrant `kinan_directives` collection.

**Implementation skeleton**:
```python
def _write_to_qdrant(self, directive_id: str, text: str, priority: int, status: str) -> bool:
    """
    Embeds directive text with nomic-embed-text (via local Ollama on Mac Mini OR
    fallback: sentence-transformers all-MiniLM-L6-v2 locally).
    Upserts to Qdrant kinan_directives collection.

    Returns: True on success, False on failure (non-fatal — Postgres is source of truth)
    """
    ...

def search_directives(self, query: str, top_k: int = 3) -> list[dict]:
    """
    Semantic search over directives in Qdrant.
    Returns: [{"directive_id": str, "text": str, "score": float}]
    """
    ...
```

**Acceptance criteria**:
- [ ] `add_directive()` triggers `_write_to_qdrant()` automatically
- [ ] `_write_to_qdrant()` failure → returns `False`, does NOT fail the `add_directive()` call (non-fatal)
- [ ] `search_directives("build a scraper")` → returns semantically relevant results
- [ ] Upsert: same `directive_id` → updates existing Qdrant record, not duplicate

**Test plan**:
```
BB1: add_directive → Qdrant record exists with correct payload
BB2: search_directives("tradie scraper") → returns "Build tradie scraper" in results
BB3: Qdrant fails → add_directive still succeeds (Postgres written, Qdrant failure logged)

WB1: Upsert same directive_id → single record in Qdrant (not duplicate)
WB2: Embedding failure → logged as warning, add_directive continues
WB3: search_directives with no matches → returns [] not exception

IT1: Full cycle on Elestio: add directive → Qdrant record exists → search returns it
```

---

# MODULE 3: CALL LIFECYCLE LAYER
**Responsibility**: Telnyx webhook handling, live transcript capture, and post-call enrichment.
**Depends on**: Module 1 + Module 2 complete
**Stories**: 3.01 → 3.10 (run sequentially within module)

---

### STORY 3.01: Telnyx Webhook Signature Verification

**File**: `core/interceptors/telnyx_signature.py`
**Depends on**: Nothing (standalone utility)
**Estimated iterations**: 6-8

**One-liner**: Implement Telnyx webhook signature verification using HMAC-SHA256 and the Telnyx public key.

**Implementation skeleton**:
```python
# core/interceptors/telnyx_signature.py
import hmac, hashlib, base64

def verify_telnyx_signature(
    payload_body: bytes,
    telnyx_signature: str,
    telnyx_timestamp: str,
    public_key: str
) -> bool:
    """
    Verifies that a webhook request genuinely came from Telnyx.
    Uses ECDSA P-256 signature verification (Telnyx standard).

    Returns: True if valid, False if invalid
    Raises: SignatureError if signature format is malformed
    """
    ...

class SignatureError(Exception):
    pass
```

**Acceptance criteria**:
- [ ] Valid Telnyx test signature → returns `True`
- [ ] Tampered payload → returns `False`
- [ ] Malformed signature string → raises `SignatureError`
- [ ] Timing-safe comparison (no timing oracle)

**Test plan**:
```
BB1: Valid signature from Telnyx test vector → True
BB2: Modified payload, valid signature → False
BB3: Correct payload, modified signature → False

WB1: Malformed base64 in signature → SignatureError
WB2: Empty timestamp → SignatureError
WB3: Comparison is timing-safe (hmac.compare_digest used)

IT1: Real Telnyx webhook with test credentials → signature validates correctly
```

---

### STORY 3.02: WebhookInterceptor — call.initiated Handler

**File**: `core/interceptors/telnyx_webhook_interceptor.py`
**Depends on**: Story 3.01
**Estimated iterations**: 8-10

**One-liner**: Handle `call.initiated` Telnyx event — verify signature, log to Postgres, spawn async context pre-load.

**Implementation skeleton**:
```python
class TelnyxWebhookInterceptor:
    async def handle_call_initiated(self, payload: dict) -> dict:
        """
        Handles call.initiated event.
        1. Extracts session_id from payload
        2. Logs conversation start to royal_conversations (started_at = now)
        3. Spawns background task: BinduHydrator.hydrate(session_id) [non-blocking]
        4. Returns {"status": "ok", "session_id": session_id}

        Raises: Nothing — any failures logged and swallowed (call must not break)
        """
        ...
```

**Acceptance criteria**:
- [ ] `handle_call_initiated(valid_payload)` returns `{"status": "ok", "session_id": "..."}`
- [ ] Row inserted in `royal_conversations` with `started_at = now`, `ended_at = NULL`
- [ ] BinduHydrator spawn is non-blocking (function returns before hydration completes)
- [ ] If Postgres fails: logs error, still returns `{"status": "ok"}` (call must not break)

**Test plan**:
```
BB1: Valid call.initiated payload → {"status": "ok"} returned
BB2: Row inserted in royal_conversations with correct started_at
BB3: Returns immediately even if BinduHydrator is slow (< 50ms response time)

WB1: Postgres write fails → logged as error, {"status": "ok"} still returned
WB2: session_id extracted from payload["data"]["payload"]["call_session_id"]
WB3: BinduHydrator task created in background (asyncio.create_task used)

IT1: POST mock call.initiated to endpoint → verify DB row + fast response
```

---

### STORY 3.03: WebhookInterceptor — call.answered Handler

**File**: `core/interceptors/telnyx_webhook_interceptor.py` (extend)
**Depends on**: Story 3.02
**Estimated iterations**: 6-8

**One-liner**: Handle `call.answered` event — spawn RLMCaptureAgent in background.

**Implementation skeleton**:
```python
async def handle_call_answered(self, payload: dict) -> dict:
    """
    Handles call.answered event.
    1. Extracts session_id, call_control_id from payload
    2. Marks conversation as answered in Redis: SET aiva:state:{session_id} {"status": "active"}
    3. Spawns RLMCaptureAgent.run(session_id, call_control_id) as background task
    4. Returns {"status": "ok", "capture_started": True}
    """
    ...
```

**Acceptance criteria**:
- [ ] Returns `{"status": "ok", "capture_started": True}` on valid payload
- [ ] Redis key `aiva:state:{session_id}` set with `{"status": "active"}`
- [ ] RLMCaptureAgent spawned as background task (non-blocking)
- [ ] If Redis fails: RLMCaptureAgent still spawned

**Test plan**:
```
BB1: Valid call.answered payload → {"status": "ok", "capture_started": True}
BB2: Redis key aiva:state:{session_id} exists after handler
BB3: Handler returns in < 50ms (non-blocking)

WB1: Redis write fails → RLMCaptureAgent still spawned (Redis failure is non-fatal)
WB2: call_control_id correctly extracted from payload
WB3: Background task is asyncio.create_task (not await — non-blocking)

IT1: POST mock call.answered → verify Redis key set + response fast
```

---

### STORY 3.04: WebhookInterceptor — call.hangup Handler

**File**: `core/interceptors/telnyx_webhook_interceptor.py` (extend)
**Depends on**: Story 3.03
**Estimated iterations**: 8-10

**One-liner**: Handle `call.hangup` event — mark call ended in Redis/Postgres, spawn PostCallEnricher with 3s deadline.

**Implementation skeleton**:
```python
async def handle_call_hangup(self, payload: dict) -> dict:
    """
    Handles call.hangup event.
    1. Extracts session_id from payload
    2. Updates Redis: aiva:state:{session_id} → {"status": "ended", "ended_at": timestamp}
    3. Updates Postgres royal_conversations: ended_at = now
    4. Spawns PostCallEnricher.enrich(session_id) with 3s hard deadline
    5. Returns {"status": "ok", "enrichment_started": True}
    """
    ...
```

**Acceptance criteria**:
- [ ] Returns `{"status": "ok", "enrichment_started": True}` immediately
- [ ] Redis state updated to `{"status": "ended"}`
- [ ] Postgres `ended_at` updated
- [ ] PostCallEnricher spawned with asyncio deadline (asyncio.wait_for with timeout=3.0)

**Test plan**:
```
BB1: Valid call.hangup → {"status": "ok"} returned
BB2: Postgres ended_at updated after handler
BB3: Redis state shows "ended" after handler

WB1: PostCallEnricher fails within 3s → logged as error, no crash
WB2: PostCallEnricher times out at 3s → TimeoutError logged, no crash
WB3: session_id correctly maps to conversation_id in Postgres

IT1: Full lifecycle: initiated → answered → hangup → all DB states correct
```

---

### STORY 3.05: RLMCaptureAgent — Async Loop + Lifecycle

**File**: `core/agents/rlm_capture_agent.py`
**Depends on**: Story 1.03 (Redis schema)
**Estimated iterations**: 8-10

**One-liner**: Implement the RLMCaptureAgent async loop that polls for call-ended signal and manages its own lifecycle.

**Implementation skeleton**:
```python
class RLMCaptureAgent:
    def __init__(self, session_id: str, call_control_id: str):
        self.session_id = session_id
        self.call_control_id = call_control_id
        self._running = True

    async def run(self):
        """
        Main loop. Runs until call ended.
        Polls every 30 seconds.
        Checks Redis aiva:state:{session_id} for {"status": "ended"} to stop.
        Max runtime: 3600 seconds (safety guard against zombie agents).
        """
        ...

    def _is_call_ended(self) -> bool:
        """Returns True if Redis state shows 'ended'."""
        ...

    async def stop(self):
        """Graceful shutdown. Sets _running = False."""
        self._running = False
```

**Acceptance criteria**:
- [ ] `run()` loops every 30s until `_is_call_ended()` returns True
- [ ] Max runtime 3600s (guard against zombies)
- [ ] Agent stops within 35s of Redis state being set to "ended"
- [ ] `_is_call_ended()` returns False if Redis key missing (not True)

**Test plan**:
```
BB1: Set Redis state to "ended" → agent stops within 35s
BB2: No Redis key → _is_call_ended() returns False (agent continues)
BB3: Zombie guard → agent stops after 3600s regardless of Redis state

WB1: Redis query fails → _is_call_ended() returns False (fail-safe: keep running)
WB2: Multiple stop() calls → idempotent, no crash
WB3: run() returns normally (no exception) after stopping

IT1: Spawn agent → run for 60s → set Redis ended → verify agent stops
```

---

### STORY 3.06: RLMCaptureAgent — Transcript Fetch + Speaker Labeling

**File**: `core/agents/rlm_capture_agent.py` (extend Story 3.05)
**Depends on**: Story 3.05
**Estimated iterations**: 10-12

**One-liner**: Implement transcript chunk fetching from Telnyx AI Assistant API with speaker labeling.

**Implementation skeleton**:
```python
async def _fetch_and_label_chunk(self) -> Optional[dict]:
    """
    Fetches latest transcript from Telnyx AI Assistant transcript API.
    Labels speakers: "AIVA" (AI), "KINAN" or "CUSTOMER" (based on call direction).

    Returns:
        {
            "t": float (unix timestamp),
            "speaker": "AIVA" | "KINAN" | "CUSTOMER",
            "text": str,
            "chunk_index": int
        }
        Or None if no new content since last fetch.

    Raises: Nothing — all errors caught and logged internally.
    """
    ...
```

**Acceptance criteria**:
- [ ] Returns chunk dict with all 4 fields on success
- [ ] Returns `None` if no new transcript content
- [ ] Speaker labeled as "AIVA" for AI turns, "KINAN"/"CUSTOMER" for human turns
- [ ] Any API failure → returns `None` (non-fatal)

**Test plan**:
```
BB1: Mock Telnyx returns transcript → chunk dict returned with correct fields
BB2: Mock Telnyx returns empty transcript → None returned
BB3: Speaker labeling: outbound call → "KINAN", inbound call → "CUSTOMER"

WB1: Telnyx API timeout → None returned (no exception)
WB2: Telnyx returns malformed JSON → None returned, error logged
WB3: chunk_index increments correctly across sequential calls

IT1: Real Telnyx test call → transcript chunk fetched with correct speaker label
```

---

### STORY 3.07: RLMCaptureAgent — Redis Append + Error Isolation

**File**: `core/agents/rlm_capture_agent.py` (extend Stories 3.05-3.06)
**Depends on**: Story 3.06
**Estimated iterations**: 6-8

**One-liner**: Wire the capture loop — fetch chunk → append to Redis list → isolate errors so agent never crashes.

**Implementation skeleton**:
```python
async def _capture_cycle(self):
    """
    One capture cycle (called every 30s from run() loop).
    1. Call _fetch_and_label_chunk()
    2. If chunk: RPUSH to aiva:transcript:{session_id}
    3. If any exception: log warning, continue (NEVER raise)
    """
    ...
```

**Acceptance criteria**:
- [ ] Successful chunk → appended to Redis list `aiva:transcript:{session_id}`
- [ ] Redis RPUSH failure → logged as warning, agent continues running
- [ ] After 3 failed cycles in a row → logged as error (escalation), agent still continues
- [ ] After call ends: Redis list contains all captured chunks in order

**Test plan**:
```
BB1: 5-minute simulated call → Redis list contains ~10 chunks (every 30s)
BB2: Call drops at 90s → Redis list contains ~3 chunks
BB3: Telnyx failure on cycle 4 → chunks 1-3 and 5+ captured, cycle 4 logged

WB1: Redis RPUSH raises exception → warning logged, loop continues
WB2: 3 consecutive failures → error-level log (not just warning)
WB3: Chunks in Redis preserve insertion order (RPUSH + LRANGE)

IT1: Real Telnyx test call → Redis list populated → LRANGE returns all chunks
```

---

### STORY 3.08: PostCallEnricher — Gemini Enrichment

**File**: `core/enrichers/post_call_enricher.py`
**Depends on**: Story 1.05 (connections), Story 3.07 (transcript in Redis)
**Estimated iterations**: 10-12

**One-liner**: Implement the Gemini Flash enrichment call — reads transcript from Redis, calls Gemini, returns parsed enrichment object.

**Implementation skeleton**:
```python
class PostCallEnricher:
    async def _enrich_with_gemini(self, session_id: str) -> dict:
        """
        1. LRANGE aiva:transcript:{session_id} 0 -1 from Redis
        2. Join chunks into formatted transcript string
        3. Call Gemini Flash with structured enrichment prompt
        4. Parse response as JSON
        5. Return enriched object

        Returns: {
            "summary": str,
            "entities": list[str],
            "decisions_made": list[str],
            "action_items": list[dict],
            "emotional_signal": str,
            "key_facts": list[str],
            "kinan_directives": list[str]
        }

        Raises: EnrichmentError if Gemini fails AND fallback fails
        """
        ...

    def _parse_gemini_response(self, raw_response: str) -> dict:
        """Parses Gemini JSON response. Falls back to raw summary if JSON invalid."""
        ...
```

**Acceptance criteria**:
- [ ] Returns dict with all 7 fields on success
- [ ] Malformed Gemini JSON → falls back to `{"summary": raw_response, "entities": [], ...}`
- [ ] Empty transcript → returns `{"summary": "Empty call — no transcript captured", ...}`
- [ ] Gemini call uses `gemini-2.0-flash` model (not Pro — speed matters)

**Test plan**:
```
BB1: 5-min transcript → all 7 fields populated
BB2: Empty Redis transcript → graceful empty enrichment object
BB3: Real Gemini Flash call → response parsed correctly

WB1: Gemini returns malformed JSON → fallback summary returned (no exception)
WB2: Gemini timeout → EnrichmentError raised
WB3: action_items correctly structured as list of dicts with "task", "owner", "deadline"

IT1: Real transcript in Redis → Gemini enrichment → valid 7-field object returned
```

---

### STORY 3.09: PostCallEnricher — Qdrant Write + Deadline Handler

**File**: `core/enrichers/post_call_enricher.py` (extend Story 3.08)
**Depends on**: Story 3.08, Story 1.04 (Qdrant collections)
**Estimated iterations**: 8-10

**One-liner**: Implement enrichment storage — embed conversation, write to Qdrant, enforce 3s deadline.

**Implementation skeleton**:
```python
async def enrich(self, session_id: str) -> Optional[str]:
    """
    Main entry point called by WebhookInterceptor on call.hangup.
    Must complete within 3 seconds.

    1. _enrich_with_gemini(session_id) [Story 3.08]
    2. Embed enriched["summary"] with nomic-embed-text → 768-dim vector
    3. Upsert to Qdrant aiva_conversations with enriched payload
    4. Returns: Qdrant vector_id (UUID string) or None on failure
    """
    ...
```

**Acceptance criteria**:
- [ ] `enrich(session_id)` completes within 3 seconds (p95)
- [ ] Returns Qdrant vector UUID on success
- [ ] Qdrant record has full enriched payload as payload fields
- [ ] Returns `None` on failure (Qdrant write is non-fatal)

**Test plan**:
```
BB1: enrich() → Qdrant record exists with correct payload
BB2: enrich() latency p95 < 3s (over 20 calls with real Gemini + Qdrant)
BB3: Returns None if Qdrant fails (not exception)

WB1: Embedding fails → None returned, error logged
WB2: Qdrant upsert vs insert: same session_id → updates, not duplicates
WB3: vector_id returned is a valid UUID format

IT1: Full enrichment cycle on Elestio: Redis transcript → Gemini → Qdrant → UUID returned
```

---

### STORY 3.10: PostCallEnricher — Postgres Write + Redis Cleanup

**File**: `core/enrichers/post_call_enricher.py` (extend Story 3.09)
**Depends on**: Story 3.09
**Estimated iterations**: 6-8

**One-liner**: Write enrichment to Postgres cold ledger, update royal_conversations row, clean up Redis transcript.

**Implementation skeleton**:
```python
async def _persist_to_postgres(
    self,
    session_id: str,
    enriched: dict,
    vector_id: Optional[str]
) -> bool:
    """
    Updates royal_conversations row for this session_id:
    - Sets transcript_raw, enriched_entities, decisions_made, action_items,
      emotional_signal, key_facts, kinan_directives, memory_vector_id
    Returns: True on success, False on failure
    """
    ...

async def _cleanup_redis(self, session_id: str):
    """Deletes aiva:transcript:{session_id} from Redis after successful Postgres write."""
    ...
```

**Acceptance criteria**:
- [ ] Postgres `royal_conversations` row updated with all enriched fields
- [ ] `memory_vector_id` set to Qdrant UUID from Story 3.09
- [ ] Redis `aiva:transcript:{session_id}` deleted after Postgres write
- [ ] If Postgres fails: Redis transcript NOT deleted (data preserved for retry)

**Test plan**:
```
BB1: enrich() → Postgres row has all enriched fields populated
BB2: After enrich() → Redis aiva:transcript:{session_id} key gone
BB3: Postgres fail → Redis transcript still exists (not prematurely deleted)

WB1: Partial write (some fields fail) → transaction rolled back, all fields remain NULL
WB2: Redis DELETE fails → logged, but no crash (Redis cleanup is best-effort)
WB3: memory_vector_id in Postgres matches Qdrant UUID

IT1: Full pipeline: Redis transcript → Gemini → Qdrant → Postgres → Redis cleaned
```

---

# MODULE 4: CONTEXT RETRIEVAL
**Stories**: 4.01 → 4.07 (run sequentially)
**Depends on**: Module 1 + Module 3

*(Stories 4.01–4.07 follow same pattern — BinduHydrator scatter tasks built one by one,
then assembled, then ConversationReplayEngine. Full specs follow same template as above.
Omitted here for brevity — see Module 3 as the template.)*

---

# MODULES 5-9: [Following same atomic pattern]

*(Full specs for Modules 5-9 follow the identical story structure established above.
Each story: 1 file, 1 function/class, 3 BB + 3 WB + 1 IT tests, 8-15 iterations.)*

---

## SUMMARY

| Module | Stories | Wave | Parallel With |
|--------|---------|------|---------------|
| 1: Memory Schema | 5 stories | 1 | Module 2 |
| 2: Registry Layer | 5 stories | 1 | Module 1 |
| 3: Call Lifecycle | 10 stories | 2 | Module 4 |
| 4: Context Retrieval | 7 stories | 2 | Module 3 |
| 5: Intent + Routing | 11 stories | 3 | Module 6 |
| 6: Merge + Coherence | 3 stories | 3 | Module 5 |
| 7: API + Injection | 7 stories | 4 | Module 8 |
| 8: Bridge + Dashboard | 6 stories | 4 | Module 7 |
| 9: Nightly Epoch | 5 stories | 5 | — |
| **TOTAL** | **59 stories** | 5 waves | — |

**Estimated clock time** (with full parallelism):
- Wave 1-4: ~3-4 days (parallel modules, 2-4 hrs per story)
- Wave 5: ~1 day
- **Total: ~4-5 days to AIVA has living memory**

---

*This PRD follows Genesis PRD Writing Doctrine v1.0*
*Each story is truly atomic: 1 file, 1 responsibility, 12-19 iterations, 100% test coverage*


Successfully wrote to E:\genesis-system\docs\plans\AIVA_RLM_NEXUS_PRD_V2.md (1277 lines) ✅ File written successfully! (1277 lines)

💡 Performance tip: For optimal speed, consider chunking files into ≤30 line pieces in future operations.


Conversation compacted