Files
Atomizer/docs/plans/ATOMIZER_DASHBOARD_V2_MASTER_PLAN.md
Anto01 ac5e9b4054 docs: Comprehensive documentation update for Dashboard V3 and Canvas
## Documentation Updates
- DASHBOARD.md: Updated to V3.0 with Canvas V3 features, file browser, introspection
- DASHBOARD_IMPLEMENTATION_STATUS.md: Marked Canvas V3 features as COMPLETE
- CANVAS.md: New comprehensive guide for Canvas Builder V3 with all features
- CLAUDE.md: Added dashboard quick reference and Canvas V3 features

## Canvas V3 Features Documented
- File Browser: Browse studies directory for model files
- Model Introspection: Auto-discover expressions, solver type, dependencies
- One-Click Add: Add expressions as design variables instantly
- Claude Bug Fixes: WebSocket reconnection, SQL errors resolved
- Health Check: /api/health endpoint for monitoring

## Backend Services
- NX introspection service with expression discovery
- File browser API with type filtering
- Claude session management improvements
- Context builder enhancements

## Frontend Components
- FileBrowser: Modal for file selection with search
- IntrospectionPanel: View discovered model information
- ExpressionSelector: Dropdown for design variable configuration
- Improved chat hooks with reconnection logic

## Plan Documents
- Added RALPH_LOOP_CANVAS_V2/V3 implementation records
- Added ATOMIZER_DASHBOARD_V2_MASTER_PLAN
- Added investigation and sync documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 20:48:58 -05:00

98 KiB

Atomizer Dashboard V2 - Master Implementation Plan

Version: 1.1 Created: January 8, 2026 Updated: January 14, 2026 Purpose: Complete technical specification for Ralph Loop autonomous development Scope: MCP Chat + Canvas + React Flow + Full Integration


IMPLEMENTATION STATUS: COMPLETE

Phase Description Status
Phase 0 MCP Chat Foundation COMPLETE
Phase 1 Canvas with React Flow COMPLETE
Phase 2 LLM Intelligence Layer COMPLETE
Phase 3 Bidirectional Sync COMPLETE
Phase 4 Templates & Polish COMPLETE
Phase 5 Tauri Desktop FUTURE

See: RALPH_LOOP_MASTER_PROMPT.md for detailed file inventory and testing checklist.


Table of Contents

  1. Executive Summary
  2. Architecture Philosophy
  3. Current State Analysis
  4. Target Architecture
  5. Phase 0: Foundation - MCP Chat
  6. Phase 1: Canvas - React Flow Integration
  7. Phase 2: LLM Intelligence Layer
  8. Phase 3: Bidirectional Sync
  9. Phase 4: Polish & Templates
  10. Phase 5: Tauri Desktop (Future)
  11. Integration with Existing Systems
  12. Data Flow Specifications
  13. Component Specifications
  14. API Specifications
  15. Security Model
  16. Testing Strategy
  17. Deployment Strategy
  18. Risk Analysis
  19. Task Breakdown
  20. Appendices

1. Executive Summary

1.1 Vision

Transform the Atomizer Dashboard from a monitoring tool into an intelligent optimization workbench where engineers interact through:

  1. Conversational Interface (Chat) - Natural language interaction with full Atomizer capabilities
  2. Visual Interface (Canvas) - Node-based workflow builder using React Flow
  3. Unified Intelligence Layer - Claude interprets both inputs using protocols, LAC, and skills

1.2 Key Principles

┌─────────────────────────────────────────────────────────────────────────────────┐
│                           CORE PRINCIPLES                                        │
├─────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  1. CLAUDE IS THE BRAIN                                                         │
│     Canvas and Chat are input modalities                                        │
│     Claude interprets, validates, executes                                      │
│     All intelligence flows through the same LLM layer                           │
│                                                                                  │
│  2. PROTOCOLS ARE THE OPERATING SYSTEM                                          │
│     OP_01, OP_02, SYS_12, etc. remain the source of truth                      │
│     Canvas doesn't hardcode - it expresses intent                               │
│     Claude follows protocols to execute                                          │
│                                                                                  │
│  3. ADDITIVE, NOT DESTRUCTIVE                                                   │
│     No existing code is broken                                                   │
│     New capabilities layer on top                                               │
│     Existing CLI/terminal workflows still work                                  │
│                                                                                  │
│  4. TWO MODES, ONE EXPERIENCE                                                   │
│     User Mode: Safe, product-like                                               │
│     Power Mode: Full Claude Code capabilities                                   │
│     Same interface, different permissions                                       │
│                                                                                  │
│  5. CONTEXT IS KING                                                             │
│     Sessions persist across navigation                                          │
│     Study context automatically loaded                                          │
│     LAC insights inform every decision                                          │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘

1.3 Success Criteria

Metric Target
Study creation via chat < 2 minutes from "create study" to running
Canvas to execution < 30 seconds from graph complete to optimization start
Session persistence 100% conversation recovery after refresh
LAC integration Every study decision informed by historical data
Zero breaking changes All existing functionality preserved

2. Architecture Philosophy

2.1 The Intelligence Stack

┌─────────────────────────────────────────────────────────────────────────────────┐
│                                                                                  │
│                          USER INTERACTION LAYER                                  │
│  ┌─────────────────────────────────┐  ┌─────────────────────────────────────┐  │
│  │           CHAT PANEL            │  │            CANVAS PANEL              │  │
│  │  ┌───────────────────────────┐  │  │  ┌─────────────────────────────────┐│  │
│  │  │ Natural Language Input    │  │  │  │ Visual Node Graph (React Flow) ││  │
│  │  │ "Create mass optimization"│  │  │  │ [Model]→[Solver]→[Extractor]   ││  │
│  │  └───────────────────────────┘  │  │  └─────────────────────────────────┘│  │
│  │  ┌───────────────────────────┐  │  │  ┌─────────────────────────────────┐│  │
│  │  │ Tool Call Visualization   │  │  │  │ Node Configuration Panels      ││  │
│  │  │ [get_trial_data ✓]        │  │  │  │ Drag-drop Palette              ││  │
│  │  └───────────────────────────┘  │  │  └─────────────────────────────────┘│  │
│  │  ┌───────────────────────────┐  │  │  ┌─────────────────────────────────┐│  │
│  │  │ Streaming Response        │  │  │  │ Action Buttons                 ││  │
│  │  │ Markdown + Code           │  │  │  │ [Validate] [Create] [Run]      ││  │
│  │  └───────────────────────────┘  │  │  └─────────────────────────────────┘│  │
│  └─────────────────────────────────┘  └─────────────────────────────────────┘  │
│                    │                                    │                       │
│                    │         Both serialize to          │                       │
│                    └──────────────┬─────────────────────┘                       │
│                                   │                                             │
│                                   ▼                                             │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │                         INTENT LAYER                                     │   │
│  │  ┌───────────────────────────────────────────────────────────────────┐  │   │
│  │  │ Unified Intent Schema                                              │  │   │
│  │  │ {                                                                  │  │   │
│  │  │   "source": "chat" | "canvas",                                    │  │   │
│  │  │   "action": "create" | "run" | "analyze" | "modify",              │  │   │
│  │  │   "intent": { ... structured description ... }                    │  │   │
│  │  │ }                                                                  │  │   │
│  │  └───────────────────────────────────────────────────────────────────┘  │   │
│  └─────────────────────────────────────────────────────────────────────────┘   │
│                                   │                                             │
│                                   ▼                                             │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │                      INTELLIGENCE LAYER (Claude)                        │   │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐       │   │
│  │  │ PROTOCOLS   │ │    LAC      │ │   SKILLS    │ │  CONTEXT    │       │   │
│  │  │ OP_01-08    │ │ Historical  │ │ Interview   │ │ Study State │       │   │
│  │  │ SYS_10-18   │ │ Insights    │ │ Creation    │ │ Session     │       │   │
│  │  │ EXT_01-04   │ │ Patterns    │ │ Modules     │ │ History     │       │   │
│  │  └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘       │   │
│  │                                                                          │   │
│  │  Claude: "I understand the intent. Based on protocols and LAC:          │   │
│  │           - This matches OP_01 (Create Study)                           │   │
│  │           - LAC says TPE works for this geometry type                   │   │
│  │           - I'll use E4 for mass, E3 for stress                        │   │
│  │           - Executing..."                                               │   │
│  │                                                                          │   │
│  └─────────────────────────────────────────────────────────────────────────┘   │
│                                   │                                             │
│                                   ▼                                             │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │                       EXECUTION LAYER                                    │   │
│  │  ┌─────────────────────────────────────────────────────────────────┐   │   │
│  │  │ Atomizer MCP Server (Tools)                                      │   │   │
│  │  │ ├─ create_study()      → StudyCreator                           │   │   │
│  │  │ ├─ run_optimization()  → OptimizationRunner                     │   │   │
│  │  │ ├─ get_trial_data()    → Database queries                       │   │   │
│  │  │ ├─ generate_report()   → ReportGenerator                        │   │   │
│  │  │ └─ ... 15+ tools                                                │   │   │
│  │  └─────────────────────────────────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────────────────────────────┘   │
│                                   │                                             │
│                                   ▼                                             │
│  ┌─────────────────────────────────────────────────────────────────────────┐   │
│  │                       ENGINE LAYER (Python)                              │   │
│  │  ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌─────────────┐ │   │
│  │  │ optimization_ │ │   extractors/ │ │      nx/      │ │  study/     │ │   │
│  │  │ engine/core/  │ │   E1-E24      │ │ solver.py     │ │ creator.py  │ │   │
│  │  │ runner.py     │ │ extract_*.py  │ │ updater.py    │ │ wizard.py   │ │   │
│  │  └───────────────┘ └───────────────┘ └───────────────┘ └─────────────┘ │   │
│  └─────────────────────────────────────────────────────────────────────────┘   │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘

2.2 Why Not Hardcode?

Traditional approach would be:

Canvas Graph → Template Engine → Python Code → Execute

This is brittle because:

  • Adding new extractor requires updating template
  • Protocol changes require code changes
  • No intelligence in the translation
  • Can't handle edge cases

Our approach:

Canvas Graph → Intent JSON → Claude (+ Protocols + LAC) → Intelligent Execution

This is robust because:

  • New extractor? Just add to SYS_12, Claude adapts
  • Protocol change? Update docs, Claude follows
  • Edge case? Claude uses judgment
  • Historical insight? LAC informs decisions

2.3 The Intent Schema

All user interactions serialize to a common intent format:

// Intent Schema - The Universal Language

interface AtomizerIntent {
  // Metadata
  _meta: {
    source: "chat" | "canvas";
    timestamp: string;
    session_id: string;
    mode: "user" | "power";
  };

  // What the user wants to do
  action:
    | "validate"        // Check if config makes sense
    | "create"          // Create study without running
    | "create_and_run"  // Create and immediately start
    | "run"             // Run existing study
    | "stop"            // Stop running optimization
    | "analyze"         // Analyze results
    | "report"          // Generate report
    | "modify"          // Modify existing study
    | "explain";        // Explain something

  // Study identification (for existing studies)
  study_ref?: {
    name?: string;
    id?: string;
    path?: string;
  };

  // For create/modify actions - the optimization definition
  optimization?: {
    // Model
    model: {
      path: string;
      type: "nx_part" | "nx_assembly";
      expressions?: string[];  // Known NX expressions
    };

    // Solver
    solver: {
      type: "nastran";
      solution: 101 | 103 | 105 | 111 | 112;
      deck_options?: Record<string, any>;
    };

    // Design Variables
    design_variables: Array<{
      name: string;
      nx_expression?: string;
      type: "continuous" | "integer" | "categorical";
      lower?: number;
      upper?: number;
      values?: (string | number)[];  // For categorical
    }>;

    // Objectives - NOTE: physics name, NOT extractor ID
    // Claude maps physics → extractor using SYS_12
    objectives: Array<{
      name: string;
      physics: string;  // "mass", "stress", "frequency", "zernike_wfe"
      direction: "minimize" | "maximize";
      weight?: number;  // For weighted sum
      target?: number;  // For target-seeking
    }>;

    // Constraints - same pattern
    constraints: Array<{
      name: string;
      physics: string;
      type: "upper" | "lower" | "equality" | "range";
      value?: number;
      lower?: number;
      upper?: number;
      unit?: string;
    }>;

    // Algorithm preference (Claude may override with LAC insights)
    algorithm?: {
      name?: string;  // "TPE" | "CMA-ES" | "NSGA-II" | "IMSO" | "auto"
      trials?: number;
      timeout_hours?: number;
      // If "auto" or omitted, Claude selects based on problem + LAC
    };

    // Surrogate preference
    surrogate?: {
      type?: "mlp" | "gnn" | "ensemble" | "none" | "auto";
      // If "auto", Claude decides based on trial count + problem type
    };
  };

  // For analyze/report actions
  analysis?: {
    type: "convergence" | "pareto" | "sensitivity" | "comparison";
    trials?: number[];  // Specific trials to analyze
    objectives?: string[];  // Which objectives to focus on
  };

  // For explain actions
  explanation?: {
    topic: string;
    context?: string;
  };

  // Additional context from canvas
  canvas_state?: {
    nodes: CanvasNode[];
    edges: CanvasEdge[];
    layout: "auto" | "manual";
  };
}

3. Current State Analysis

3.1 Existing Dashboard Structure

atomizer-dashboard/
├── frontend/                          # React + TypeScript + Vite
│   ├── src/
│   │   ├── components/
│   │   │   ├── charts/               # Nivo charts (parallel coords, etc.)
│   │   │   ├── chat/                 # NEW: Chat components (in progress)
│   │   │   │   ├── ChatPane.tsx
│   │   │   │   ├── ChatMessage.tsx
│   │   │   │   ├── ChatInput.tsx
│   │   │   │   └── ThinkingIndicator.tsx
│   │   │   ├── common/               # Shared UI components
│   │   │   ├── layout/               # MainLayout, Sidebar
│   │   │   └── study/                # Study-specific views
│   │   ├── context/
│   │   │   └── StudyContext.tsx      # Global study state
│   │   ├── hooks/
│   │   │   └── useChat.ts            # NEW: Chat hook (in progress)
│   │   ├── pages/
│   │   │   ├── Dashboard.tsx
│   │   │   └── StudyView.tsx
│   │   └── App.tsx
│   └── package.json
│
├── backend/                           # FastAPI
│   ├── api/
│   │   ├── routes/
│   │   │   ├── studies.py
│   │   │   ├── trials.py
│   │   │   ├── optimization.py
│   │   │   └── claude.py             # NEW: Claude routes (in progress)
│   │   └── services/
│   │       └── claude_cli_agent.py   # NEW: CLI agent (basic)
│   ├── main.py
│   └── sessions.db                   # SQLite for sessions
│
└── mcp-server/                        # EXISTING: siemens-docs MCP
    └── ...

3.2 What's Working

Component Status Notes
Study listing Working Lists all studies from filesystem
Study viewing Working Shows trials, charts, config
Parallel coordinates Working Nivo-based visualization
Convergence chart Working Best-so-far tracking
Trial table Working Sortable, filterable
Backend API Working FastAPI, SQLite integration
Chat UI shell 🔄 In Progress Components created, not connected
MCP siemens-docs Working NX Open documentation server

3.3 What's Missing

Component Status Priority
Claude session management Not Started P0
MCP Atomizer tools server Not Started P0
Canvas (React Flow) Not Started P1
Intent serialization Not Started P1
Bidirectional sync Not Started P2
Templates library Not Started P3

3.4 Dependencies

Frontend:
├── react: 18.x                    # ✅ Installed
├── typescript: 5.x                # ✅ Installed
├── vite: 5.x                      # ✅ Installed
├── reactflow: 11.x                # ❌ TO INSTALL
├── @tanstack/react-query: 5.x     # ✅ Installed
├── lucide-react: 0.x              # ✅ Installed
├── react-markdown: 9.x            # ✅ Installed
└── zustand: 4.x                   # ❌ TO INSTALL (for canvas state)

Backend:
├── fastapi: 0.x                   # ✅ Installed
├── uvicorn: 0.x                   # ✅ Installed
├── sqlite3: built-in              # ✅ Available
├── asyncio: built-in              # ✅ Available
└── websockets: 0.x                # ✅ Installed

MCP Server (new):
├── @modelcontextprotocol/sdk      # ❌ TO INSTALL
├── typescript: 5.x                # ✅ Installed (workspace)
├── better-sqlite3: 11.x           # ❌ TO INSTALL
└── zod: 3.x                       # ❌ TO INSTALL

4. Target Architecture

4.1 Directory Structure (Final State)

atomizer-dashboard/
├── frontend/
│   ├── src/
│   │   ├── components/
│   │   │   ├── charts/                    # UNCHANGED
│   │   │   ├── chat/                      # ENHANCED
│   │   │   │   ├── ChatPane.tsx           # + Mode toggle, tool viz
│   │   │   │   ├── ChatMessage.tsx        # + Tool call rendering
│   │   │   │   ├── ChatInput.tsx          # UNCHANGED
│   │   │   │   ├── ThinkingIndicator.tsx  # UNCHANGED
│   │   │   │   ├── ModeToggle.tsx         # NEW
│   │   │   │   ├── ToolCallCard.tsx       # NEW
│   │   │   │   └── index.ts
│   │   │   ├── canvas/                    # NEW
│   │   │   │   ├── AtomizerCanvas.tsx     # Main canvas component
│   │   │   │   ├── CanvasProvider.tsx     # Zustand state
│   │   │   │   ├── nodes/                 # Custom node types
│   │   │   │   │   ├── BaseNode.tsx
│   │   │   │   │   ├── ModelNode.tsx
│   │   │   │   │   ├── SolverNode.tsx
│   │   │   │   │   ├── DesignVarNode.tsx
│   │   │   │   │   ├── ExtractorNode.tsx
│   │   │   │   │   ├── ObjectiveNode.tsx
│   │   │   │   │   ├── ConstraintNode.tsx
│   │   │   │   │   ├── AlgorithmNode.tsx
│   │   │   │   │   ├── SurrogateNode.tsx
│   │   │   │   │   └── index.ts
│   │   │   │   ├── edges/
│   │   │   │   │   ├── DataFlowEdge.tsx   # Color-coded by type
│   │   │   │   │   └── index.ts
│   │   │   │   ├── palette/
│   │   │   │   │   ├── NodePalette.tsx    # Drag-drop source
│   │   │   │   │   ├── NodeCategory.tsx
│   │   │   │   │   └── index.ts
│   │   │   │   ├── panels/
│   │   │   │   │   ├── NodeConfigPanel.tsx
│   │   │   │   │   ├── ValidationPanel.tsx
│   │   │   │   │   ├── CodePreviewPanel.tsx
│   │   │   │   │   └── index.ts
│   │   │   │   └── index.ts
│   │   │   ├── common/                    # UNCHANGED
│   │   │   ├── layout/                    # MINOR UPDATES
│   │   │   │   ├── MainLayout.tsx         # + Canvas integration
│   │   │   │   └── Sidebar.tsx            # UNCHANGED
│   │   │   └── study/                     # UNCHANGED
│   │   ├── context/
│   │   │   ├── StudyContext.tsx           # UNCHANGED
│   │   │   └── CanvasContext.tsx          # NEW
│   │   ├── hooks/
│   │   │   ├── useChat.ts                 # ENHANCED (WebSocket, sessions)
│   │   │   ├── useCanvas.ts               # NEW
│   │   │   ├── useCanvasChat.ts           # NEW (bridge)
│   │   │   └── useIntentBuilder.ts        # NEW
│   │   ├── lib/
│   │   │   ├── canvas/
│   │   │   │   ├── schema.ts              # Node/edge type definitions
│   │   │   │   ├── intent.ts              # Graph → Intent serialization
│   │   │   │   ├── validation.ts          # Graph validation rules
│   │   │   │   └── templates.ts           # Preset graph templates
│   │   │   └── api/
│   │   │       └── claude.ts              # Claude API client
│   │   ├── pages/
│   │   │   ├── Dashboard.tsx              # UNCHANGED
│   │   │   ├── StudyView.tsx              # + Canvas tab
│   │   │   └── CanvasView.tsx             # NEW (standalone canvas)
│   │   └── App.tsx                        # + Canvas routes
│   ├── package.json
│   └── vite.config.ts
│
├── backend/
│   ├── api/
│   │   ├── routes/
│   │   │   ├── studies.py                 # UNCHANGED
│   │   │   ├── trials.py                  # UNCHANGED
│   │   │   ├── optimization.py            # UNCHANGED
│   │   │   └── claude.py                  # REWRITTEN
│   │   └── services/
│   │       ├── session_manager.py         # NEW
│   │       ├── conversation_store.py      # NEW
│   │       ├── context_builder.py         # NEW
│   │       └── claude_cli_agent.py        # DEPRECATED
│   ├── main.py                            # + Lifespan handlers
│   └── sessions.db
│
└── mcp-server/
    ├── siemens-docs/                      # EXISTING - UNCHANGED
    │   └── ...
    └── atomizer-tools/                    # NEW
        ├── package.json
        ├── tsconfig.json
        ├── src/
        │   ├── index.ts                   # MCP server entry
        │   ├── tools/
        │   │   ├── study.ts               # Study management tools
        │   │   ├── optimization.ts        # Run/stop tools
        │   │   ├── analysis.ts            # Analysis tools
        │   │   ├── reporting.ts           # Report generation
        │   │   ├── physics.ts             # Physics explanation
        │   │   ├── canvas.ts              # Canvas intent processing
        │   │   └── admin.ts               # Power mode tools
        │   └── utils/
        │       ├── database.ts            # SQLite helpers
        │       ├── python.ts              # Python subprocess
        │       └── validation.ts          # Input validation
        └── README.md

4.2 Data Flow Diagram

┌─────────────────────────────────────────────────────────────────────────────────┐
│                              DATA FLOW                                           │
├─────────────────────────────────────────────────────────────────────────────────┤
│                                                                                  │
│  ┌──────────────────────────────────────────────────────────────────────────┐  │
│  │ FRONTEND (React)                                                          │  │
│  │                                                                           │  │
│  │  ┌─────────────┐         ┌─────────────┐         ┌─────────────┐        │  │
│  │  │  ChatPane   │◄───────▶│  useChat    │◄───────▶│  WebSocket  │        │  │
│  │  │             │         │  (hook)     │         │  Client     │        │  │
│  │  └─────────────┘         └─────────────┘         └──────┬──────┘        │  │
│  │                                                         │                │  │
│  │  ┌─────────────┐         ┌─────────────┐         ┌──────▼──────┐        │  │
│  │  │  Canvas     │◄───────▶│  useCanvas  │◄───────▶│  Intent     │        │  │
│  │  │  (ReactFlow)│         │  (hook)     │         │  Builder    │        │  │
│  │  └─────────────┘         └─────────────┘         └──────┬──────┘        │  │
│  │                                                         │                │  │
│  │                                                         ▼                │  │
│  │                                               ┌─────────────────┐        │  │
│  │                                               │ useCanvasChat   │        │  │
│  │                                               │ (bridge hook)   │        │  │
│  │                                               └────────┬────────┘        │  │
│  └───────────────────────────────────────────────────────┼──────────────────┘  │
│                                                          │                      │
│                                                          │ WebSocket            │
│                                                          │ /api/claude/ws       │
│                                                          ▼                      │
│  ┌──────────────────────────────────────────────────────────────────────────┐  │
│  │ BACKEND (FastAPI)                                                         │  │
│  │                                                                           │  │
│  │  ┌─────────────┐         ┌─────────────┐         ┌─────────────┐        │  │
│  │  │  WebSocket  │────────▶│  Session    │────────▶│  Context    │        │  │
│  │  │  Handler    │         │  Manager    │         │  Builder    │        │  │
│  │  └─────────────┘         └──────┬──────┘         └──────┬──────┘        │  │
│  │                                 │                       │                │  │
│  │                                 │ spawn                 │ build          │  │
│  │                                 ▼                       ▼                │  │
│  │                          ┌─────────────┐         ┌─────────────┐        │  │
│  │                          │   Claude    │◄────────│  System     │        │  │
│  │                          │  Subprocess │         │  Prompt     │        │  │
│  │                          └──────┬──────┘         └─────────────┘        │  │
│  │                                 │                                        │  │
│  │                                 │ --mcp-config                          │  │
│  │                                 ▼                                        │  │
│  └─────────────────────────────────┼────────────────────────────────────────┘  │
│                                    │                                            │
│                                    │ MCP Protocol                               │
│                                    ▼                                            │
│  ┌──────────────────────────────────────────────────────────────────────────┐  │
│  │ MCP SERVER (Node.js)                                                      │  │
│  │                                                                           │  │
│  │  ┌───────────────────────────────────────────────────────────────────┐  │  │
│  │  │ TOOLS                                                              │  │  │
│  │  │                                                                    │  │  │
│  │  │  User Mode:              Power Mode (additional):                  │  │  │
│  │  │  ├─ list_studies         ├─ edit_file                             │  │  │
│  │  │  ├─ get_study_status     ├─ create_extractor                      │  │  │
│  │  │  ├─ create_study         ├─ run_shell_command                     │  │  │
│  │  │  ├─ run_optimization     ├─ search_codebase                       │  │  │
│  │  │  ├─ stop_optimization    └─ git_operations                        │  │  │
│  │  │  ├─ get_trial_data                                                │  │  │
│  │  │  ├─ analyze_convergence                                           │  │  │
│  │  │  ├─ compare_trials                                                │  │  │
│  │  │  ├─ get_best_design                                               │  │  │
│  │  │  ├─ generate_report                                               │  │  │
│  │  │  ├─ explain_physics                                               │  │  │
│  │  │  ├─ recommend_method                                              │  │  │
│  │  │  ├─ query_extractors                                              │  │  │
│  │  │  └─ interpret_canvas_intent                                       │  │  │
│  │  │                                                                    │  │  │
│  │  └───────────────────────────────────────────────────────────────────┘  │  │
│  │                                 │                                        │  │
│  │                                 │ subprocess / import                   │  │
│  │                                 ▼                                        │  │
│  └─────────────────────────────────┼────────────────────────────────────────┘  │
│                                    │                                            │
│  ┌─────────────────────────────────┼────────────────────────────────────────┐  │
│  │ PYTHON ENGINE (optimization_engine/)                                      │  │
│  │                                 │                                         │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐     │  │
│  │  │   study/    │  │ extractors/ │  │    nx/      │  │   core/     │     │  │
│  │  │ creator.py  │  │  E1-E24     │  │ solver.py   │  │ runner.py   │     │  │
│  │  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘     │  │
│  │                                                                          │  │
│  └──────────────────────────────────────────────────────────────────────────┘  │
│                                                                                  │
└─────────────────────────────────────────────────────────────────────────────────┘

5. Phase 0: Foundation - MCP Chat

5.1 Overview

Establish the core chat infrastructure with Claude subprocess management, MCP tools, and WebSocket streaming.

5.2 Components

5.2.1 Atomizer MCP Server

Location: mcp-server/atomizer-tools/

Purpose: Expose Atomizer capabilities as MCP tools that Claude can call.

Package.json:

{
  "name": "atomizer-mcp-server",
  "version": "1.0.0",
  "type": "module",
  "main": "dist/index.js",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsx watch src/index.ts"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0",
    "better-sqlite3": "^11.0.0",
    "zod": "^3.23.0"
  },
  "devDependencies": {
    "@types/better-sqlite3": "^7.6.0",
    "@types/node": "^20.0.0",
    "tsx": "^4.0.0",
    "typescript": "^5.0.0"
  }
}

Index.ts Structure:

// mcp-server/atomizer-tools/src/index.ts

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

import { studyTools } from "./tools/study.js";
import { optimizationTools } from "./tools/optimization.js";
import { analysisTools } from "./tools/analysis.js";
import { reportingTools } from "./tools/reporting.js";
import { physicsTools } from "./tools/physics.js";
import { canvasTools } from "./tools/canvas.js";
import { adminTools } from "./tools/admin.js";

// Environment determines mode
const MODE = process.env.ATOMIZER_MODE || "user";
const ATOMIZER_ROOT = process.env.ATOMIZER_ROOT || "C:/Users/antoi/Atomizer";

// Tool registry based on mode
const userTools = [
  ...studyTools,
  ...optimizationTools,
  ...analysisTools,
  ...reportingTools,
  ...physicsTools,
  ...canvasTools,
];

const powerTools = [
  ...userTools,
  ...adminTools,
];

const tools = MODE === "power" ? powerTools : userTools;

// Create server
const server = new Server(
  {
    name: "atomizer-tools",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// List tools handler
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: tools.map(t => t.definition),
}));

// Call tool handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const toolName = request.params.name;
  const tool = tools.find(t => t.definition.name === toolName);

  if (!tool) {
    throw new Error(`Unknown tool: ${toolName}`);
  }

  try {
    return await tool.handler(request.params.arguments ?? {});
  } catch (error: any) {
    return {
      content: [{
        type: "text",
        text: `Error executing ${toolName}: ${error.message}`,
      }],
      isError: true,
    };
  }
});

// Start server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Atomizer MCP Server running...");
}

main().catch(console.error);

5.2.2 Tool Implementations

Study Tools (study.ts):

// mcp-server/atomizer-tools/src/tools/study.ts

import { z } from "zod";
import { execSync, spawn } from "child_process";
import Database from "better-sqlite3";
import * as fs from "fs/promises";
import * as path from "path";

const ATOMIZER_ROOT = process.env.ATOMIZER_ROOT || "C:/Users/antoi/Atomizer";
const STUDIES_DIR = path.join(ATOMIZER_ROOT, "studies");
const PYTHON_PATH = "C:/Users/antoi/anaconda3/envs/atomizer/python.exe";

interface Tool {
  definition: {
    name: string;
    description: string;
    inputSchema: Record<string, any>;
  };
  handler: (args: any) => Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }>;
}

export const studyTools: Tool[] = [
  {
    definition: {
      name: "list_studies",
      description: "List all available optimization studies with their status, trial counts, and best results",
      inputSchema: {
        type: "object",
        properties: {
          filter: {
            type: "string",
            description: "Optional filter pattern to match study names (e.g., 'bracket', 'mirror')",
          },
          include_archived: {
            type: "boolean",
            description: "Include archived studies (default: false)",
          },
        },
      },
    },
    handler: async (args: { filter?: string; include_archived?: boolean }) => {
      try {
        const entries = await fs.readdir(STUDIES_DIR, { withFileTypes: true });
        let studies = entries
          .filter(e => e.isDirectory())
          .filter(e => !e.name.startsWith("_") || args.include_archived)
          .map(e => e.name);

        if (args.filter) {
          const pattern = args.filter.toLowerCase();
          studies = studies.filter(s => s.toLowerCase().includes(pattern));
        }

        const results = await Promise.all(studies.map(async (name) => {
          const studyDir = path.join(STUDIES_DIR, name);
          const result: any = { name, status: "unknown", trials: 0 };

          // Check for config
          const configPaths = [
            path.join(studyDir, "optimization_config.json"),
            path.join(studyDir, "1_setup", "optimization_config.json"),
          ];

          for (const configPath of configPaths) {
            try {
              const config = JSON.parse(await fs.readFile(configPath, "utf-8"));
              result.objectives = config.objectives?.map((o: any) => o.name) || [];
              result.design_vars = config.design_variables?.length || 0;
              result.algorithm = config.optimization?.algorithm || "unknown";
              break;
            } catch {}
          }

          // Check for results
          const dbPath = path.join(studyDir, "3_results", "study.db");
          try {
            const db = new Database(dbPath, { readonly: true });
            const row = db.prepare("SELECT COUNT(*) as count FROM trials WHERE state = 'COMPLETE'").get() as any;
            result.trials = row?.count || 0;

            if (result.trials > 0) {
              const best = db.prepare(`
                SELECT MIN(tv.value) as best_value
                FROM trial_values tv
                JOIN trials t ON tv.trial_id = t.trial_id
                WHERE t.state = 'COMPLETE'
              `).get() as any;
              result.best_objective = best?.best_value;
              result.status = "has_results";
            } else {
              result.status = "configured";
            }

            db.close();
          } catch {
            result.status = result.objectives ? "configured" : "incomplete";
          }

          return result;
        }));

        return {
          content: [{
            type: "text",
            text: JSON.stringify(results, null, 2),
          }],
        };
      } catch (error: any) {
        return {
          content: [{ type: "text", text: `Error listing studies: ${error.message}` }],
          isError: true,
        };
      }
    },
  },

  {
    definition: {
      name: "get_study_status",
      description: "Get detailed status of a specific study including configuration, trial count, best results, and current state",
      inputSchema: {
        type: "object",
        properties: {
          study_name: {
            type: "string",
            description: "Name of the study to inspect",
          },
        },
        required: ["study_name"],
      },
    },
    handler: async (args: { study_name: string }) => {
      const studyDir = path.join(STUDIES_DIR, args.study_name);

      try {
        await fs.access(studyDir);
      } catch {
        return {
          content: [{ type: "text", text: `Study '${args.study_name}' not found` }],
          isError: true,
        };
      }

      const result: any = {
        name: args.study_name,
        path: studyDir,
      };

      // Load config
      const configPaths = [
        path.join(studyDir, "optimization_config.json"),
        path.join(studyDir, "1_setup", "optimization_config.json"),
      ];

      for (const configPath of configPaths) {
        try {
          result.config = JSON.parse(await fs.readFile(configPath, "utf-8"));
          break;
        } catch {}
      }

      // Load results
      const dbPath = path.join(studyDir, "3_results", "study.db");
      try {
        const db = new Database(dbPath, { readonly: true });

        // Trial count
        const countRow = db.prepare("SELECT COUNT(*) as total, SUM(CASE WHEN state = 'COMPLETE' THEN 1 ELSE 0 END) as complete FROM trials").get() as any;
        result.trials = {
          total: countRow?.total || 0,
          complete: countRow?.complete || 0,
        };

        // Best result
        if (result.trials.complete > 0) {
          const best = db.prepare(`
            SELECT t.number, tv.value
            FROM trial_values tv
            JOIN trials t ON tv.trial_id = t.trial_id
            WHERE t.state = 'COMPLETE'
            ORDER BY tv.value ASC
            LIMIT 1
          `).get() as any;

          result.best = {
            trial: best?.number,
            objective_value: best?.value,
          };

          // Recent trials
          const recent = db.prepare(`
            SELECT t.number, t.state, tv.value
            FROM trials t
            LEFT JOIN trial_values tv ON t.trial_id = tv.trial_id
            ORDER BY t.trial_id DESC
            LIMIT 5
          `).all();

          result.recent_trials = recent;
        }

        db.close();
      } catch (e) {
        result.database_error = "Could not read results database";
      }

      return {
        content: [{
          type: "text",
          text: JSON.stringify(result, null, 2),
        }],
      };
    },
  },

  {
    definition: {
      name: "create_study",
      description: "Create a new optimization study. This tool accepts a natural language description or structured intent and creates the complete study structure including config files and run script.",
      inputSchema: {
        type: "object",
        properties: {
          name: {
            type: "string",
            description: "Study name in snake_case (e.g., 'bracket_mass_v1')",
          },
          description: {
            type: "string",
            description: "Natural language description of what to optimize, or 'INTENT:' followed by JSON intent",
          },
          template: {
            type: "string",
            enum: ["bracket", "beam", "mirror", "custom"],
            description: "Base template to use for study structure",
          },
          nx_model_path: {
            type: "string",
            description: "Path to the NX model file (.prt)",
          },
        },
        required: ["name", "description"],
      },
    },
    handler: async (args) => {
      // This tool formats the request for Claude to process
      // Claude will use protocols (OP_01) to actually create the study

      const isIntent = args.description.startsWith("INTENT:");

      if (isIntent) {
        // Canvas-originated intent
        const intentJson = args.description.substring(7);
        return {
          content: [{
            type: "text",
            text: `Canvas intent received for study creation.

Study Name: ${args.name}
Template: ${args.template || "custom"}
NX Model: ${args.nx_model_path || "not specified"}

Intent:
${intentJson}

Please process this intent using OP_01 (Create Study) protocol:
1. Validate the intent structure
2. Map physics to extractors using SYS_12
3. Query LAC for similar optimizations
4. Create study directory structure
5. Generate optimization_config.json
6. Generate run_optimization.py
7. Report completion status`,
          }],
        };
      } else {
        // Natural language description
        return {
          content: [{
            type: "text",
            text: `Study creation requested via natural language.

Study Name: ${args.name}
Template: ${args.template || "custom"}
NX Model: ${args.nx_model_path || "not specified"}

Description:
${args.description}

Please process this using Study Interview Mode or OP_01 protocol:
1. Parse the description to understand objectives, constraints, design variables
2. Ask clarifying questions if needed
3. Map to appropriate extractors (SYS_12)
4. Query LAC for insights
5. Create study structure
6. Generate configuration files`,
          }],
        };
      }
    },
  },
];

Optimization Tools (optimization.ts):

// mcp-server/atomizer-tools/src/tools/optimization.ts

import { execSync, spawn, ChildProcess } from "child_process";
import * as path from "path";
import * as fs from "fs/promises";
import Database from "better-sqlite3";

const ATOMIZER_ROOT = process.env.ATOMIZER_ROOT || "C:/Users/antoi/Atomizer";
const STUDIES_DIR = path.join(ATOMIZER_ROOT, "studies");
const PYTHON_PATH = "C:/Users/antoi/anaconda3/envs/atomizer/python.exe";

// Track running optimizations
const runningOptimizations: Map<string, ChildProcess> = new Map();

export const optimizationTools = [
  {
    definition: {
      name: "run_optimization",
      description: "Start an optimization run for a study. The optimization runs in the background.",
      inputSchema: {
        type: "object",
        properties: {
          study_name: {
            type: "string",
            description: "Name of the study to optimize",
          },
          max_trials: {
            type: "number",
            description: "Maximum number of trials (overrides config)",
          },
          method: {
            type: "string",
            enum: ["TPE", "CMA-ES", "NSGA-II", "IMSO", "RandomSearch"],
            description: "Optimization method (overrides config)",
          },
        },
        required: ["study_name"],
      },
    },
    handler: async (args: { study_name: string; max_trials?: number; method?: string }) => {
      const studyDir = path.join(STUDIES_DIR, args.study_name);
      const runScript = path.join(studyDir, "run_optimization.py");

      // Check if already running
      if (runningOptimizations.has(args.study_name)) {
        return {
          content: [{
            type: "text",
            text: `Optimization for '${args.study_name}' is already running.`,
          }],
        };
      }

      // Check script exists
      try {
        await fs.access(runScript);
      } catch {
        return {
          content: [{
            type: "text",
            text: `No run_optimization.py found for study '${args.study_name}'. Create the study first.`,
          }],
          isError: true,
        };
      }

      // Build command
      const cmdArgs = [runScript];
      if (args.max_trials) cmdArgs.push("--trials", String(args.max_trials));
      if (args.method) cmdArgs.push("--method", args.method);

      // Spawn process
      const process = spawn(PYTHON_PATH, cmdArgs, {
        cwd: studyDir,
        stdio: ["ignore", "pipe", "pipe"],
        detached: true,
      });

      runningOptimizations.set(args.study_name, process);

      process.on("exit", (code) => {
        runningOptimizations.delete(args.study_name);
      });

      return {
        content: [{
          type: "text",
          text: `Optimization started for '${args.study_name}'.
PID: ${process.pid}
Method: ${args.method || "from config"}
Max Trials: ${args.max_trials || "from config"}

Use get_optimization_status to monitor progress.`,
        }],
      };
    },
  },

  {
    definition: {
      name: "stop_optimization",
      description: "Stop a running optimization gracefully",
      inputSchema: {
        type: "object",
        properties: {
          study_name: {
            type: "string",
            description: "Name of the study to stop",
          },
        },
        required: ["study_name"],
      },
    },
    handler: async (args: { study_name: string }) => {
      const process = runningOptimizations.get(args.study_name);

      if (!process) {
        return {
          content: [{
            type: "text",
            text: `No running optimization found for '${args.study_name}'.`,
          }],
        };
      }

      // Send SIGTERM for graceful shutdown
      process.kill("SIGTERM");
      runningOptimizations.delete(args.study_name);

      return {
        content: [{
          type: "text",
          text: `Optimization for '${args.study_name}' has been stopped.`,
        }],
      };
    },
  },

  {
    definition: {
      name: "get_optimization_status",
      description: "Get the current status of an optimization including whether it's running, trial count, and recent progress",
      inputSchema: {
        type: "object",
        properties: {
          study_name: {
            type: "string",
            description: "Name of the study to check",
          },
        },
        required: ["study_name"],
      },
    },
    handler: async (args: { study_name: string }) => {
      const isRunning = runningOptimizations.has(args.study_name);
      const studyDir = path.join(STUDIES_DIR, args.study_name);
      const dbPath = path.join(studyDir, "3_results", "study.db");

      const result: any = {
        study_name: args.study_name,
        is_running: isRunning,
      };

      try {
        const db = new Database(dbPath, { readonly: true });

        // Get counts
        const counts = db.prepare(`
          SELECT
            COUNT(*) as total,
            SUM(CASE WHEN state = 'COMPLETE' THEN 1 ELSE 0 END) as complete,
            SUM(CASE WHEN state = 'FAIL' THEN 1 ELSE 0 END) as failed
          FROM trials
        `).get() as any;

        result.trials = counts;

        // Get best
        const best = db.prepare(`
          SELECT t.number, tv.value
          FROM trial_values tv
          JOIN trials t ON tv.trial_id = t.trial_id
          WHERE t.state = 'COMPLETE'
          ORDER BY tv.value ASC
          LIMIT 1
        `).get() as any;

        result.best = best;

        // Get recent
        const recent = db.prepare(`
          SELECT t.number, t.state, tv.value, t.datetime_start
          FROM trials t
          LEFT JOIN trial_values tv ON t.trial_id = tv.trial_id
          ORDER BY t.trial_id DESC
          LIMIT 5
        `).all();

        result.recent = recent;

        db.close();
      } catch (e) {
        result.database_error = "Could not read results";
      }

      return {
        content: [{
          type: "text",
          text: JSON.stringify(result, null, 2),
        }],
      };
    },
  },
];

Analysis Tools (analysis.ts):

// mcp-server/atomizer-tools/src/tools/analysis.ts

import Database from "better-sqlite3";
import * as path from "path";

const ATOMIZER_ROOT = process.env.ATOMIZER_ROOT || "C:/Users/antoi/Atomizer";
const STUDIES_DIR = path.join(ATOMIZER_ROOT, "studies");

export const analysisTools = [
  {
    definition: {
      name: "get_trial_data",
      description: "Query trial data from a study's database with various filters",
      inputSchema: {
        type: "object",
        properties: {
          study_name: { type: "string", description: "Study name" },
          query: {
            type: "string",
            enum: ["all", "best", "pareto", "recent", "failed"],
            description: "Type of query",
          },
          limit: { type: "number", description: "Max results (default: 10)" },
          objective: { type: "string", description: "Objective to sort by" },
        },
        required: ["study_name", "query"],
      },
    },
    handler: async (args) => {
      const dbPath = path.join(STUDIES_DIR, args.study_name, "3_results", "study.db");
      const limit = args.limit || 10;

      try {
        const db = new Database(dbPath, { readonly: true });

        let sql: string;
        switch (args.query) {
          case "best":
            sql = `
              SELECT t.number as trial, tv.value as objective,
                     GROUP_CONCAT(tp.param_name || '=' || tp.param_value, ', ') as params
              FROM trials t
              JOIN trial_values tv ON t.trial_id = tv.trial_id
              JOIN trial_params tp ON t.trial_id = tp.trial_id
              WHERE t.state = 'COMPLETE'
              GROUP BY t.trial_id
              ORDER BY tv.value ASC
              LIMIT ${limit}
            `;
            break;
          case "recent":
            sql = `
              SELECT t.number as trial, t.state, tv.value as objective,
                     t.datetime_start as started
              FROM trials t
              LEFT JOIN trial_values tv ON t.trial_id = tv.trial_id
              ORDER BY t.trial_id DESC
              LIMIT ${limit}
            `;
            break;
          case "failed":
            sql = `
              SELECT t.number as trial, t.state, t.datetime_start as started
              FROM trials t
              WHERE t.state = 'FAIL'
              ORDER BY t.trial_id DESC
              LIMIT ${limit}
            `;
            break;
          case "pareto":
            // Simplified - real Pareto requires multi-objective handling
            sql = `
              SELECT t.number as trial, tv.value as objective
              FROM trials t
              JOIN trial_values tv ON t.trial_id = tv.trial_id
              WHERE t.state = 'COMPLETE'
              ORDER BY tv.value ASC
              LIMIT ${limit}
            `;
            break;
          default:
            sql = `
              SELECT t.number as trial, t.state, tv.value as objective
              FROM trials t
              LEFT JOIN trial_values tv ON t.trial_id = tv.trial_id
              ORDER BY t.trial_id
              LIMIT ${limit}
            `;
        }

        const results = db.prepare(sql).all();
        db.close();

        return {
          content: [{
            type: "text",
            text: JSON.stringify(results, null, 2),
          }],
        };
      } catch (e: any) {
        return {
          content: [{ type: "text", text: `Error querying trials: ${e.message}` }],
          isError: true,
        };
      }
    },
  },

  {
    definition: {
      name: "analyze_convergence",
      description: "Analyze optimization convergence and provide insights",
      inputSchema: {
        type: "object",
        properties: {
          study_name: { type: "string" },
          window_size: { type: "number", description: "Rolling window for analysis (default: 10)" },
        },
        required: ["study_name"],
      },
    },
    handler: async (args) => {
      const dbPath = path.join(STUDIES_DIR, args.study_name, "3_results", "study.db");
      const window = args.window_size || 10;

      try {
        const db = new Database(dbPath, { readonly: true });

        // Get all completed trials with values
        const trials = db.prepare(`
          SELECT t.number, tv.value
          FROM trials t
          JOIN trial_values tv ON t.trial_id = tv.trial_id
          WHERE t.state = 'COMPLETE'
          ORDER BY t.number
        `).all() as Array<{ number: number; value: number }>;

        db.close();

        if (trials.length < 5) {
          return {
            content: [{
              type: "text",
              text: `Not enough trials (${trials.length}) for convergence analysis. Need at least 5.`,
            }],
          };
        }

        // Calculate metrics
        const values = trials.map(t => t.value);
        const bestSoFar: number[] = [];
        let best = Infinity;
        for (const v of values) {
          best = Math.min(best, v);
          bestSoFar.push(best);
        }

        // Improvement rate (last window vs previous window)
        const recentBest = Math.min(...values.slice(-window));
        const previousBest = Math.min(...values.slice(-2 * window, -window));
        const improvementRate = previousBest > 0 ? (previousBest - recentBest) / previousBest : 0;

        // Variance in recent trials
        const recentValues = values.slice(-window);
        const mean = recentValues.reduce((a, b) => a + b, 0) / recentValues.length;
        const variance = recentValues.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / recentValues.length;
        const stdDev = Math.sqrt(variance);

        const result = {
          total_trials: trials.length,
          best_value: best,
          best_trial: trials.find(t => t.value === best)?.number,
          improvement_rate: improvementRate,
          recent_std_dev: stdDev,
          converged: improvementRate < 0.01 && stdDev < mean * 0.1,
          recommendation: "",
        };

        // Generate recommendation
        if (result.converged) {
          result.recommendation = "Optimization appears converged. Consider stopping or increasing exploration.";
        } else if (improvementRate > 0.05) {
          result.recommendation = "Still improving significantly. Continue optimization.";
        } else if (stdDev > mean * 0.3) {
          result.recommendation = "High variance in results. May benefit from more exploitation (reduce exploration).";
        } else {
          result.recommendation = "Slow convergence. Consider different algorithm or expanded bounds.";
        }

        return {
          content: [{
            type: "text",
            text: JSON.stringify(result, null, 2),
          }],
        };
      } catch (e: any) {
        return {
          content: [{ type: "text", text: `Error analyzing convergence: ${e.message}` }],
          isError: true,
        };
      }
    },
  },

  {
    definition: {
      name: "compare_trials",
      description: "Compare multiple trials side by side",
      inputSchema: {
        type: "object",
        properties: {
          study_name: { type: "string" },
          trial_numbers: {
            type: "array",
            items: { type: "number" },
            description: "Trial numbers to compare",
          },
        },
        required: ["study_name", "trial_numbers"],
      },
    },
    handler: async (args) => {
      const dbPath = path.join(STUDIES_DIR, args.study_name, "3_results", "study.db");

      try {
        const db = new Database(dbPath, { readonly: true });

        const results = args.trial_numbers.map(num => {
          const trial = db.prepare(`
            SELECT t.number, t.state, tv.value as objective
            FROM trials t
            LEFT JOIN trial_values tv ON t.trial_id = tv.trial_id
            WHERE t.number = ?
          `).get(num) as any;

          if (!trial) return { trial: num, error: "Not found" };

          const params = db.prepare(`
            SELECT param_name, param_value
            FROM trial_params tp
            JOIN trials t ON tp.trial_id = t.trial_id
            WHERE t.number = ?
          `).all(num) as Array<{ param_name: string; param_value: string }>;

          trial.parameters = Object.fromEntries(
            params.map(p => [p.param_name, parseFloat(p.param_value) || p.param_value])
          );

          return trial;
        });

        db.close();

        return {
          content: [{
            type: "text",
            text: JSON.stringify(results, null, 2),
          }],
        };
      } catch (e: any) {
        return {
          content: [{ type: "text", text: `Error comparing trials: ${e.message}` }],
          isError: true,
        };
      }
    },
  },

  {
    definition: {
      name: "get_best_design",
      description: "Get the best design found in an optimization",
      inputSchema: {
        type: "object",
        properties: {
          study_name: { type: "string" },
          objective_index: {
            type: "number",
            description: "Index of objective (0-based) for multi-objective studies",
          },
        },
        required: ["study_name"],
      },
    },
    handler: async (args) => {
      const dbPath = path.join(STUDIES_DIR, args.study_name, "3_results", "study.db");

      try {
        const db = new Database(dbPath, { readonly: true });

        const best = db.prepare(`
          SELECT t.number, t.state, tv.value as objective
          FROM trials t
          JOIN trial_values tv ON t.trial_id = tv.trial_id
          WHERE t.state = 'COMPLETE'
          ORDER BY tv.value ASC
          LIMIT 1
        `).get() as any;

        if (!best) {
          db.close();
          return {
            content: [{ type: "text", text: "No completed trials found." }],
          };
        }

        const params = db.prepare(`
          SELECT param_name, param_value
          FROM trial_params tp
          JOIN trials t ON tp.trial_id = t.trial_id
          WHERE t.number = ?
        `).all(best.number);

        best.parameters = Object.fromEntries(
          (params as any[]).map(p => [p.param_name, parseFloat(p.param_value) || p.param_value])
        );

        db.close();

        return {
          content: [{
            type: "text",
            text: JSON.stringify(best, null, 2),
          }],
        };
      } catch (e: any) {
        return {
          content: [{ type: "text", text: `Error getting best design: ${e.message}` }],
          isError: true,
        };
      }
    },
  },
];

[Document continues with remaining tool implementations, backend components, frontend components, etc.]


6. Phase 1: Canvas - React Flow Integration

6.1 Overview

Add a visual workflow builder using React Flow that allows users to construct optimization configurations by connecting nodes.

6.2 React Flow Setup

Install Dependencies:

cd atomizer-dashboard/frontend
npm install reactflow zustand
npm install -D @types/reactflow

6.3 Node Types

// frontend/src/lib/canvas/schema.ts

export type NodeCategory =
  | "input"      // Model, expressions
  | "process"    // Solver, updater
  | "extract"    // Physics extractors
  | "logic"      // Objectives, constraints
  | "optimize"   // Algorithm, surrogate
  | "output";    // Results, reports

export interface PortDefinition {
  id: string;
  label: string;
  dataType: DataType;
  direction: "input" | "output";
  required?: boolean;
  multiple?: boolean;  // Can accept multiple connections
}

export type DataType =
  | "model"       // NX part/assembly
  | "mesh"        // FEM mesh
  | "results"     // OP2/F06 files
  | "scalar"      // Single numeric value
  | "vector"      // Array of values
  | "config"      // Configuration object
  | "any";        // Accepts any type

export interface BaseNodeData {
  label: string;
  category: NodeCategory;
  description?: string;
  ports: {
    inputs: PortDefinition[];
    outputs: PortDefinition[];
  };
  config: Record<string, any>;
  validation?: {
    isValid: boolean;
    errors: string[];
    warnings: string[];
  };
}

// Specific node data types
export interface ModelNodeData extends BaseNodeData {
  category: "input";
  config: {
    path: string;
    type: "nx_part" | "nx_assembly";
    expressions?: string[];
  };
}

export interface SolverNodeData extends BaseNodeData {
  category: "process";
  config: {
    solver: "nastran";
    solution: 101 | 103 | 105 | 111 | 112;
    options?: Record<string, any>;
  };
}

export interface ExtractorNodeData extends BaseNodeData {
  category: "extract";
  config: {
    physics: string;      // Human-readable: "mass", "stress", etc.
    extractor_id?: string; // Resolved by Claude: "E4", "E3", etc.
    subcase?: number;
    aggregation?: "max" | "min" | "mean" | "sum";
    node_set?: string;
  };
}

export interface ObjectiveNodeData extends BaseNodeData {
  category: "logic";
  config: {
    name: string;
    direction: "minimize" | "maximize";
    weight?: number;
    target?: number;
  };
}

export interface ConstraintNodeData extends BaseNodeData {
  category: "logic";
  config: {
    name: string;
    type: "upper" | "lower" | "equality" | "range";
    value?: number;
    lower?: number;
    upper?: number;
    unit?: string;
  };
}

export interface AlgorithmNodeData extends BaseNodeData {
  category: "optimize";
  config: {
    method: "TPE" | "CMA-ES" | "NSGA-II" | "IMSO" | "auto";
    trials?: number;
    timeout?: number;
    seed?: number;
  };
}

export interface SurrogateNodeData extends BaseNodeData {
  category: "optimize";
  config: {
    type: "mlp" | "gnn" | "ensemble" | "none" | "auto";
    train_after?: number;  // Train after N FEA trials
    predict_count?: number;  // Predictions per FEA validation
  };
}

export interface DesignVarNodeData extends BaseNodeData {
  category: "input";
  config: {
    name: string;
    nx_expression?: string;
    type: "continuous" | "integer" | "categorical";
    lower?: number;
    upper?: number;
    values?: (string | number)[];
  };
}

6.4 Node Components

// frontend/src/components/canvas/nodes/BaseNode.tsx

import React from 'react';
import { Handle, Position, NodeProps } from 'reactflow';
import { BaseNodeData, PortDefinition } from '../../../lib/canvas/schema';

interface BaseNodeProps extends NodeProps<BaseNodeData> {
  icon: React.ReactNode;
  color: string;
  children?: React.ReactNode;
}

export const BaseNode: React.FC<BaseNodeProps> = ({
  data,
  selected,
  icon,
  color,
  children,
}) => {
  return (
    <div
      className={`
        px-4 py-3 rounded-lg border-2 min-w-[180px]
        ${selected ? 'border-primary-500 shadow-lg' : 'border-dark-600'}
        bg-dark-800
      `}
    >
      {/* Input Handles */}
      {data.ports.inputs.map((port, idx) => (
        <Handle
          key={port.id}
          type="target"
          position={Position.Left}
          id={port.id}
          style={{
            top: `${((idx + 1) / (data.ports.inputs.length + 1)) * 100}%`,
            background: getDataTypeColor(port.dataType),
          }}
          title={port.label}
        />
      ))}

      {/* Header */}
      <div className="flex items-center gap-2 mb-2">
        <div
          className="w-8 h-8 rounded-md flex items-center justify-center"
          style={{ backgroundColor: `${color}20` }}
        >
          <span style={{ color }}>{icon}</span>
        </div>
        <div>
          <div className="text-sm font-medium text-dark-100">{data.label}</div>
          <div className="text-xs text-dark-400">{data.category}</div>
        </div>
      </div>

      {/* Content */}
      {children}

      {/* Validation Status */}
      {data.validation && !data.validation.isValid && (
        <div className="mt-2 text-xs text-red-400">
          {data.validation.errors[0]}
        </div>
      )}

      {/* Output Handles */}
      {data.ports.outputs.map((port, idx) => (
        <Handle
          key={port.id}
          type="source"
          position={Position.Right}
          id={port.id}
          style={{
            top: `${((idx + 1) / (data.ports.outputs.length + 1)) * 100}%`,
            background: getDataTypeColor(port.dataType),
          }}
          title={port.label}
        />
      ))}
    </div>
  );
};

function getDataTypeColor(type: string): string {
  const colors: Record<string, string> = {
    model: '#3b82f6',    // blue
    mesh: '#8b5cf6',     // purple
    results: '#f97316',  // orange
    scalar: '#22c55e',   // green
    vector: '#14b8a6',   // teal
    config: '#6b7280',   // gray
    any: '#a855f7',      // violet
  };
  return colors[type] || colors.any;
}
// frontend/src/components/canvas/nodes/ExtractorNode.tsx

import React from 'react';
import { NodeProps } from 'reactflow';
import { Beaker } from 'lucide-react';
import { BaseNode } from './BaseNode';
import { ExtractorNodeData } from '../../../lib/canvas/schema';

// Physics options with their descriptions
const PHYSICS_OPTIONS = [
  { value: 'mass', label: 'Mass', description: 'Total mass from BDF' },
  { value: 'stress', label: 'Von Mises Stress', description: 'Maximum stress' },
  { value: 'displacement', label: 'Displacement', description: 'Nodal displacement' },
  { value: 'frequency', label: 'Natural Frequency', description: 'Modal frequency' },
  { value: 'zernike_wfe', label: 'Zernike WFE', description: 'Wavefront error' },
  { value: 'buckling', label: 'Buckling Factor', description: 'Critical load factor' },
  { value: 'temperature', label: 'Temperature', description: 'Thermal results' },
];

export const ExtractorNode: React.FC<NodeProps<ExtractorNodeData>> = (props) => {
  const { data } = props;

  const selectedPhysics = PHYSICS_OPTIONS.find(p => p.value === data.config.physics);

  return (
    <BaseNode {...props} icon={<Beaker className="w-4 h-4" />} color="#f97316">
      <div className="space-y-2">
        <div className="text-xs">
          <span className="text-dark-400">Physics: </span>
          <span className="text-dark-200">{selectedPhysics?.label || 'Not set'}</span>
        </div>

        {data.config.aggregation && (
          <div className="text-xs">
            <span className="text-dark-400">Aggregation: </span>
            <span className="text-dark-200">{data.config.aggregation}</span>
          </div>
        )}

        {data.config.extractor_id && (
          <div className="text-xs text-dark-500">
             {data.config.extractor_id}
          </div>
        )}
      </div>
    </BaseNode>
  );
};

// Default data for new extractor nodes
export const extractorNodeDefaults: ExtractorNodeData = {
  label: 'Extractor',
  category: 'extract',
  description: 'Extract physics quantity from FEA results',
  ports: {
    inputs: [
      { id: 'results', label: 'Results', dataType: 'results', direction: 'input', required: true },
    ],
    outputs: [
      { id: 'value', label: 'Value', dataType: 'scalar', direction: 'output' },
    ],
  },
  config: {
    physics: '',
    aggregation: 'max',
  },
};

6.5 Canvas State Management

// frontend/src/hooks/useCanvas.ts

import { create } from 'zustand';
import {
  Node,
  Edge,
  Connection,
  addEdge,
  applyNodeChanges,
  applyEdgeChanges,
  NodeChange,
  EdgeChange,
} from 'reactflow';
import { BaseNodeData } from '../lib/canvas/schema';
import { buildIntent } from '../lib/canvas/intent';

interface CanvasState {
  // Graph state
  nodes: Node<BaseNodeData>[];
  edges: Edge[];

  // Selection
  selectedNodeId: string | null;

  // Validation
  validationErrors: string[];
  validationWarnings: string[];

  // Actions
  setNodes: (nodes: Node<BaseNodeData>[]) => void;
  setEdges: (edges: Edge[]) => void;
  onNodesChange: (changes: NodeChange[]) => void;
  onEdgesChange: (changes: EdgeChange[]) => void;
  onConnect: (connection: Connection) => void;

  addNode: (type: string, position: { x: number; y: number }) => void;
  removeNode: (nodeId: string) => void;
  updateNodeConfig: (nodeId: string, config: Record<string, any>) => void;

  selectNode: (nodeId: string | null) => void;

  validate: () => boolean;
  buildIntent: () => any;

  loadFromConfig: (config: any) => void;
  loadTemplate: (templateName: string) => void;
  clear: () => void;
}

export const useCanvasStore = create<CanvasState>((set, get) => ({
  nodes: [],
  edges: [],
  selectedNodeId: null,
  validationErrors: [],
  validationWarnings: [],

  setNodes: (nodes) => set({ nodes }),
  setEdges: (edges) => set({ edges }),

  onNodesChange: (changes) => {
    set({
      nodes: applyNodeChanges(changes, get().nodes) as Node<BaseNodeData>[],
    });
  },

  onEdgesChange: (changes) => {
    set({
      edges: applyEdgeChanges(changes, get().edges),
    });
  },

  onConnect: (connection) => {
    // Validate connection types match
    const { nodes } = get();
    const sourceNode = nodes.find(n => n.id === connection.source);
    const targetNode = nodes.find(n => n.id === connection.target);

    if (!sourceNode || !targetNode) return;

    const sourcePort = sourceNode.data.ports.outputs.find(
      p => p.id === connection.sourceHandle
    );
    const targetPort = targetNode.data.ports.inputs.find(
      p => p.id === connection.targetHandle
    );

    if (!sourcePort || !targetPort) return;

    // Check type compatibility
    if (targetPort.dataType !== 'any' && sourcePort.dataType !== targetPort.dataType) {
      console.warn(`Type mismatch: ${sourcePort.dataType}${targetPort.dataType}`);
      return;
    }

    set({
      edges: addEdge(
        {
          ...connection,
          type: 'dataFlow',
          data: { dataType: sourcePort.dataType },
        },
        get().edges
      ),
    });
  },

  addNode: (type, position) => {
    const { nodes } = get();
    const id = `${type}_${Date.now()}`;

    // Get default data for node type
    const defaults = getNodeDefaults(type);

    const newNode: Node<BaseNodeData> = {
      id,
      type,
      position,
      data: {
        ...defaults,
        label: `${defaults.label} ${nodes.filter(n => n.type === type).length + 1}`,
      },
    };

    set({ nodes: [...nodes, newNode] });
  },

  removeNode: (nodeId) => {
    set({
      nodes: get().nodes.filter(n => n.id !== nodeId),
      edges: get().edges.filter(e => e.source !== nodeId && e.target !== nodeId),
      selectedNodeId: get().selectedNodeId === nodeId ? null : get().selectedNodeId,
    });
  },

  updateNodeConfig: (nodeId, config) => {
    set({
      nodes: get().nodes.map(node =>
        node.id === nodeId
          ? { ...node, data: { ...node.data, config: { ...node.data.config, ...config } } }
          : node
      ),
    });
  },

  selectNode: (nodeId) => set({ selectedNodeId: nodeId }),

  validate: () => {
    const { nodes, edges } = get();
    const errors: string[] = [];
    const warnings: string[] = [];

    // Check for required nodes
    const hasModel = nodes.some(n => n.type === 'model');
    const hasSolver = nodes.some(n => n.type === 'solver');
    const hasObjective = nodes.some(n => n.type === 'objective');
    const hasAlgorithm = nodes.some(n => n.type === 'algorithm');

    if (!hasModel) errors.push('Missing model node');
    if (!hasSolver) errors.push('Missing solver node');
    if (!hasObjective) errors.push('Missing objective node');
    if (!hasAlgorithm) warnings.push('No algorithm specified - will use default');

    // Check for unconnected required ports
    nodes.forEach(node => {
      node.data.ports.inputs
        .filter(p => p.required)
        .forEach(port => {
          const hasConnection = edges.some(
            e => e.target === node.id && e.targetHandle === port.id
          );
          if (!hasConnection) {
            errors.push(`${node.data.label}: Missing required input "${port.label}"`);
          }
        });
    });

    // Check for objectives without constraints (warning)
    const hasConstraint = nodes.some(n => n.type === 'constraint');
    if (hasObjective && !hasConstraint) {
      const objectiveNodes = nodes.filter(n => n.type === 'objective');
      const hasMassObjective = objectiveNodes.some(
        n => (n.data as any).config?.name?.toLowerCase().includes('mass')
      );
      if (hasMassObjective) {
        warnings.push('Mass objective without constraint may produce paper-thin designs');
      }
    }

    set({
      validationErrors: errors,
      validationWarnings: warnings,
      nodes: nodes.map(node => ({
        ...node,
        data: {
          ...node.data,
          validation: {
            isValid: !errors.some(e => e.includes(node.data.label)),
            errors: errors.filter(e => e.includes(node.data.label)),
            warnings: warnings.filter(w => w.includes(node.data.label)),
          },
        },
      })),
    });

    return errors.length === 0;
  },

  buildIntent: () => {
    const { nodes, edges } = get();
    return buildIntent(nodes, edges);
  },

  loadFromConfig: (config) => {
    // Convert optimization_config.json to canvas nodes
    // Implementation details...
  },

  loadTemplate: (templateName) => {
    // Load preset template
    // Implementation details...
  },

  clear: () => set({ nodes: [], edges: [], selectedNodeId: null }),
}));

function getNodeDefaults(type: string): BaseNodeData {
  // Return default data for each node type
  const defaults: Record<string, BaseNodeData> = {
    model: {
      label: 'Model',
      category: 'input',
      ports: {
        inputs: [],
        outputs: [{ id: 'model', label: 'Model', dataType: 'model', direction: 'output' }],
      },
      config: { path: '', type: 'nx_part' },
    },
    solver: {
      label: 'Solver',
      category: 'process',
      ports: {
        inputs: [{ id: 'model', label: 'Model', dataType: 'model', direction: 'input', required: true }],
        outputs: [{ id: 'results', label: 'Results', dataType: 'results', direction: 'output' }],
      },
      config: { solver: 'nastran', solution: 101 },
    },
    extractor: {
      label: 'Extractor',
      category: 'extract',
      ports: {
        inputs: [{ id: 'results', label: 'Results', dataType: 'results', direction: 'input', required: true }],
        outputs: [{ id: 'value', label: 'Value', dataType: 'scalar', direction: 'output' }],
      },
      config: { physics: '', aggregation: 'max' },
    },
    objective: {
      label: 'Objective',
      category: 'logic',
      ports: {
        inputs: [{ id: 'value', label: 'Value', dataType: 'scalar', direction: 'input', required: true }],
        outputs: [{ id: 'objective', label: 'Objective', dataType: 'config', direction: 'output' }],
      },
      config: { name: '', direction: 'minimize' },
    },
    constraint: {
      label: 'Constraint',
      category: 'logic',
      ports: {
        inputs: [{ id: 'value', label: 'Value', dataType: 'scalar', direction: 'input', required: true }],
        outputs: [{ id: 'constraint', label: 'Constraint', dataType: 'config', direction: 'output' }],
      },
      config: { name: '', type: 'upper' },
    },
    algorithm: {
      label: 'Algorithm',
      category: 'optimize',
      ports: {
        inputs: [
          { id: 'objectives', label: 'Objectives', dataType: 'config', direction: 'input', required: true, multiple: true },
          { id: 'constraints', label: 'Constraints', dataType: 'config', direction: 'input', multiple: true },
        ],
        outputs: [{ id: 'config', label: 'Config', dataType: 'config', direction: 'output' }],
      },
      config: { method: 'auto', trials: 100 },
    },
    designVar: {
      label: 'Design Variable',
      category: 'input',
      ports: {
        inputs: [],
        outputs: [{ id: 'var', label: 'Variable', dataType: 'config', direction: 'output' }],
      },
      config: { name: '', type: 'continuous' },
    },
    surrogate: {
      label: 'Surrogate',
      category: 'optimize',
      ports: {
        inputs: [{ id: 'config', label: 'Config', dataType: 'config', direction: 'input' }],
        outputs: [{ id: 'config', label: 'Config', dataType: 'config', direction: 'output' }],
      },
      config: { type: 'auto' },
    },
  };

  return defaults[type] || defaults.model;
}

[Document continues with remaining sections...]


19. Task Breakdown

Phase 0: MCP Chat Foundation

## P0.1 - Create MCP Server Scaffold
Priority: CRITICAL
Estimated: 2-3 hours

Files:
- CREATE: mcp-server/atomizer-tools/package.json
- CREATE: mcp-server/atomizer-tools/tsconfig.json
- CREATE: mcp-server/atomizer-tools/src/index.ts
- CREATE: mcp-server/atomizer-tools/src/utils/paths.ts

Tasks:
1. Create directory structure
2. Initialize npm package
3. Configure TypeScript
4. Create server entry point
5. Test: npm run build succeeds
6. Test: server starts without error

Acceptance:
- [ ] npm install completes
- [ ] npm run build succeeds
- [ ] Server runs and logs startup message

---

## P0.2 - Implement Study Tools
Priority: HIGH
Estimated: 3-4 hours
Dependencies: P0.1

Files:
- CREATE: mcp-server/atomizer-tools/src/tools/study.ts

Tools:
1. list_studies - List all studies with status
2. get_study_status - Detailed study info
3. create_study - Format intent for Claude

Acceptance:
- [ ] list_studies returns array of studies
- [ ] get_study_status returns config + trials
- [ ] create_study formats proper prompt

---

## P0.3 - Implement Optimization Tools
Priority: HIGH
Estimated: 2-3 hours
Dependencies: P0.1

Files:
- CREATE: mcp-server/atomizer-tools/src/tools/optimization.ts

Tools:
1. run_optimization - Start optimization subprocess
2. stop_optimization - Graceful termination
3. get_optimization_status - Running state + progress

Acceptance:
- [ ] Can start optimization
- [ ] Can stop running optimization
- [ ] Status reflects actual state

---

## P0.4 - Implement Analysis Tools
Priority: HIGH
Estimated: 3-4 hours
Dependencies: P0.1

Files:
- CREATE: mcp-server/atomizer-tools/src/tools/analysis.ts

Tools:
1. get_trial_data - Query with filters
2. analyze_convergence - Trend metrics
3. compare_trials - Side-by-side comparison
4. get_best_design - Best design with params

Acceptance:
- [ ] All queries work on test database
- [ ] Convergence analysis produces valid metrics
- [ ] Comparison returns structured data

---

## P0.5 - Implement Reporting Tools
Priority: MEDIUM
Estimated: 2 hours
Dependencies: P0.1

Files:
- CREATE: mcp-server/atomizer-tools/src/tools/reporting.ts

Tools:
1. generate_report - Markdown report
2. export_data - CSV/JSON export

Acceptance:
- [ ] Report generates valid markdown
- [ ] Export creates valid files

---

## P0.6 - Implement Physics Tools
Priority: MEDIUM
Estimated: 2 hours
Dependencies: P0.1

Files:
- CREATE: mcp-server/atomizer-tools/src/tools/physics.ts

Tools:
1. explain_physics - FEA concept explanation
2. recommend_method - Algorithm recommendation
3. query_extractors - List available extractors

Acceptance:
- [ ] Explanations are contextual
- [ ] Recommendations consider problem type

---

## P0.7 - Implement Admin Tools (Power Mode)
Priority: MEDIUM
Estimated: 3-4 hours
Dependencies: P0.2-P0.6

Files:
- CREATE: mcp-server/atomizer-tools/src/tools/admin.ts

Tools:
1. edit_file - Modify codebase files
2. create_extractor - Generate new extractor
3. run_shell_command - Execute commands
4. search_codebase - Search files

Acceptance:
- [ ] Only available in power mode
- [ ] File edits work correctly
- [ ] Commands execute safely

---

## P0.8 - Build and Register MCP Server
Priority: HIGH
Estimated: 1-2 hours
Dependencies: P0.2-P0.7

Tasks:
1. npm run build
2. Test all tools
3. Create MCP config file
4. Test with claude --mcp-config

Acceptance:
- [ ] Build succeeds
- [ ] Claude can list tools
- [ ] Tools execute correctly

---

## P0.9 - Create Backend Session Manager
Priority: CRITICAL
Estimated: 4-5 hours
Dependencies: P0.8

Files:
- CREATE: backend/api/services/session_manager.py
- CREATE: backend/api/services/conversation_store.py
- CREATE: backend/api/services/context_builder.py

Components:
1. SessionManager - Claude subprocess lifecycle
2. ConversationStore - SQLite persistence
3. ContextBuilder - System prompt generation

Acceptance:
- [ ] Sessions persist across messages
- [ ] Context includes study state
- [ ] Mode switching works

---

## P0.10 - Update Backend Routes
Priority: HIGH
Estimated: 2-3 hours
Dependencies: P0.9

Files:
- UPDATE: backend/api/routes/claude.py
- UPDATE: backend/main.py

Changes:
1. Session creation endpoint
2. WebSocket message handler
3. Mode switching endpoint
4. Lifespan handlers

Acceptance:
- [ ] WebSocket connects
- [ ] Messages stream correctly
- [ ] Sessions cleanup properly

---

## P0.11 - Update Frontend Chat Components
Priority: HIGH
Estimated: 3-4 hours
Dependencies: P0.10

Files:
- CREATE: frontend/src/components/chat/ModeToggle.tsx
- CREATE: frontend/src/components/chat/ToolCallCard.tsx
- UPDATE: frontend/src/components/chat/ChatPane.tsx
- UPDATE: frontend/src/components/chat/ChatMessage.tsx
- UPDATE: frontend/src/hooks/useChat.ts

Changes:
1. Mode toggle UI
2. Tool call visualization
3. WebSocket integration
4. Session management

Acceptance:
- [ ] Mode toggle works
- [ ] Tool calls display
- [ ] Streaming works
- [ ] Sessions persist

---

## P0.12 - Integration Testing
Priority: HIGH
Estimated: 2-3 hours
Dependencies: P0.11

Tests:
1. Create session → send message → receive response
2. Tool call → visualize → show result
3. Mode switch → confirm → new capabilities
4. Page refresh → restore session

Acceptance:
- [ ] Full conversation flow works
- [ ] No regressions in existing features

Phase 1: Canvas - React Flow

## P1.1 - Install Dependencies
Priority: HIGH
Estimated: 30 min

Commands:
npm install reactflow zustand
npm install -D @types/reactflow

---

## P1.2 - Create Canvas Schema
Priority: HIGH
Estimated: 2-3 hours

Files:
- CREATE: frontend/src/lib/canvas/schema.ts
- CREATE: frontend/src/lib/canvas/intent.ts
- CREATE: frontend/src/lib/canvas/validation.ts

---

## P1.3 - Create Base Node Component
Priority: HIGH
Estimated: 2 hours

Files:
- CREATE: frontend/src/components/canvas/nodes/BaseNode.tsx

---

## P1.4 - Create Node Types
Priority: HIGH
Estimated: 4-5 hours

Files:
- CREATE: frontend/src/components/canvas/nodes/ModelNode.tsx
- CREATE: frontend/src/components/canvas/nodes/SolverNode.tsx
- CREATE: frontend/src/components/canvas/nodes/ExtractorNode.tsx
- CREATE: frontend/src/components/canvas/nodes/ObjectiveNode.tsx
- CREATE: frontend/src/components/canvas/nodes/ConstraintNode.tsx
- CREATE: frontend/src/components/canvas/nodes/AlgorithmNode.tsx
- CREATE: frontend/src/components/canvas/nodes/DesignVarNode.tsx
- CREATE: frontend/src/components/canvas/nodes/SurrogateNode.tsx

---

## P1.5 - Create Node Palette
Priority: HIGH
Estimated: 2 hours

Files:
- CREATE: frontend/src/components/canvas/palette/NodePalette.tsx
- CREATE: frontend/src/components/canvas/palette/NodeCategory.tsx

---

## P1.6 - Create Canvas State Management
Priority: HIGH
Estimated: 3-4 hours

Files:
- CREATE: frontend/src/hooks/useCanvas.ts
- CREATE: frontend/src/context/CanvasContext.tsx

---

## P1.7 - Create Main Canvas Component
Priority: HIGH
Estimated: 3-4 hours

Files:
- CREATE: frontend/src/components/canvas/AtomizerCanvas.tsx

---

## P1.8 - Create Config Panels
Priority: MEDIUM
Estimated: 3-4 hours

Files:
- CREATE: frontend/src/components/canvas/panels/NodeConfigPanel.tsx
- CREATE: frontend/src/components/canvas/panels/ValidationPanel.tsx

---

## P1.9 - Integrate Canvas with Layout
Priority: HIGH
Estimated: 2 hours

Files:
- UPDATE: frontend/src/pages/StudyView.tsx
- UPDATE: frontend/src/components/layout/MainLayout.tsx
- CREATE: frontend/src/pages/CanvasView.tsx

---

## P1.10 - Canvas Testing
Priority: HIGH
Estimated: 2-3 hours

Tests:
1. Add nodes via drag-drop
2. Connect nodes
3. Validate graph
4. Serialize to intent

Phase 2: LLM Integration

## P2.1 - Create Canvas Intent Tool
Priority: HIGH
Estimated: 2 hours

Files:
- CREATE: mcp-server/atomizer-tools/src/tools/canvas.ts

---

## P2.2 - Create Canvas-Chat Bridge
Priority: HIGH
Estimated: 3-4 hours

Files:
- CREATE: frontend/src/hooks/useCanvasChat.ts

---

## P2.3 - Implement Intent Processing
Priority: HIGH
Estimated: 3-4 hours

Update context_builder.py to handle canvas intents

---

## P2.4 - Integration Testing
Priority: HIGH
Estimated: 2-3 hours

Tests:
1. Build graph → submit → Claude processes
2. Claude responses → canvas updates
3. Validation feedback loop

Phase 3: Bidirectional Sync

## P3.1 - Chat → Canvas Updates
Priority: MEDIUM
Estimated: 3-4 hours

When Claude mentions creating/modifying study, update canvas

---

## P3.2 - Interview Mode → Canvas Population
Priority: MEDIUM
Estimated: 3-4 hours

As interview progresses, build graph visually

---

## P3.3 - Config Import
Priority: MEDIUM
Estimated: 2-3 hours

Load existing optimization_config.json into canvas

---

## P3.4 - Real-time Sync Testing
Priority: HIGH
Estimated: 2-3 hours

Phase 4: Templates & Polish

## P4.1 - Create Template Library
Priority: LOW
Estimated: 3-4 hours

Templates:
- Mass minimization
- Multi-objective
- Turbo mode
- Mirror Zernike

---

## P4.2 - Add Template Selector
Priority: LOW
Estimated: 2 hours

---

## P4.3 - Polish & Animations
Priority: LOW
Estimated: 2-3 hours

---

## P4.4 - Documentation
Priority: MEDIUM
Estimated: 2-3 hours

20. Appendices

A. MCP Configuration File

{
  "mcpServers": {
    "atomizer": {
      "command": "node",
      "args": ["C:/Users/antoi/Atomizer/mcp-server/atomizer-tools/dist/index.js"],
      "env": {
        "ATOMIZER_MODE": "user",
        "ATOMIZER_ROOT": "C:/Users/antoi/Atomizer"
      }
    },
    "siemens-docs": {
      "command": "node",
      "args": ["C:/Users/antoi/Atomizer/mcp-server/dist/index.js"]
    }
  }
}

B. Physics to Extractor Mapping

Physics Primary Extractor ID Notes
mass extract_mass_from_bdf E4 Default
mass (CAD) extract_mass_from_expression E5 When BDF unavailable
stress extract_solid_stress E3 Von Mises
displacement extract_displacement E1 Nodal
frequency extract_frequency E2 Modal
zernike_wfe extract_zernike_wfe_standard E8 Standard
zernike_wfe_rel extract_zernike_wfe_relative E9 Relative
buckling extract_buckling E23 SOL 105
temperature extract_temperature E15 Thermal

C. Intent Schema (Complete)

See Section 2.3 for full schema definition.

D. Error Codes

Code Category Description
E001 Session Session not found
E002 Session Session expired
E003 Auth Invalid mode switch
E010 Study Study not found
E011 Study Invalid configuration
E012 Study Missing required files
E020 Opt Already running
E021 Opt Failed to start
E022 Opt Solver error
E030 Canvas Invalid graph
E031 Canvas Missing required node
E032 Canvas Invalid connection

Document Information

Total Estimated Hours: 80-100 hours Recommended Sprint Duration: 2-3 weeks Critical Path: P0.1 → P0.8 → P0.9 → P0.10 → P0.11 → P1.1-P1.9 → P2.1-P2.4

For Ralph Loop Execution:

  1. Read this document fully before starting
  2. Execute phases in order
  3. Test after each phase
  4. Commit after each phase completes
  5. Record any issues to LAC

Document Version: 1.0 Last Updated: January 8, 2026 Prepared for: Ralph Loop Autonomous Development