# Project export: VerdictX – Institutional AI Investment Committee

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: An AI investment committee that researches, debates, and delivers transparent, evidence-backed investment decisions.
- Devpost: https://devpost.com/software/verdictx-institutional-ai-investment-committee
- GitHub: https://github.com/raj-k21/VerdictX
- Video: https://player.vimeo.com/video/1211810336?byline=0&portrait=0&title=0#t=
- Team: 1 GitHub contributor(s) — raj-k21 (7 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

# VerdictX – Institutional AI Investment Committee

VerdictX is a real-time, multi-agent AI investment committee that researches, debates, and delivers transparent, evidence-backed investment decisions.

Unlike traditional AI assistants that return a single response, VerdictX simulates how institutional investment firms operate. Multiple specialized AI analysts independently research a company, challenge each other's assumptions, update their confidence throughout a live committee discussion, and ultimately produce an institutional-quality investment memo.

Built for the OpenAI Hackathon 2026.

---

## Why VerdictX?

Financial decisions shouldn't rely on one AI generating one answer.

Professional investment firms use committees because diverse perspectives produce stronger decisions.

VerdictX recreates that process with AI.

Every investment request launches six specialized committee members:

- Bull Analyst
- Bear Analyst
- Quantitative Analyst
- Macro Strategist
- Risk Officer
- Chairperson

Each specialist researches independently before entering a moderated committee discussion where they:

- Present evidence
- Challenge other specialists
- Agree or disagree with previous arguments
- Update confidence in real time
- Vote independently

The Chairperson then synthesizes the discussion into a transparent investment recommendation.

---

# Features

- Real-time institutional investment committee
- Multi-agent AI architecture
- Live research streaming
- Turn-by-turn moderated debate
- Dynamic confidence updates
- Institutional investment memo generation
- Financial metrics dashboard
- Evidence-backed reasoning
- Consensus visualization
- Live debate transcript
- Automatic provider failover
- Circuit breaker protection
- Event-driven backend
- Real-time SSE streaming
- Production-ready architecture

---

# Architecture

```
User
   │
   ▼
Research Engine
   │
   ▼
Financial Data Collection
   │
   ▼
Evidence Packet
   │
   ▼
────────────────────────────────────
Bull Analyst
Bear Analyst
Quant Analyst
Macro Strategist
Risk Officer
────────────────────────────────────
   │
   ▼
Chairperson Moderated Debate
   │
   ▼
Confidence Updates
   │
   ▼
Committee Voting
   │
   ▼
Institutional Memo
```

---

# Tech Stack

## Frontend

- Next.js
- React
- TypeScript
- Tailwind CSS
- Framer Motion

## Backend

- FastAPI
- Python
- AsyncIO
- Server-Sent Events (SSE)

## AI

- OpenAI GPT-5
- NVIDIA NIM
- OpenRouter (Fallback)
- Multi-Agent Prompt Architecture

## Infrastructure

- Vercel
- GitHub
- Event-Driven Architecture
- Circuit Breakers
- Automatic Provider Failover

---

# How It Works

## 1. Research

VerdictX collects financial information from trusted public sources and builds a structured evidence packet.

Sources include:

- SEC EDGAR
- Yahoo Finance
- Financial statements
- Company fundamentals

---

## 2. Independent Analysis

Each specialist receives the evidence packet and independently develops their own thesis.

No analyst simply copies another.

Every role has its own objectives and reasoning framework.

---

## 3. Committee Debate

The Chairperson moderates a real committee session.

Specialists:

- defend their opinions
- challenge opposing views
- reference previous arguments
- change confidence when persuaded
- debate until consensus emerges

Every exchange streams live.

---

## 4. Consensus

Votes appear individually.

Confidence changes in real time.

Users can watch the committee reach consensus rather than only seeing the final answer.

---

## 5. Investment Memo

The Chairperson generates an institutional-quality memo containing:

- Recommendation
- Confidence
- Investment Thesis
- Supporting Evidence
- Risks
- Monitoring Triggers

---

# Real-Time Streaming

VerdictX is fully event-driven.

Instead of waiting for analysis to complete, every stage streams immediately.

Pipeline:

```
Research
↓

Specialist Analysis
↓

Committee Opening
↓

Live Debate
↓

Confidence Updates
↓

Voting
↓

Memo Generation
↓

Completed Report
```

---

# Reliability

VerdictX includes production-grade resilience.

- Automatic provider failover
- Circuit breaker protection
- Retry with exponential backoff
- Provider health monitoring
- Session persistence
- Graceful failure handling

If one AI provider becomes unavailable, VerdictX automatically switches providers without interrupting the committee.

---

# What Makes VerdictX Different?

Most AI finance tools:

- One prompt
- One model
- One answer

VerdictX:

- Multiple specialists
- Independent reasoning
- Live committee debate
- Transparent decision making
- Evidence-backed conclusions
- Institutional workflow

It doesn't simply answer.

It explains how the decision was reached.

---

# Challenges

Building VerdictX required solving several difficult engineering problems:

- Designing a real-time streaming architecture
- Coordinating multiple AI specialists
- Synchronizing live frontend updates
- Eliminating fake loading states
- Building provider failover
- Maintaining session consistency
- Streaming debates in chronological order
- Generating institutional-quality memos

---

# What I Learned

Building VerdictX taught me that production AI applications require much more than prompt engineering.

Reliable AI systems depend on:

- streaming architectures
- fault tolerance
- state management
- resilient infrastructure
- transparent reasoning

The biggest lesson was that users trust AI more when they can observe the reasoning process instead of simply receiving a final answer.

---

# Future Roadmap

- Portfolio-level committee analysis
- Earnings call analysis
- SEC filing monitoring
- Multi-company comparison
- Historical backtesting
- Watchlists
- Team collaboration
- Institutional workspaces
- Portfolio optimization

---

# Local Setup

Clone the repository:

```bash
git clone https://github.com/YOUR_USERNAME/verdictx.git
cd verdictx
```

Install frontend dependencies:

```bash
npm install
```

Install backend dependencies:

```bash
pip install -r requirements.txt
```

Create a `.env` file:

```env
NVIDIA_API_KEY=your_key
OPENROUTER_API_KEY=your_key
CHAIRPERSON_API_KEY=your_key

ENABLE_FAILOVER=true
ENABLE_RETRIES=true
```

Run the backend:

```bash
uvicorn backend.main:app --reload
```

Run the frontend:

```bash
npm run dev
```

Open:

```
http://localhost:3000
```

---

# Project Structure

```
app/
backend/
components/
lib/
public/
prompts/
schemas/
providers/

README.md
package.json
requirements.txt
```

---

# Built With

- OpenAI
- GPT-5
- FastAPI
- Next.js
- React
- TypeScript
- Python
- Tailwind CSS
- Framer Motion
- Server-Sent Events
- AsyncIO
- NVIDIA NIM
- OpenRouter
- Vercel

---

# AI Development Workflow

VerdictX was developed using GPT-5.6 and Codex as collaborative engineering tools throughout the project lifecycle.

## GPT-5.6

GPT-5.6 was used as the primary design and architecture partner. It assisted with:

- Designing the multi-agent institutional investment committee architecture
- Iterating on system design, backend architecture, and frontend UX
- Refining specialist prompts and debate workflows
- Planning the real-time streaming experience
- Reviewing implementation decisions and identifying architectural improvements
- Debugging complex backend, frontend, and event-driven issues
- Writing technical documentation, README content, and project descriptions
- Improving production readiness and deployment strategy

Rather than generating isolated snippets of code, GPT-5.6 was used throughout the project to iteratively design, review, and improve the system from initial concept to final implementation.

## Codex

Codex accelerated implementation by performing repository-aware engineering tasks, including:

- Refactoring large sections of the frontend and backend
- Building and modifying React, Next.js, and FastAPI components
- Implementing the real-time SSE event pipeline
- Updating multi-agent orchestration logic
- Refactoring schemas and event models
- Implementing provider fail

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 146 recognized source files, 944 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Vercel (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (120 of 154)

```
.gitignore
app/dashboard-legacy/page.tsx
app/dashboard-v4/page.tsx
app/dashboard/page.tsx
app/globals.css
app/layout.tsx
app/page.tsx
backend/__init__.py
backend/api/__init__.py
backend/api/chat.py
backend/api/committee.py
backend/api/dependencies.py
backend/api/providers.py
backend/committee/__init__.py
backend/committee/event_service.py
backend/committee/prompts.py
backend/committee/service.py
backend/committee/session_store.py
backend/committee/streaming_service.py
backend/config/__init__.py
backend/config/settings.py
backend/main.py
backend/memo/__init__.py
backend/memo/service.py
backend/prompts/bear.md
backend/prompts/bull.md
backend/prompts/chairperson_summary.md
backend/prompts/chairperson.md
backend/prompts/confidence_update_single.md
backend/prompts/confidence.md
backend/prompts/cross_exam.md
backend/prompts/macro.md
backend/prompts/memo.md
backend/prompts/moderator_challenge.md
backend/prompts/quant.md
backend/prompts/risk.md
backend/prompts/specialist_rebuttal.md
backend/providers/__init__.py
backend/providers/base_provider.py
backend/providers/bluesmind_provider.py
backend/providers/nvidia_provider.py
backend/providers/openai_compatible.py
backend/providers/provider_factory.py
backend/providers/provider_manager.py
backend/requirements.txt
backend/research/__init__.py
backend/research/event_emitter.py
backend/research/formatters.py
backend/research/service.py
backend/schemas/__init__.py
backend/schemas/chat.py
backend/schemas/committee.py
backend/schemas/events.py
backend/schemas/research.py
backend/utils/__init__.py
backend/utils/errors.py
backend/utils/logging.py
backend/utils/timing.py
components/dashboard-v4/AgentWorkspaces.tsx
components/dashboard-v4/AgreementNetwork.tsx
components/dashboard-v4/AnalysisLauncher.tsx
components/dashboard-v4/ArgumentBoard.tsx
components/dashboard-v4/ChairpersonSummary.tsx
components/dashboard-v4/ChallengeCommand.tsx
components/dashboard-v4/ChatDock.tsx
components/dashboard-v4/CommitteeConstellation.tsx
components/dashboard-v4/CommitteeSession.tsx
components/dashboard-v4/ConsensusBuilder.tsx
components/dashboard-v4/ConsensusFormation.tsx
components/dashboard-v4/ConsensusMeter.tsx
components/dashboard-v4/ControlStrip.tsx
components/dashboard-v4/DebateLogsOverlay.tsx
components/dashboard-v4/DebateTranscript.tsx
components/dashboard-v4/DecisionDock.tsx
components/dashboard-v4/DeliberationTimeline.tsx
components/dashboard-v4/EvidenceIntelligenceFeed.tsx
components/dashboard-v4/FinancialKPIGrid.tsx
components/dashboard-v4/FinancialMetricsPage.tsx
components/dashboard-v4/IndividualAgentReports.tsx
components/dashboard-v4/InstitutionalMemo.tsx
components/dashboard-v4/LiveDebate.tsx
components/dashboard-v4/MemoStream.tsx
components/dashboard-v4/MemoWorkspace.tsx
components/dashboard-v4/ResearchNavigator.tsx
components/dashboard-v4/ResearchWorkspace.tsx
components/dashboard-v4/SessionArchive.tsx
components/dashboard-v4/Sidebar.tsx
components/dashboard-v4/SimplifiedMemo.tsx
components/dashboard-v4/SpeakerCard.tsx
components/dashboard-v4/VoteReveal.tsx
components/dashboard/architecture-modal.tsx
components/dashboard/chairperson-panel.tsx
components/dashboard/challenge-panel.tsx
components/dashboard/chat-panel.tsx
components/dashboard/committee-panel.tsx
components/dashboard/evidence-panel.tsx
components/dashboard/financial-charts.tsx
components/dashboard/floating-dock.tsx
components/dashboard/history-list.tsx
components/dashboard/insights-panel.tsx
components/dashboard/memo-panel.tsx
components/dashboard/panel.tsx
components/dashboard/pipeline-orbit.tsx
components/dashboard/progress-stepper.tsx
components/dashboard/search-experience.tsx
components/dashboard/session-timeline.tsx
components/dashboard/sidebar.tsx
components/dashboard/top-bar.tsx
components/landing/landing-page.tsx
components/landing/v2/ArchitectureJourney.tsx
components/landing/v2/CinematicHero.tsx
components/landing/v2/CommitteeSection.tsx
components/landing/v2/FinalCTA.tsx
components/landing/v2/index.tsx
components/landing/v2/LandingReveal.tsx
components/landing/v2/LiveDemoStrip.tsx
components/landing/v2/Navigation.tsx
components/landing/v2/TechnologySection.tsx
components/landing/v2/ThinkingTimeline.tsx
components/landing/v2/WhyVerdictX.tsx
[34 more files omitted for size]
```

### Dependencies

- backend/requirements.txt: fastapi@==0.111.0, httpx@==0.27.0, openai@==1.93.0, pydantic-settings@==2.3.4, python-dotenv@==1.0.1, uvicorn[standard]@==0.30.1
- package.json: @react-three/drei@^9.88.11, @react-three/fiber@^8.15.12, @types/node@^20.14.10, @types/react@^18.3.3, @types/react-dom@^18.3.0, @types/three@^0.162.0, autoprefixer@^10.4.19, eslint@^8.57.0, eslint-config-next@^14.2.5, framer-motion@^11.3.8, jspdf@^4.2.1, leva@^0.10.1, lucide-react@^0.468.0, next@^14.2.5, postcss@^8.4.39, react@^18.3.1, react-dom@^18.3.1, react-markdown@^10.1.0, recharts@^2.15.0, remark-gfm@^4.0.1, tailwindcss@^3.4.4, three@^0.162.0, typescript@^5.5.3

### Recent commits (newest first)

- Update README.md
- Create vercel.json
- Create .gitignore
- Create README.md
- Add files via upload
- Add files via upload
- Add files via upload
- Add files via upload
- Add files via upload

## Key source files (fetched from GitHub, selected and truncated for size)

### backend/prompts/cross_exam.md

```markdown
You are the Chairperson conducting the cross-examination round of an institutional investment committee.
You have the structured Evidence Packet and every specialist opinion.

For EACH committee member (Bull Analyst, Bear Analyst, Macro Strategist, Risk Officer, Quant Analyst),
review their thesis against the strongest evidence and the counterarguments from other members.

GROUNDING:
- Use ONLY the Evidence Packet and the provided opinions. Do not invent facts.
- If evidence is insufficient, note "Insufficient evidence." rather than guessing.

For each member return an object with:
- role
- stance: one of "challenge", "agree", "modify"
- target_arguments: the specific claims being addressed
- revised_reasoning: how the member's reasoning should be refined in light of the debate

Return only JSON matching the requested schema.
```

### backend/prompts/memo.md

```markdown
You are the Chairperson writing the executive investment memo for an institutional investment committee.
Use the Evidence Packet, the specialist opinions, the cross-examination, the confidence updates, and the final vote.

GROUNDING:
- Use ONLY the provided materials. Never invent financial numbers. If a figure is unavailable, write "Insufficient evidence."
- Be professional, concise, source-aware, and suitable for decision review.

Produce a JSON object with these exact fields:
- executive_summary
- company_overview
- bull_case
- bear_case
- macro_view
- risk_assessment
- quantitative_analysis
- committee_discussion_summary
- final_recommendation
- key_risks: list of the most material risks
- key_opportunities: list of the most material upside opportunities
- sources: the provided research sources

Return only JSON matching the requested schema.
```

### package.json

```
{
  "name": "verdictx",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@react-three/drei": "^9.88.11",
    "@react-three/fiber": "^8.15.12",
    "@types/node": "^20.14.10",
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.0",
    "@types/three": "^0.162.0",
    "framer-motion": "^11.3.8",
    "jspdf": "^4.2.1",
    "leva": "^0.10.1",
    "lucide-react": "^0.468.0",
    "next": "^14.2.5",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-markdown": "^10.1.0",
    "recharts": "^2.15.0",
    "remark-gfm": "^4.0.1",
    "three": "^0.162.0",
    "typescript": "^5.5.3"
  },
  "devDependencies": {
    "autoprefixer": "^10.4.19",
    "eslint": "^8.57.0",
    "eslint-config-next": "^14.2.5",
    "postcss": "^8.4.39",
    "tailwindcss": "^3.4.4"
  }
}

```

### backend/requirements.txt

```
fastapi==0.111.0
uvicorn[standard]==0.30.1
httpx==0.27.0
openai==1.93.0
pydantic-settings==2.3.4
python-dotenv==1.0.1

```

### app/page.tsx

```typescript
import { LandingPageV2 } from "@/components/landing/v2";

export default function Home() {
  return <LandingPageV2 />;
}

```

### app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Manrope, Instrument_Serif, IBM_Plex_Mono } from "next/font/google";
import "./globals.css";

const manrope = Manrope({
  subsets: ["latin"],
  variable: "--font-manrope",
  display: "swap",
});

const instrumentSerif = Instrument_Serif({
  subsets: ["latin"],
  weight: "400",
  variable: "--font-instrument-serif",
  display: "swap",
});

const ibmPlexMono = IBM_Plex_Mono({
  subsets: ["latin"],
  weight: ["400", "500"],
  variable: "--font-ibm-plex-mono",
  display: "swap",
});

export const metadata: Metadata = {
  title: "VerdictX | Institutional AI Investment Analysis",
  description: "AI-powered investment committee analysis for public companies.",
};

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en" className={`${manrope.variable} ${instrumentSerif.variable} ${ibmPlexMono.variable}`}>
      <body className="bg-vx-bg antialiased">
        {children}
      </body>
    </html>
  );
}

```

### backend/main.py

```python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from backend.api.chat import router as chat_router
from backend.api.committee import router as committee_router
from backend.api.providers import router as providers_router
from backend.config.settings import get_settings
from backend.utils.logging import configure_logging, get_logger

configure_logging()
settings = get_settings()
logger = get_logger(__name__)

# ── Startup validation ──────────────────────────────────────────────────────
# Run configuration validation and log any errors. This catches mistakes
# like missing API keys, malformed JSON, invalid URLs, and duplicate providers
# BEFORE any requests are processed.
startup_errors = settings.validate_startup()
if startup_errors:
    logger.error(
        "startup_validation_failed",
        extra={
            "extra": {
                "error_count": len(startup_errors),
                "errors": startup_errors,
                "fix_hint": "Fix these in your .env file. See .env.example for reference.",
            }
        },
    )
    # Log each error individually for easy reading
    for i, err in enumerate(startup_errors, 1):
        logger.error(
            f"config_error_{i}",
            extra={"extra": {"error": err}},
        )
else:
    logger.info("startup_validation_passed")

logger.info(
    "VerdictX providers loaded",
    extra={
        "extra": {
            "specialist_provider": settings.LLM_PROVIDER,
            "specialist_model": settings.LLM_MODEL,
            "chairperson_provider": settings.CHAIRPERSON_PROVIDER,
            "chairperson_configured": bool(settings.CHAIRPERSON_API_KEY and settings.CHAIRPERSON_BASE_URL),
            "failover_enabled": settings.ENABLE_FAILOVER,
            "max_retries": settings.MAX_RETRIES,
            "circuit_breaker_threshold": settings.CIRCUIT_BREAKER_THRESHOLD,
            "fallback_providers_count": len(settings.parse_fallback_providers()),
            "chairperson_fallback_count": len(settings.parse_chairperson_fallback_providers()),
        }
    },
)

app = FastAPI(title="VerdictX API", version="1.0.0")

app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.cors_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(committee_router)
app.include_router(chat_router)
app.include_router(providers_router)


@app.get("/health")
async def health() -> dict[str, str]:
    return {"status": "ok", "service": "verdictx-api"}

```

### app/dashboard-v4/page.tsx

```typescript
import { redirect } from "next/navigation";

export default function DashboardV4Redirect() {
  redirect("/dashboard");
}

```

### app/dashboard-legacy/page.tsx

```typescript
import { redirect } from "next/navigation";

export default function DashboardLegacyRedirect() {
  redirect("/dashboard");
}

```

### app/dashboard/page.tsx

```typescript
"use client";

import { useCallback, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import {
  AlertTriangle, RefreshCw, Clock, AlertCircle,
} from "lucide-react";
import { useCommitteeStream } from "@/lib/hooks/useCommitteeStream";
import type { SidebarSection } from "@/components/dashboard-v4/Sidebar";
import { Sidebar } from "@/components/dashboard-v4/Sidebar";
import { ControlStrip } from "@/components/dashboard-v4/ControlStrip";
import { AnalysisLauncher } from "@/components/dashboard-v4/AnalysisLauncher";
import { ResearchWorkspace } from "@/components/dashboard-v4/ResearchWorkspace";
import { AgentWorkspaces } from "@/components/dashboard-v4/AgentWorkspaces";
import { CommitteeSession } from "@/components/dashboard-v4/CommitteeSession";
import { MemoStream } from "@/components/dashboard-v4/MemoStream";
import { DecisionDock } from "@/components/dashboard-v4/DecisionDock";
import { MemoWorkspace } from "@/components/dashboard-v4/MemoWorkspace";
import { ChatDock } from "@/components/dashboard-v4/ChatDock";
import { ResearchNavigator } from "@/components/dashboard-v4/ResearchNavigator";
import { SessionArchive } from "@/components/dashboard-v4/SessionArchive";
import { ChallengeCommand } from "@/components/dashboard-v4/ChallengeCommand";
import { InstitutionalMemo } from "@/components/dashboard-v4/InstitutionalMemo";
import { DebateLogsOverlay } from "@/components/dashboard-v4/DebateLogsOverlay";
import { useBackendHealth } from "@/lib/hooks/useBackendHealth";

const STAGE_LABELS: Record<string, string> = {
  idle: "Ready",
  research: "Research Phase",
  agent: "Agent Analysis",
  debate: "Committee Debate",
  consensus: "Consensus Formation",
  memo: "Memo Generation",
  complete: "Analysis Complete",
  error: "Error",
};

export default function DashboardPage() {
  // ── Single unified pipeline: SSE-driven, no dual pipeline, no fake timers ──
  const {
    state: pipeline,
    result,
    error,
    stageError,
    watchdog,
    providerNotification,
    history,
    startAnalysis,
    reset,
    challengeResult,
    selectHistory,
    clearHistory,
  } = useCommitteeStream();

  const backendOk = useBackendHealth();

  // ── UI state ──
  const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
  const [activeSection, setActiveSection] = useState<SidebarSection>("decision-room");
  const [showDebateLogs, setShowDebateLogs] = useState(false);
  const [chatInitialMessage, setChatInitialMessage] = useState<string | undefined>(undefined);
  const [chatInitialSpecialist, setChatInitialSpecialist] = useState<string | undefined>(undefined);
  const [overlay, setOverlay] = useState<string | null>(null);
  const [query, setQuery] = useState("");

  const stage = pipeline.stage;
  const isRunning = stage !== "idle" && stage !== "complete" && stage !== "error";
  const isComplete = stage === "complete" && !!result;

  // Open chat with optional initial message and specialist
  const handleOpenChat = useCallback((message?: string, specialist?: string) => {
    setChatInitialMessage(message);
    setChatInitialSpecialist(specialist);
    setOverlay("chat");
  }, []);

  const handleNavigate = useCallback((section: SidebarSection) => {
    setActiveSection(section);
    if (section === "previous-sessions" && result) {
      setOverlay("archive");
    } else if (section === "saved-memos" && result) {
      setOverlay("memo");
    }
  }, [result]);

  const handleOpenOverlay = useCallback((name: string) => {
    setOverlay(name);
  }, []);

  const handleRunAnalysis = useCallback((company: string) => {
    setQuery(company);
    // Single call — SSE drives everything. No dual pipeline.
    startAnalysis(company);
  }, [startAnalysis]);

  const handleReset = useCallback(() => {
    reset();
  }, [reset]);

  const handleRetryFromError = useCallback(() => {
    const company = pipeline.company || query;
    reset();
    if (company) {
      // Small delay to allow state reset to propagate
      setTimeout(() => startAnalysis(company), 100);
    }
  }, [reset, startAnalysis, pipeline.company, query]);

  const sidebarWidth = sidebarCollapsed ? 64 : 220;

  // ── Render current stage content ──
  const renderStageContent = () => {
    if (stage === "idle") {
      return (
        <motion.div
          key="idle"
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0, y: -20 }}
          transition={{ duration: 0.4 }}
        >
          <AnalysisLauncher
            query={query}
            onQueryChange={setQuery}
            onRun={handleRunAnalysis}
          />
        </motion.div>
      );
    }

    // ── Watchdog: stalled indicator ──
    if (isRunning && watchdog.level === "stalled") {
      return (
        <motion.div
          key="stalled"
          initial={{ opacity: 0, y: 10 }}
          animate={{ opacity: 1, y: 0 }}
          className="flex items-center justify-center min-h-[70vh] px-4"
        >
          <div className="v4-glass-deep rounded-2xl p-6 max-w-md w-full">
            <div className="flex items-start gap-4">
              <div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl border border-amber-400/20 bg-amber-500/8">
                <AlertCircle className="h-6 w-6 text-amber-300" />
              </div>
              <div className="flex-1">
                <p className="text-sm font-semibold text-amber-200">Committee Appears Stalled</p>
                <p className="mt-1 text-[12px] text-amber-200/60 leading-5">
                  No events received for over 60 seconds. The pipeline may be stuck.
                </p>
                <div className="mt-4 flex gap-2">
                  <button
                    onClick={handleRetryFromError}
                    className="v4-dock-btn !bg-emerald-500/15 !border-emerald-400/20 !text-emerald-300"
                  >
                    <RefreshCw className="h-3 w-3" /> Retry
                  </button>
   
[truncated — 13047 more characters]
```

[136 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]