# 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](RALPH_LOOP_MASTER_PROMPT.md) for detailed file inventory and testing checklist. --- ## Table of Contents 1. [Executive Summary](#1-executive-summary) 2. [Architecture Philosophy](#2-architecture-philosophy) 3. [Current State Analysis](#3-current-state-analysis) 4. [Target Architecture](#4-target-architecture) 5. [Phase 0: Foundation - MCP Chat](#5-phase-0-foundation---mcp-chat) 6. [Phase 1: Canvas - React Flow Integration](#6-phase-1-canvas---react-flow-integration) 7. [Phase 2: LLM Intelligence Layer](#7-phase-2-llm-intelligence-layer) 8. [Phase 3: Bidirectional Sync](#8-phase-3-bidirectional-sync) 9. [Phase 4: Polish & Templates](#9-phase-4-polish--templates) 10. [Phase 5: Tauri Desktop (Future)](#10-phase-5-tauri-desktop-future) 11. [Integration with Existing Systems](#11-integration-with-existing-systems) 12. [Data Flow Specifications](#12-data-flow-specifications) 13. [Component Specifications](#13-component-specifications) 14. [API Specifications](#14-api-specifications) 15. [Security Model](#15-security-model) 16. [Testing Strategy](#16-testing-strategy) 17. [Deployment Strategy](#17-deployment-strategy) 18. [Risk Analysis](#18-risk-analysis) 19. [Task Breakdown](#19-task-breakdown) 20. [Appendices](#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: ```typescript // 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; }; // 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:** ```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:** ```typescript // 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):** ```typescript // 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; }; 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):** ```typescript // 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 = 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):** ```typescript // 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:** ```bash cd atomizer-dashboard/frontend npm install reactflow zustand npm install -D @types/reactflow ``` ### 6.3 Node Types ```typescript // 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; 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; }; } 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 ```tsx // 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 { icon: React.ReactNode; color: string; children?: React.ReactNode; } export const BaseNode: React.FC = ({ data, selected, icon, color, children, }) => { return (
{/* Input Handles */} {data.ports.inputs.map((port, idx) => ( ))} {/* Header */}
{icon}
{data.label}
{data.category}
{/* Content */} {children} {/* Validation Status */} {data.validation && !data.validation.isValid && (
{data.validation.errors[0]}
)} {/* Output Handles */} {data.ports.outputs.map((port, idx) => ( ))}
); }; function getDataTypeColor(type: string): string { const colors: Record = { 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; } ``` ```tsx // 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> = (props) => { const { data } = props; const selectedPhysics = PHYSICS_OPTIONS.find(p => p.value === data.config.physics); return ( } color="#f97316">
Physics: {selectedPhysics?.label || 'Not set'}
{data.config.aggregation && (
Aggregation: {data.config.aggregation}
)} {data.config.extractor_id && (
→ {data.config.extractor_id}
)}
); }; // 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 ```typescript // 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[]; edges: Edge[]; // Selection selectedNodeId: string | null; // Validation validationErrors: string[]; validationWarnings: string[]; // Actions setNodes: (nodes: Node[]) => 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) => void; selectNode: (nodeId: string | null) => void; validate: () => boolean; buildIntent: () => any; loadFromConfig: (config: any) => void; loadTemplate: (templateName: string) => void; clear: () => void; } export const useCanvasStore = create((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[], }); }, 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 = { 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 = { 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 ```markdown ## 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 ```markdown ## 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 ```markdown ## 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 ```markdown ## 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 ```markdown ## 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 ```json { "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*