blue/.blue/docs/rfcs/0056-alignment-visualization-dashboard.draft.md
Eric Garcia 6e8f0db6c0 chore: add dialogues, RFCs, docs and minor improvements
- Add dialogue prompt file writing for audit/debugging
- Update README install instructions
- Add new RFCs (0053, 0055-0059, 0062)
- Add recorded dialogues and expert pools
- Add ADR 0018 dynamodb-portable-schema
- Update TODO with hook configuration notes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-26 08:51:56 -05:00

554 lines
26 KiB
Markdown

# RFC 0056: Alignment Visualization Dashboard
| | |
|---|---|
| **Status** | Draft |
| **Date** | 2026-02-02 |
| **ADRs** | 0014 (Alignment Dialogue Agents), 0018 (DynamoDB-Portable Schema) |
| **Depends On** | RFC 0051 (Global Perspective & Tension Tracking), RFC 0053 (Storage Abstraction Layer) |
---
## Summary
Build a Next.js web application to visualize alignment dialogues with three core capabilities:
1. **Real-time Monitoring** — Watch dialogues in progress via WebSocket
2. **Post-Dialogue Analysis** — Deep-dive into completed dialogues
3. **Cross-Dialogue Analytics** — Discover patterns across many dialogues
**Key design principle:** Storage-agnostic architecture that works identically with SQLite (local) or DynamoDB (AWS production).
## Problem
The alignment dialogue system generates rich structured data:
- Expert contributions and scores
- Perspectives, tensions, recommendations, evidence, claims
- Cross-references between entities
- Velocity and convergence metrics
- Verdicts and dissents
Currently this data is only accessible via:
- Raw SQLite queries
- MCP tool calls
- JSON exports
There's no visual way to:
- Monitor a dialogue in real-time during execution
- Explore the relationship graph between entities
- Compare dialogues or track patterns over time
- Share dialogue results with stakeholders
## Design
### Architecture
**Local Development:**
```
┌─────────────────────────────────────────────────────────────┐
│ Next.js Dashboard │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Live │ │ Analysis │ │ Analytics │ │
│ │ Monitor │ │ Explorer │ │ Dashboard │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ │
WebSocket (ws://) REST API (http://)
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Next.js API Routes │
│ WS /api/ws/dialogues/:id GET /api/dialogues/:id │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ DialogueStore (RFC 0053) │
│ ┌─────────────────────┐ │
│ │ SqliteDialogueStore │ │
│ │ (better-sqlite3) │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
**AWS Production:**
```
┌─────────────────────────────────────────────────────────────┐
│ Next.js Dashboard │
│ (Vercel / Amplify) │
└─────────────────────────────────────────────────────────────┘
│ │
WebSocket (wss://) REST API (https://)
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────────────────┐
│ API Gateway │ │ Lambda Functions │
│ WebSocket API │ │ GET /dialogues/:id │
│ $connect │ │ GET /stats │
│ $disconnect │ └─────────────────────────────┘
│ subscribe │ │
└─────────────────┘ │
│ │
└───────────────┬───────────────┘
┌─────────────────────────────────────────────────────────────┐
│ DialogueStore (RFC 0053) │
│ ┌─────────────────────┐ │
│ │ DynamoDialogueStore │ │
│ │ (encrypted, KMS) │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
**Key Principle:** The `DialogueStore` interface (RFC 0053) is identical in both environments. The dashboard code doesn't know or care which backend is active.
### Tech Stack
| Layer | Technology | Rationale |
|-------|------------|-----------|
| Framework | Next.js 14+ (App Router) | SSR, API routes, React Server Components |
| Styling | Tailwind CSS | Rapid iteration, consistent design |
| Charts | Recharts or Victory | React-native charting |
| Graph | React Flow or D3 | Interactive node-edge visualization |
| State | Zustand or React Query | Lightweight, SSR-friendly |
| Real-time | Server-Sent Events or WebSocket | Live updates |
### Core Views
#### 1. Live Monitor (`/live/:dialogueId`)
Real-time view during active dialogue execution.
**Components:**
- **Velocity Chart** — Line chart showing ALIGNMENT score per round
- **Convergence Indicator** — Visual signal when velocity approaches zero
- **Expert Leaderboard** — Live-updating score table
- **Tension Tracker** — Open/addressed/resolved counts with progress bar
- **Activity Feed** — Stream of new perspectives, tensions, verdicts
**Data Source:** `get_dialogue_progress()` polled or streamed
**Wireframe:**
```
┌────────────────────────────────────────────────────────────┐
│ 📊 Live: Investment Portfolio Analysis [Round 3] │
├────────────────────────────────────────────────────────────┤
│ ┌──────────────────────┐ ┌────────────────────────────┐ │
│ │ ALIGNMENT Velocity │ │ Expert Leaderboard │ │
│ │ ▲ │ │ 1. 🧁 Donut 33 pts │ │
│ │ │ ● │ │ 2. 🧁 Muffin 22 pts │ │
│ │ │ ● ● │ │ 3. 🧁 Palmier 12 pts │ │
│ │ │● │ │ 4. 🧁 Cupcake 13 pts │ │
│ │ └──────────▶ │ └────────────────────────────┘ │
│ │ R0 R1 R2 R3 │ │
│ └──────────────────────┘ ┌────────────────────────────┐ │
│ │ Tensions │ │
│ ┌──────────────────────┐ │ ████████░░ 4/5 resolved │ │
│ │ 🟢 Converging │ └────────────────────────────┘ │
│ │ Velocity: +2 │ │
│ │ Est. 1 round left │ │
│ └──────────────────────┘ │
├────────────────────────────────────────────────────────────┤
│ Activity Feed │
│ • [R3] Muffin: CONVERGE on R0001 options overlay │
│ • [R3] Donut: RESOLVE T0001 via C0101 │
│ • [R2] Palmier: NEW P0101 concentration risk │
└────────────────────────────────────────────────────────────┘
```
#### 2. Analysis Explorer (`/dialogue/:dialogueId`)
Post-hoc exploration of a completed dialogue.
**Components:**
- **Summary Card** — Title, question, final verdict, total ALIGNMENT
- **Entity Graph** — Interactive visualization of P/R/T/E/C relationships
- **Round Timeline** — Expandable accordion with round details
- **Expert Profiles** — Per-expert contribution breakdown
- **Verdict Panel** — Final, minority, dissent verdicts
**Entity Graph:**
```
┌─────────┐
│ P0001 │◄──── support ────┐
│ Income │ │
└────┬────┘ │
│ ┌───┴────┐
support │ E0101 │
│ │Premium │
▼ └────────┘
┌─────────┐ │
│ T0001 │ depend
│ Conflict│ │
└────┬────┘ ▼
│ ┌────────┐
address │ C0101 │
│ │Resolved│
▼ └───┬────┘
┌─────────┐ │
│ R0001 │◄──── resolve ────┘
│ Options │
└─────────┘
```
**Data Source:**
- `get_dialogue()`, `get_perspectives()`, `get_tensions()`, etc.
- `expand_citation()` for hover tooltips
#### 3. Analytics Dashboard (`/analytics`)
Cross-dialogue patterns and trends.
**Components:**
- **Stats Overview** — Total dialogues, perspectives, tensions, avg ALIGNMENT
- **Top Experts** — Leaderboard across all dialogues
- **Tension Patterns** — Common tension labels/themes
- **Dialogue Comparison** — Side-by-side metrics
- **Search** — Find dialogues by topic
**Data Source:** `get_cross_dialogue_stats()`, `find_similar_dialogues()`
**Wireframe:**
```
┌────────────────────────────────────────────────────────────┐
│ 📈 Analytics Dashboard │
├────────────────────────────────────────────────────────────┤
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ 12 │ │ 156 │ │ 34 │ │ 78 │ │
│ │Dialogues│ │Perspect│ │Tensions│ │Avg ALIGN│ │
│ └────────┘ └────────┘ └────────┘ └────────┘ │
├────────────────────────────────────────────────────────────┤
│ Top Experts (All Time) │ Recent Dialogues │
│ 1. muffin 456 pts (12 dlg) │ • Investment Analysis ✓ │
│ 2. donut 389 pts (11 dlg) │ • API Design ✓ │
│ 3. cupcake 312 pts (10 dlg) │ • Risk Assessment 🔄 │
│ 4. brioche 287 pts (9 dlg) │ • Product Strategy ✓ │
├────────────────────────────────────────────────────────────┤
│ [Search dialogues...] │
│ Related: "investment" → 3 matches │
└────────────────────────────────────────────────────────────┘
```
### API Routes
| Route | Method | Description | Data Source |
|-------|--------|-------------|-------------|
| `/api/dialogues` | GET | List all dialogues | `get_dialogues()` |
| `/api/dialogues/:id` | GET | Full dialogue with entities | `get_dialogue()` + entities |
| `/api/dialogues/:id/progress` | GET | Real-time progress | `get_dialogue_progress()` |
| `/api/dialogues/:id/graph` | GET | Entity relationship graph | entities + refs |
| `/api/dialogues/:id/stream` | SSE | Live updates | poll `get_dialogue_progress()` |
| `/api/stats` | GET | Cross-dialogue stats | `get_cross_dialogue_stats()` |
| `/api/search` | GET | Find similar dialogues | `find_similar_dialogues()` |
| `/api/citation/:id` | GET | Expand citation | `expand_citation()` |
### Database Access
Dual-mode storage matching RFC 0053 (Storage Abstraction Layer):
**Local Development: SQLite**
- Next.js API routes use `better-sqlite3`
- Read-only access to Blue's SQLite database
- Simple, fast, zero infrastructure
**Production: DynamoDB**
- Uses RFC 0053 storage abstraction
- Encrypted at rest (AWS KMS)
- Same query interface, different backend
- Authentication required
```typescript
// lib/db.ts
import { createStore } from '@blue/storage';
const store = createStore({
backend: process.env.NODE_ENV === 'production' ? 'dynamodb' : 'sqlite',
sqlite: { path: process.env.BLUE_DB_PATH },
dynamodb: { table: process.env.DYNAMODB_TABLE, region: 'us-east-1' }
});
export const getDialogue = (id: string) => store.dialogue.get(id);
export const getProgress = (id: string) => store.dialogue.progress(id);
// ... etc
```
### Real-Time Updates (WebSocket)
WebSocket for live monitoring, with environment-aware implementation:
**Local: Next.js WebSocket (via socket.io or ws)**
```typescript
// pages/api/ws/dialogues/[id].ts (using next-ws or similar)
import { WebSocketServer } from 'ws';
import { store } from '@/lib/db';
export default function handler(ws: WebSocket, req: Request) {
const dialogueId = req.params.id;
// Subscribe to dialogue updates
const interval = setInterval(async () => {
const progress = await store.dialogue.progress(dialogueId);
ws.send(JSON.stringify({ type: 'progress', data: progress }));
}, 1000);
ws.on('close', () => clearInterval(interval));
}
```
**Production: API Gateway WebSocket**
```typescript
// Lambda handler for API Gateway WebSocket
export const handler = async (event: APIGatewayWebSocketEvent) => {
const { routeKey, connectionId, body } = event;
switch (routeKey) {
case '$connect':
// Store connectionId in DynamoDB connections table
await store.connections.add(connectionId);
break;
case 'subscribe':
const { dialogueId } = JSON.parse(body);
await store.subscriptions.add(connectionId, dialogueId);
break;
case '$disconnect':
await store.connections.remove(connectionId);
break;
}
return { statusCode: 200 };
};
// Separate Lambda triggered by DynamoDB Streams or EventBridge
export const broadcastProgress = async (dialogueId: string) => {
const subscribers = await store.subscriptions.forDialogue(dialogueId);
const progress = await store.dialogue.progress(dialogueId);
for (const connectionId of subscribers) {
await apiGateway.postToConnection({
ConnectionId: connectionId,
Data: JSON.stringify({ type: 'progress', data: progress })
});
}
};
```
**Client Hook:**
```typescript
// hooks/useDialogueProgress.ts
export function useDialogueProgress(dialogueId: string) {
const [progress, setProgress] = useState<DialogueProgress | null>(null);
useEffect(() => {
const wsUrl = process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:3000/api/ws';
const ws = new WebSocket(`${wsUrl}/dialogues/${dialogueId}`);
ws.onmessage = (event) => {
const { type, data } = JSON.parse(event.data);
if (type === 'progress') setProgress(data);
};
return () => ws.close();
}, [dialogueId]);
return progress;
}
```
### Entity Graph Visualization
Using React Flow for interactive graphs:
```typescript
const entityToNode = (entity: Entity): Node => ({
id: entity.display_id,
type: entity.type, // perspective | tension | recommendation | evidence | claim
data: {
label: entity.label,
contributors: entity.contributors,
status: entity.status
},
position: calculatePosition(entity), // Force-directed or hierarchical
});
const refToEdge = (ref: Ref): Edge => ({
id: `${ref.source_id}-${ref.target_id}`,
source: ref.source_id,
target: ref.target_id,
label: ref.ref_type, // support, oppose, resolve, etc.
animated: ref.ref_type === 'resolve',
style: getEdgeStyle(ref.ref_type),
});
```
## Implementation Plan
### Phase 1: Foundation
- [ ] Initialize Next.js project with Tailwind
- [ ] Set up SQLite connection (read-only)
- [ ] Implement core API routes (`/dialogues`, `/stats`)
- [ ] Create basic layout and navigation
### Phase 2: Analytics Dashboard
- [ ] Stats overview cards
- [ ] Top experts leaderboard
- [ ] Dialogue list with search
- [ ] Basic filtering
### Phase 3: Dialogue Explorer
- [ ] Dialogue detail page
- [ ] Round timeline accordion
- [ ] Expert profiles
- [ ] Verdict display
### Phase 4: Entity Graph
- [ ] React Flow integration
- [ ] Entity nodes by type
- [ ] Reference edges with labels
- [ ] Hover tooltips via `expand_citation()`
- [ ] Click to expand/focus
### Phase 5: Live Monitor
- [ ] WebSocket endpoint for progress
- [ ] Velocity chart (Recharts)
- [ ] Live leaderboard
- [ ] Convergence indicator
- [ ] Activity feed
### Phase 6: Polish
- [ ] Responsive design
- [ ] Dark mode
- [ ] Export to PNG/PDF
- [ ] Shareable links
### Phase 7: AWS Deployment
- [ ] Implement `DynamoDialogueStore` (RFC 0053)
- [ ] API Gateway WebSocket API for real-time
- [ ] Lambda functions for REST endpoints
- [ ] CloudFront distribution
- [ ] Cognito authentication
- [ ] KMS encryption for DynamoDB
- [ ] CDK/Terraform infrastructure as code
## File Structure
```
blue-viz/
├── app/
│ ├── layout.tsx
│ ├── page.tsx # Home → redirect to /analytics
│ ├── analytics/
│ │ └── page.tsx # Cross-dialogue dashboard
│ ├── dialogue/
│ │ └── [id]/
│ │ └── page.tsx # Post-hoc explorer
│ ├── live/
│ │ └── [id]/
│ │ └── page.tsx # Real-time monitor
│ └── api/
│ ├── dialogues/
│ │ ├── route.ts # GET list
│ │ └── [id]/
│ │ ├── route.ts # GET detail
│ │ ├── progress/route.ts
│ │ └── graph/route.ts
│ ├── ws/
│ │ └── dialogues/[id].ts # WebSocket endpoint
│ ├── stats/route.ts
│ ├── search/route.ts
│ └── citation/[id]/route.ts
├── components/
│ ├── VelocityChart.tsx
│ ├── Leaderboard.tsx
│ ├── TensionTracker.tsx
│ ├── EntityGraph.tsx
│ ├── RoundTimeline.tsx
│ ├── VerdictPanel.tsx
│ └── CitationTooltip.tsx
├── hooks/
│ ├── useDialogueProgress.ts # WebSocket hook
│ └── useDialogue.ts # Data fetching
├── lib/
│ ├── store.ts # Storage abstraction (RFC 0053)
│ └── types.ts # Shared types
└── package.json
```
## AWS Infrastructure (Production)
```
┌─────────────────────────────────────────────────────────────────────┐
│ CloudFront │
│ (CDN + HTTPS termination) │
└─────────────────────────────────────────────────────────────────────┘
│ │
Static Assets API Requests
│ │
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────────────────┐
│ S3 Bucket │ │ API Gateway │
│ (Next.js static) │ │ ┌─────────────┬─────────────────┐ │
└─────────────────────────┘ │ │ REST API │ WebSocket API │ │
│ │ /dialogues │ $connect │ │
│ │ /stats │ subscribe │ │
│ │ /search │ $disconnect │ │
│ └─────────────┴─────────────────┘ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Lambda Functions │
│ - dialogue-get │
│ - dialogue-list │
│ - progress-get │
│ - ws-connect │
│ - ws-subscribe │
│ - ws-broadcast │
└─────────────────────────────────────┘
┌─────────────────────────┐ ┌─────────────────────────────────────┐
│ Cognito │ │ DynamoDB │
│ (Authentication) │ │ - blue_dialogues (main table) │
│ - User Pool │ │ - blue_connections (WebSocket) │
│ - Identity Pool │ │ - Encrypted with KMS │
└─────────────────────────┘ └─────────────────────────────────────┘
```
**DynamoDB Table Design (Single-Table):**
| PK | SK | Attributes |
|----|-----|------------|
| `DLG#investment-analysis` | `META` | title, status, total_alignment, ... |
| `DLG#investment-analysis` | `EXPERT#muffin` | role, tier, scores, ... |
| `DLG#investment-analysis` | `ROUND#00` | score, summary, ... |
| `DLG#investment-analysis` | `P#0001` | label, content, contributors, ... |
| `DLG#investment-analysis` | `T#0001` | label, status, ... |
| `DLG#investment-analysis` | `REF#P0001#T0001` | ref_type, ... |
| `WS#abc123` | `SUB#investment-analysis` | connectionId, subscribedAt |
**GSI for queries:**
- `GSI1`: `status` → list dialogues by status
- `GSI2`: `expert_slug` → find all dialogues for an expert
## Decisions
1. **Deployment**: Local for development/testing, hosted for production
- Local: SQLite directly
- Hosted: DynamoDB with encryption (see RFC 0053 Storage Abstraction)
2. **Authentication**: None locally; required for hosted (TBD)
3. **Write Operations**: Read-only for v1
4. **Embedding**: Deferred — consider later for sharing widgets in Notion, Slack, etc.
## Success Criteria
- [ ] Can monitor an active dialogue in real-time
- [ ] Can explore entity relationships visually
- [ ] Can compare multiple dialogues side-by-side
- [ ] Loads in under 2 seconds
- [ ] Works on mobile (responsive)
---
*"The elephant becomes visible — now let's draw it."*