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

2816 lines
98 KiB
Markdown

# 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<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:**
```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<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):**
```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<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):**
```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<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
```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<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;
}
```
```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<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
```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<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
```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*