docs: Major documentation overhaul - restructure folders, update tagline, add Getting Started guide
- Restructure docs/ folder (remove numeric prefixes): - 04_USER_GUIDES -> guides/ - 05_API_REFERENCE -> api/ - 06_PHYSICS -> physics/ - 07_DEVELOPMENT -> development/ - 08_ARCHIVE -> archive/ - 09_DIAGRAMS -> diagrams/ - Replace tagline 'Talk, don't click' with 'LLM-driven optimization framework' in 9 files - Create comprehensive docs/GETTING_STARTED.md: - Prerequisites and quick setup - Project structure overview - First study tutorial (Claude or manual) - Dashboard usage guide - Neural acceleration introduction - Rewrite docs/00_INDEX.md with correct paths and modern structure - Archive obsolete files: - 01_PROTOCOLS.md -> archive/historical/01_PROTOCOLS_legacy.md - 03_GETTING_STARTED.md -> archive/historical/ - ATOMIZER_PODCAST_BRIEFING.md -> archive/marketing/ - Update timestamps to 2026-01-20 across all key files - Update .gitignore to exclude docs/generated/ - Version bump: ATOMIZER_CONTEXT v1.8 -> v2.0
This commit is contained in:
@@ -750,7 +750,7 @@ The system prompt is structured to maximize KV-cache hits:
|
||||
```
|
||||
[SECTION 1: STABLE - Never changes]
|
||||
- Atomizer identity and capabilities
|
||||
- Core principles (talk don't click)
|
||||
- Core principles (LLM-driven optimization)
|
||||
- Tool schemas and definitions
|
||||
- Base protocol routing table
|
||||
|
||||
@@ -780,7 +780,7 @@ You are assisting with **Atomizer**, an LLM-first FEA optimization framework.
|
||||
- Neural acceleration (600-1000x speedup)
|
||||
|
||||
## Principles
|
||||
1. Talk, don't click - users describe goals in plain language
|
||||
1. LLM-driven - users describe goals in plain language
|
||||
2. Never modify master models - work on copies
|
||||
3. Always validate before running
|
||||
4. Document everything
|
||||
|
||||
@@ -50,7 +50,7 @@ Implement an **Interview Mode** that systematically gathers engineering requirem
|
||||
|
||||
| Principle | Implementation |
|
||||
|-----------|----------------|
|
||||
| **Talk, don't click** | Natural conversation, not forms |
|
||||
| **Conversational interface** | Natural conversation, not forms |
|
||||
| **Intelligence first** | Auto-detect what's possible, ask about intent |
|
||||
| **No assumptions** | Ask instead of guessing on critical decisions |
|
||||
| **Adaptive depth** | Simple studies = fewer questions |
|
||||
|
||||
737
docs/plans/CLAUDE_CANVAS_INTEGRATION_V2.md
Normal file
737
docs/plans/CLAUDE_CANVAS_INTEGRATION_V2.md
Normal file
@@ -0,0 +1,737 @@
|
||||
# Claude + Canvas Integration V2
|
||||
|
||||
## The Vision
|
||||
|
||||
**Side-by-side LLM + Canvas** where:
|
||||
1. **Claude talks → Canvas updates in real-time** (user sees nodes appear/change)
|
||||
2. **User tweaks Canvas → Claude sees changes** (bi-directional sync)
|
||||
3. **Full Claude Code-level power** through the dashboard chat
|
||||
4. **Interview-driven study creation** entirely through chat
|
||||
|
||||
The user can:
|
||||
- Describe what they want in natural language
|
||||
- Watch the canvas build itself
|
||||
- Make quick manual tweaks
|
||||
- Continue the conversation with Claude seeing their changes
|
||||
- Have Claude execute protocols, create files, run optimizations
|
||||
|
||||
---
|
||||
|
||||
## Current State vs Target
|
||||
|
||||
### What We Have Now
|
||||
|
||||
```
|
||||
┌──────────────────┐ ┌──────────────────┐
|
||||
│ Chat Panel │ │ Canvas │
|
||||
│ (Power Mode) │ │ (SpecRenderer) │
|
||||
├──────────────────┤ ├──────────────────┤
|
||||
│ - Anthropic API │ │ - Loads spec │
|
||||
│ - Write tools │ │ - User edits │
|
||||
│ - spec_modified │--->│ - Auto-refresh │
|
||||
│ events │ │ on event │
|
||||
└──────────────────┘ └──────────────────┘
|
||||
│ │
|
||||
│ No real-time │
|
||||
│ canvas state │
|
||||
│ in Claude context │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
**Gaps:**
|
||||
1. Claude doesn't see current canvas state in real-time
|
||||
2. No interview engine for guided study creation
|
||||
3. Limited tool set (no file ops, no protocol execution)
|
||||
4. No streaming for tool calls
|
||||
5. Mode switching requires reconnection
|
||||
|
||||
### What We Want
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────────┐
|
||||
│ ATOMIZER DASHBOARD │
|
||||
├────────────────────────────┬──────────────────────────────────────┤
|
||||
│ │ │
|
||||
│ CHAT PANEL │ CANVAS │
|
||||
│ (Atomizer Assistant) │ (SpecRenderer) │
|
||||
│ │ │
|
||||
│ ┌──────────────────────┐ │ ┌────────────────────────────────┐ │
|
||||
│ │ "Create a bracket │ │ │ │ │
|
||||
│ │ optimization with │ │ │ [DV: thickness] │ │
|
||||
│ │ mass and stiffness" │ │ │ │ │ │
|
||||
│ └──────────────────────┘ │ │ ▼ │ │
|
||||
│ │ │ │ [Model Node] │ │
|
||||
│ ▼ │ │ │ │ │
|
||||
│ ┌──────────────────────┐ │ │ ▼ │ │
|
||||
│ │ 🔧 Adding thickness │ │ │ [Ext: mass]──>[Obj: min] │ │
|
||||
│ │ 🔧 Adding mass ext │◄─┼──┤ [Ext: disp]──>[Obj: min] │ │
|
||||
│ │ 🔧 Adding objective │ │ │ │ │
|
||||
│ │ │ │ │ (nodes appear in real-time) │ │
|
||||
│ │ ✓ Study configured! │ │ │ │ │
|
||||
│ └──────────────────────┘ │ └────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────────────┐ │ User can click any node to edit │
|
||||
│ │ Claude sees the │ │ Claude sees user's edits │
|
||||
│ │ canvas state and │◄─┼──────────────────────────────────────│
|
||||
│ │ user's manual edits │ │ │
|
||||
│ └──────────────────────┘ │ │
|
||||
└────────────────────────────┴──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### 1. WebSocket Hub (Bi-directional Sync)
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ WebSocket Hub │
|
||||
│ (Single Connection)│
|
||||
└─────────┬───────────┘
|
||||
│
|
||||
┌────────────────────┼────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Chat Panel │ │ Canvas │ │ Spec Store │
|
||||
│ │ │ │ │ │
|
||||
│ - Send messages │ │ - User edits │ │ - Single source │
|
||||
│ - Receive text │ │ - Node add/del │ │ of truth │
|
||||
│ - See tool calls│ │ - Edge changes │ │ - Validates │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
|
||||
Message Types:
|
||||
Client → Server:
|
||||
{ type: "message", content: "..." } # Chat message
|
||||
{ type: "canvas_edit", patch: {...} } # User made canvas change
|
||||
{ type: "set_study", study_id: "..." } # Switch study
|
||||
{ type: "ping" } # Heartbeat
|
||||
|
||||
Server → Client:
|
||||
{ type: "text", content: "...", done: false } # Streaming text
|
||||
{ type: "tool_start", tool: "...", input: {...} }
|
||||
{ type: "tool_result", tool: "...", result: "..." }
|
||||
{ type: "spec_updated", spec: {...} } # Full spec after change
|
||||
{ type: "canvas_patch", patch: {...} } # Incremental update
|
||||
{ type: "done" } # Response complete
|
||||
{ type: "pong" } # Heartbeat response
|
||||
```
|
||||
|
||||
### 2. Enhanced Claude Agent
|
||||
|
||||
The `AtomizerClaudeAgent` needs to be more like **Claude Code**:
|
||||
|
||||
```python
|
||||
class AtomizerClaudeAgent:
|
||||
"""Full-power Claude agent with Claude Code-like capabilities"""
|
||||
|
||||
def __init__(self, study_id: Optional[str] = None):
|
||||
self.client = anthropic.Anthropic()
|
||||
self.study_id = study_id
|
||||
self.spec_store = SpecStore(study_id) # Real-time spec access
|
||||
self.interview_state = None # For guided creation
|
||||
self.tools = self._define_full_tools()
|
||||
|
||||
async def chat_stream(
|
||||
self,
|
||||
message: str,
|
||||
conversation: List[Dict],
|
||||
canvas_state: Optional[Dict] = None # Current canvas from frontend
|
||||
) -> AsyncGenerator[Dict, None]:
|
||||
"""Stream responses with tool calls"""
|
||||
|
||||
# Build context with current canvas state
|
||||
system = self._build_system_prompt(canvas_state)
|
||||
|
||||
# Stream the response
|
||||
with self.client.messages.stream(
|
||||
model="claude-sonnet-4-20250514",
|
||||
max_tokens=8192,
|
||||
system=system,
|
||||
messages=conversation + [{"role": "user", "content": message}],
|
||||
tools=self.tools
|
||||
) as stream:
|
||||
for event in stream:
|
||||
if event.type == "content_block_delta":
|
||||
if event.delta.type == "text_delta":
|
||||
yield {"type": "text", "content": event.delta.text}
|
||||
|
||||
elif event.type == "content_block_start":
|
||||
if event.content_block.type == "tool_use":
|
||||
yield {
|
||||
"type": "tool_start",
|
||||
"tool": event.content_block.name,
|
||||
"input": {} # Will be completed
|
||||
}
|
||||
|
||||
# Handle tool calls after stream
|
||||
response = stream.get_final_message()
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
result = await self._execute_tool(block.name, block.input)
|
||||
yield {
|
||||
"type": "tool_result",
|
||||
"tool": block.name,
|
||||
"result": result["result"],
|
||||
"spec_changed": result.get("spec_changed", False)
|
||||
}
|
||||
|
||||
# If spec changed, send the updated spec
|
||||
if result.get("spec_changed"):
|
||||
yield {
|
||||
"type": "spec_updated",
|
||||
"spec": self.spec_store.get_dict()
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Full Tool Set
|
||||
|
||||
Claude needs more tools to match Claude Code power:
|
||||
|
||||
```python
|
||||
FULL_TOOLS = [
|
||||
# === READ TOOLS ===
|
||||
"read_study_config", # Read atomizer_spec.json
|
||||
"query_trials", # Query optimization database
|
||||
"list_studies", # List available studies
|
||||
"read_file", # Read any file in study
|
||||
"list_files", # List files in study directory
|
||||
"read_nx_expressions", # Get NX model expressions
|
||||
|
||||
# === WRITE TOOLS (Spec Modification) ===
|
||||
"add_design_variable", # Add DV to spec
|
||||
"add_extractor", # Add extractor (built-in or custom)
|
||||
"add_objective", # Add objective
|
||||
"add_constraint", # Add constraint
|
||||
"update_spec_field", # Update any spec field by path
|
||||
"remove_node", # Remove any node by ID
|
||||
"update_canvas_layout", # Reposition nodes for better layout
|
||||
|
||||
# === STUDY MANAGEMENT ===
|
||||
"create_study", # Create new study directory + spec
|
||||
"clone_study", # Clone existing study
|
||||
"validate_spec", # Validate current spec
|
||||
"migrate_config", # Migrate legacy config to spec v2
|
||||
|
||||
# === OPTIMIZATION CONTROL ===
|
||||
"start_optimization", # Start optimization run
|
||||
"stop_optimization", # Stop running optimization
|
||||
"get_optimization_status",# Check if running, trial count
|
||||
|
||||
# === FILE OPERATIONS ===
|
||||
"write_file", # Write file to study directory
|
||||
"create_directory", # Create directory in study
|
||||
|
||||
# === NX INTEGRATION ===
|
||||
"introspect_model", # Get model info (expressions, features)
|
||||
"suggest_design_vars", # AI-suggest design variables from model
|
||||
|
||||
# === INTERVIEW/GUIDED CREATION ===
|
||||
"start_interview", # Begin guided study creation
|
||||
"process_answer", # Process user's interview answer
|
||||
"get_interview_state", # Get current interview progress
|
||||
]
|
||||
```
|
||||
|
||||
### 4. Interview Engine Integration
|
||||
|
||||
The interview happens **through chat**, not a separate UI:
|
||||
|
||||
```python
|
||||
class InterviewEngine:
|
||||
"""Guided study creation through conversation"""
|
||||
|
||||
PHASES = [
|
||||
("model", "Let's set up your model. What's the path to your NX simulation file?"),
|
||||
("objectives", "What do you want to optimize? (e.g., minimize mass, minimize displacement)"),
|
||||
("design_vars", "Which parameters can I vary? I can suggest some based on your model."),
|
||||
("constraints", "Any constraints to respect? (e.g., max stress, min frequency)"),
|
||||
("method", "I recommend {method} for this problem. Should I configure it?"),
|
||||
("review", "Here's the complete configuration. Ready to create the study?"),
|
||||
]
|
||||
|
||||
def __init__(self, spec_store: SpecStore):
|
||||
self.spec_store = spec_store
|
||||
self.current_phase = 0
|
||||
self.collected_data = {}
|
||||
|
||||
def get_current_question(self) -> str:
|
||||
phase_name, question = self.PHASES[self.current_phase]
|
||||
# Customize question based on collected data
|
||||
if phase_name == "method":
|
||||
method = self._recommend_method()
|
||||
question = question.format(method=method)
|
||||
return question
|
||||
|
||||
def process_answer(self, answer: str) -> Dict:
|
||||
"""Process answer and build spec incrementally"""
|
||||
phase_name, _ = self.PHASES[self.current_phase]
|
||||
|
||||
# Extract structured data from answer
|
||||
extracted = self._extract_for_phase(phase_name, answer)
|
||||
self.collected_data[phase_name] = extracted
|
||||
|
||||
# Update spec with extracted data
|
||||
spec_update = self._apply_to_spec(phase_name, extracted)
|
||||
|
||||
# Advance to next phase
|
||||
self.current_phase += 1
|
||||
|
||||
return {
|
||||
"phase": phase_name,
|
||||
"extracted": extracted,
|
||||
"spec_update": spec_update,
|
||||
"next_question": self.get_current_question() if self.current_phase < len(self.PHASES) else None,
|
||||
"complete": self.current_phase >= len(self.PHASES)
|
||||
}
|
||||
```
|
||||
|
||||
Claude uses the interview through tools:
|
||||
|
||||
```python
|
||||
async def _tool_start_interview(self, params: Dict) -> str:
|
||||
"""Start guided study creation"""
|
||||
self.interview_state = InterviewEngine(self.spec_store)
|
||||
return {
|
||||
"status": "started",
|
||||
"first_question": self.interview_state.get_current_question()
|
||||
}
|
||||
|
||||
async def _tool_process_answer(self, params: Dict) -> str:
|
||||
"""Process user's answer in interview"""
|
||||
if not self.interview_state:
|
||||
return {"error": "No interview in progress"}
|
||||
|
||||
result = self.interview_state.process_answer(params["answer"])
|
||||
|
||||
if result["spec_update"]:
|
||||
# Spec was updated - this will trigger canvas update
|
||||
return {
|
||||
"status": "updated",
|
||||
"spec_changed": True,
|
||||
"next_question": result["next_question"],
|
||||
"complete": result["complete"]
|
||||
}
|
||||
|
||||
return result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend Implementation
|
||||
|
||||
### 1. Unified WebSocket Hook
|
||||
|
||||
```typescript
|
||||
// hooks/useAtomizerSocket.ts
|
||||
export function useAtomizerSocket(studyId: string | undefined) {
|
||||
const [spec, setSpec] = useState<AtomizerSpec | null>(null);
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||
const [isThinking, setIsThinking] = useState(false);
|
||||
const [currentTool, setCurrentTool] = useState<string | null>(null);
|
||||
|
||||
const ws = useRef<WebSocket | null>(null);
|
||||
|
||||
// Single WebSocket connection for everything
|
||||
useEffect(() => {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const host = import.meta.env.DEV ? 'localhost:8001' : window.location.host;
|
||||
ws.current = new WebSocket(`${protocol}//${host}/api/atomizer/ws`);
|
||||
|
||||
ws.current.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
switch (data.type) {
|
||||
case 'text':
|
||||
// Streaming text from Claude
|
||||
setMessages(prev => {
|
||||
const last = prev[prev.length - 1];
|
||||
if (last?.role === 'assistant' && !last.complete) {
|
||||
return [...prev.slice(0, -1), {
|
||||
...last,
|
||||
content: last.content + data.content
|
||||
}];
|
||||
}
|
||||
return [...prev, {
|
||||
id: Date.now().toString(),
|
||||
role: 'assistant',
|
||||
content: data.content,
|
||||
complete: false
|
||||
}];
|
||||
});
|
||||
break;
|
||||
|
||||
case 'tool_start':
|
||||
setCurrentTool(data.tool);
|
||||
// Add tool indicator to chat
|
||||
setMessages(prev => [...prev, {
|
||||
id: Date.now().toString(),
|
||||
role: 'tool',
|
||||
tool: data.tool,
|
||||
status: 'running'
|
||||
}]);
|
||||
break;
|
||||
|
||||
case 'tool_result':
|
||||
setCurrentTool(null);
|
||||
// Update tool message with result
|
||||
setMessages(prev => prev.map(m =>
|
||||
m.role === 'tool' && m.tool === data.tool && m.status === 'running'
|
||||
? { ...m, status: 'complete', result: data.result }
|
||||
: m
|
||||
));
|
||||
break;
|
||||
|
||||
case 'spec_updated':
|
||||
// Canvas gets the new spec - this is the magic!
|
||||
setSpec(data.spec);
|
||||
break;
|
||||
|
||||
case 'done':
|
||||
setIsThinking(false);
|
||||
// Mark last message as complete
|
||||
setMessages(prev => prev.map((m, i) =>
|
||||
i === prev.length - 1 ? { ...m, complete: true } : m
|
||||
));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Set study context
|
||||
if (studyId) {
|
||||
ws.current.onopen = () => {
|
||||
ws.current?.send(JSON.stringify({
|
||||
type: 'set_study',
|
||||
study_id: studyId
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
return () => ws.current?.close();
|
||||
}, [studyId]);
|
||||
|
||||
// Send message
|
||||
const sendMessage = useCallback((content: string) => {
|
||||
if (!ws.current) return;
|
||||
|
||||
setIsThinking(true);
|
||||
setMessages(prev => [...prev, {
|
||||
id: Date.now().toString(),
|
||||
role: 'user',
|
||||
content
|
||||
}]);
|
||||
|
||||
ws.current.send(JSON.stringify({
|
||||
type: 'message',
|
||||
content
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// Notify Claude about canvas edits
|
||||
const notifyCanvasEdit = useCallback((patch: any) => {
|
||||
ws.current?.send(JSON.stringify({
|
||||
type: 'canvas_edit',
|
||||
patch
|
||||
}));
|
||||
}, []);
|
||||
|
||||
return {
|
||||
spec,
|
||||
messages,
|
||||
isThinking,
|
||||
currentTool,
|
||||
sendMessage,
|
||||
notifyCanvasEdit
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Integrated Canvas View
|
||||
|
||||
```typescript
|
||||
// pages/CanvasView.tsx (revised)
|
||||
export function CanvasView() {
|
||||
const { '*': studyId } = useParams();
|
||||
|
||||
// Single hook manages everything
|
||||
const {
|
||||
spec,
|
||||
messages,
|
||||
isThinking,
|
||||
currentTool,
|
||||
sendMessage,
|
||||
notifyCanvasEdit
|
||||
} = useAtomizerSocket(studyId);
|
||||
|
||||
// When user edits canvas, notify Claude
|
||||
const handleSpecChange = useCallback((newSpec: AtomizerSpec) => {
|
||||
// This is called by SpecRenderer when user makes edits
|
||||
notifyCanvasEdit({
|
||||
type: 'spec_replace',
|
||||
spec: newSpec
|
||||
});
|
||||
}, [notifyCanvasEdit]);
|
||||
|
||||
return (
|
||||
<div className="h-screen flex">
|
||||
{/* Canvas - receives spec from WebSocket */}
|
||||
<div className="flex-1">
|
||||
<SpecRenderer
|
||||
spec={spec}
|
||||
onChange={handleSpecChange} // User edits flow back
|
||||
highlightNode={currentTool ? getAffectedNode(currentTool) : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Chat Panel */}
|
||||
<div className="w-96 border-l">
|
||||
<ChatPanel
|
||||
messages={messages}
|
||||
isThinking={isThinking}
|
||||
currentTool={currentTool}
|
||||
onSend={sendMessage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Visual Feedback for Tool Calls
|
||||
|
||||
When Claude calls a tool, the canvas shows visual feedback:
|
||||
|
||||
```typescript
|
||||
// components/canvas/SpecRenderer.tsx
|
||||
function SpecRenderer({ spec, highlightNode, onChange }) {
|
||||
// When a tool is targeting a node, highlight it
|
||||
const getNodeStyle = (nodeId: string) => {
|
||||
if (highlightNode === nodeId) {
|
||||
return {
|
||||
boxShadow: '0 0 0 3px #f59e0b', // Amber glow
|
||||
animation: 'pulse 1s infinite'
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
// When new nodes are added, animate them
|
||||
const [newNodes, setNewNodes] = useState<Set<string>>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
if (spec) {
|
||||
const currentIds = new Set([
|
||||
...spec.design_variables.map(d => d.id),
|
||||
...spec.extractors.map(e => e.id),
|
||||
...spec.objectives.map(o => o.id),
|
||||
...spec.constraints.map(c => c.id)
|
||||
]);
|
||||
|
||||
// Find truly new nodes
|
||||
const added = [...currentIds].filter(id => !prevIds.current.has(id));
|
||||
if (added.length > 0) {
|
||||
setNewNodes(new Set(added));
|
||||
setTimeout(() => setNewNodes(new Set()), 1000); // Clear animation
|
||||
}
|
||||
prevIds.current = currentIds;
|
||||
}
|
||||
}, [spec]);
|
||||
|
||||
// Render with animations
|
||||
return (
|
||||
<ReactFlow nodes={nodes.map(n => ({
|
||||
...n,
|
||||
style: {
|
||||
...getNodeStyle(n.id),
|
||||
...(newNodes.has(n.id) ? { animation: 'slideIn 0.5s ease-out' } : {})
|
||||
}
|
||||
}))} />
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## User Experience Flow
|
||||
|
||||
### Flow 1: Create Study Through Chat
|
||||
|
||||
```
|
||||
User: "Create a bracket optimization. I want to minimize mass while keeping
|
||||
stress below 200 MPa. The thickness can vary from 2mm to 10mm."
|
||||
|
||||
Claude: [Internal: Parse intent, no interview needed - user gave full spec]
|
||||
|
||||
🔧 Creating study "bracket_optimization"...
|
||||
🔧 Adding design variable: thickness [2mm - 10mm]
|
||||
🔧 Adding extractor: mass (BDF mass extraction)
|
||||
🔧 Adding extractor: max_stress (von Mises stress)
|
||||
🔧 Adding objective: minimize mass
|
||||
🔧 Adding constraint: stress <= 200 MPa
|
||||
|
||||
✓ Study created! I've configured:
|
||||
- 1 design variable (thickness)
|
||||
- 2 extractors (mass, stress)
|
||||
- 1 objective (minimize mass)
|
||||
- 1 constraint (max stress)
|
||||
|
||||
The canvas shows your optimization setup. You can:
|
||||
- Click any node to adjust settings
|
||||
- Ask me to add more variables or constraints
|
||||
- Say "run" to start optimization
|
||||
|
||||
[Canvas shows: DV→Model→Extractors→Objectives/Constraints, all connected]
|
||||
```
|
||||
|
||||
### Flow 2: Interview-Guided Creation
|
||||
|
||||
```
|
||||
User: "Help me set up a new optimization"
|
||||
|
||||
Claude: Let's set up your optimization step by step.
|
||||
|
||||
First, what's the path to your NX simulation file?
|
||||
(You can drag & drop or type the path)
|
||||
|
||||
User: "studies/M1_Mirror/model.sim"
|
||||
|
||||
Claude: 🔧 Loading model...
|
||||
[tool: introspect_model]
|
||||
|
||||
Found your model! It has 15 expressions. I noticed these look like
|
||||
good design variables:
|
||||
- rib_thickness (current: 8mm)
|
||||
- mirror_depth (current: 50mm)
|
||||
- support_angle (current: 45°)
|
||||
|
||||
What are you trying to optimize?
|
||||
- Minimize mass?
|
||||
- Minimize wavefront error (WFE)?
|
||||
- Both? (I can set up multi-objective)
|
||||
|
||||
[Canvas shows: Model node appears]
|
||||
|
||||
User: "minimize WFE and mass"
|
||||
|
||||
Claude: 🔧 Adding Zernike WFE extractor
|
||||
🔧 Adding mass extractor
|
||||
🔧 Adding objectives (NSGA-II multi-objective)
|
||||
|
||||
Got it - multi-objective optimization for WFE and mass.
|
||||
|
||||
Which parameters should I vary?
|
||||
I suggest: rib_thickness, mirror_depth, support_angle
|
||||
|
||||
[Canvas shows: Model→WFE Extractor→Obj, Model→Mass Extractor→Obj]
|
||||
|
||||
User: "Yes, use those, but keep support_angle fixed"
|
||||
|
||||
Claude: 🔧 Adding design variable: rib_thickness [4mm - 16mm]
|
||||
🔧 Adding design variable: mirror_depth [30mm - 80mm]
|
||||
|
||||
Perfect. Any constraints?
|
||||
- Maximum mass?
|
||||
- Maximum stress?
|
||||
- Minimum frequency?
|
||||
|
||||
[Canvas shows: 2 DVs connected to Model]
|
||||
|
||||
User: "max stress 150 MPa"
|
||||
|
||||
Claude: 🔧 Adding stress extractor
|
||||
🔧 Adding constraint: stress <= 150 MPa
|
||||
|
||||
✓ Configuration complete! Ready to optimize?
|
||||
|
||||
[Canvas shows: Full optimization flow]
|
||||
```
|
||||
|
||||
### Flow 3: User Edits Canvas, Claude Sees
|
||||
|
||||
```
|
||||
[User clicks thickness DV node, changes max from 10mm to 15mm]
|
||||
|
||||
[WebSocket sends: { type: "canvas_edit", patch: { path: "design_variables.0.bounds.max", value: 15 } }]
|
||||
|
||||
Claude: [Receives canvas edit notification in context]
|
||||
|
||||
User: "Is this range reasonable?"
|
||||
|
||||
Claude: I see you changed thickness max to 15mm. That's quite thick for a
|
||||
bracket - typical ranges are 2-10mm.
|
||||
|
||||
With 15mm max, you might see:
|
||||
- Higher mass designs explored
|
||||
- Potentially better stiffness
|
||||
- Longer optimization time (larger search space)
|
||||
|
||||
Want me to adjust or keep it?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Unified WebSocket (1 week)
|
||||
|
||||
1. **Backend**: Create `/api/atomizer/ws` endpoint
|
||||
- Single connection for chat + spec sync
|
||||
- Streaming response support
|
||||
- Canvas edit notifications
|
||||
|
||||
2. **Frontend**: Create `useAtomizerSocket` hook
|
||||
- Replaces `useChat` + `useSpecWebSocket`
|
||||
- Single source of truth for spec state
|
||||
|
||||
3. **Integration**: Wire SpecRenderer to socket
|
||||
- Receive spec updates from Claude's tools
|
||||
- Send edit notifications back
|
||||
|
||||
### Phase 2: Enhanced Tools (1 week)
|
||||
|
||||
1. Add remaining write tools
|
||||
2. Implement `introspect_model` for NX expression discovery
|
||||
3. Add `create_study` for new study creation
|
||||
4. Add file operation tools
|
||||
|
||||
### Phase 3: Interview Engine (1 week)
|
||||
|
||||
1. Implement `InterviewEngine` class
|
||||
2. Add interview tools to Claude
|
||||
3. Test guided creation flow
|
||||
4. Add smart defaults and recommendations
|
||||
|
||||
### Phase 4: Polish (1 week)
|
||||
|
||||
1. Visual feedback for tool calls
|
||||
2. Node highlight during modification
|
||||
3. Animation for new nodes
|
||||
4. Error recovery and reconnection
|
||||
5. Performance optimization
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
1. **Creation Time**: User can create complete study in <3 minutes through chat
|
||||
2. **Edit Latency**: Canvas updates within 200ms of Claude's tool call
|
||||
3. **Sync Reliability**: 100% of user edits reflected in Claude's context
|
||||
4. **Interview Success**: 90% of studies created through interview are valid
|
||||
|
||||
---
|
||||
|
||||
## Key Differences from Current Implementation
|
||||
|
||||
| Current | Target |
|
||||
|---------|--------|
|
||||
| Separate chat/canvas WebSockets | Single unified WebSocket |
|
||||
| Claude doesn't see canvas state | Real-time canvas state in context |
|
||||
| Manual spec refresh | Automatic spec push on changes |
|
||||
| No interview engine | Guided creation through chat |
|
||||
| Limited tools | Full Claude Code-like tool set |
|
||||
| Mode switching breaks connection | Seamless power mode |
|
||||
|
||||
---
|
||||
|
||||
*This is the architecture that makes Atomizer truly powerful - where Claude and Canvas work together as one system.*
|
||||
1239
docs/plans/CLAUDE_CANVAS_PROJECT.md
Normal file
1239
docs/plans/CLAUDE_CANVAS_PROJECT.md
Normal file
File diff suppressed because it is too large
Load Diff
693
docs/plans/DASHBOARD_CLAUDE_CODE_INTEGRATION.md
Normal file
693
docs/plans/DASHBOARD_CLAUDE_CODE_INTEGRATION.md
Normal file
@@ -0,0 +1,693 @@
|
||||
# Dashboard Claude Code Integration Plan
|
||||
|
||||
**Date**: January 16, 2026
|
||||
**Status**: 🟢 IMPLEMENTED
|
||||
**Priority**: CRITICAL
|
||||
**Implemented**: January 16, 2026
|
||||
|
||||
---
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The dashboard chat assistant is **fundamentally underpowered** compared to Claude Code CLI. Users expect the same level of intelligence, proactivity, and capability when interacting with the dashboard as they get from the terminal.
|
||||
|
||||
### Current Experience (Terminal - Claude Code CLI)
|
||||
```
|
||||
User: "Add 10 new design variables to the M1 mirror study"
|
||||
|
||||
Claude Code:
|
||||
1. Reads optimization_config.json
|
||||
2. Understands the current structure
|
||||
3. Adds 10 variables with intelligent defaults
|
||||
4. ACTUALLY MODIFIES the file
|
||||
5. Shows the diff
|
||||
6. Can immediately run/test
|
||||
```
|
||||
|
||||
### Current Experience (Dashboard Chat)
|
||||
```
|
||||
User: "Add 10 new design variables"
|
||||
|
||||
Dashboard Chat:
|
||||
1. Calls MCP tool canvas_add_node
|
||||
2. Returns JSON instruction
|
||||
3. Frontend SHOULD apply it but doesn't
|
||||
4. Nothing visible happens
|
||||
5. User frustrated
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
### Issue 1: MCP Tools Don't Actually Modify Anything
|
||||
|
||||
The current MCP tools (`canvas_add_node`, etc.) just return instructions like:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"modification": {
|
||||
"action": "add_node",
|
||||
"nodeType": "designVar",
|
||||
"data": {...}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The **frontend is supposed to receive and apply these**, but:
|
||||
- WebSocket message handling may not process tool results
|
||||
- No automatic application of modifications
|
||||
- User sees "success" message but nothing changes
|
||||
|
||||
### Issue 2: Claude API vs Claude Code CLI
|
||||
|
||||
| Capability | Claude API (Dashboard) | Claude Code CLI (Terminal) |
|
||||
|------------|------------------------|---------------------------|
|
||||
| Read files | Via MCP tool | Native |
|
||||
| Write files | Via MCP tool (limited) | Native |
|
||||
| Run commands | Via MCP tool (limited) | Native |
|
||||
| Edit in place | NO | YES |
|
||||
| Git operations | NO | YES |
|
||||
| Multi-step reasoning | Limited | Full |
|
||||
| Tool chaining | Awkward | Natural |
|
||||
| Context window | 200k | Unlimited (summarization) |
|
||||
|
||||
### Issue 3: Model Capability Gap
|
||||
|
||||
Dashboard uses Claude API (likely Sonnet or Haiku for cost). Terminal uses **Opus 4.5** with full Claude Code capabilities.
|
||||
|
||||
---
|
||||
|
||||
## Proposed Solution: Claude Code CLI Backend
|
||||
|
||||
Instead of MCP tools calling Python scripts, **spawn actual Claude Code CLI sessions** in the backend that have full power.
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ DASHBOARD FRONTEND │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Canvas Builder │ Chat Panel │ Study Views │ Results │
|
||||
└────────┬────────────────┬─────────────────────────────────────┬─┘
|
||||
│ │ │
|
||||
│ WebSocket │ REST API │
|
||||
▼ ▼ │
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ BACKEND (FastAPI) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ CLAUDE CODE SESSION MANAGER │ │
|
||||
│ │ │ │
|
||||
│ │ - Spawns claude CLI processes │ │
|
||||
│ │ - Maintains conversation context │ │
|
||||
│ │ - Streams output back to frontend │ │
|
||||
│ │ - Has FULL Atomizer codebase access │ │
|
||||
│ │ - Uses Opus 4.5 model │ │
|
||||
│ │ - Can edit files, run commands, modify studies │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ ATOMIZER CODEBASE │ │
|
||||
│ │ │ │
|
||||
│ │ studies/ optimization_engine/ │ │
|
||||
│ │ M1_Mirror/ extractors/ │ │
|
||||
│ │ optimization_config.json runner.py │ │
|
||||
│ │ run_optimization.py ... │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Key Changes
|
||||
|
||||
1. **Backend spawns Claude Code CLI** instead of calling Claude API
|
||||
2. **Full file system access** - Claude can read/write any file
|
||||
3. **Full command execution** - Run Python, git, npm, etc.
|
||||
4. **Opus 4.5 model** - Same intelligence as terminal
|
||||
5. **Stream output** - Real-time feedback to user
|
||||
6. **Canvas sync** - After Claude modifies files, canvas reloads from config
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Claude Code CLI Session Manager
|
||||
|
||||
**File**: `atomizer-dashboard/backend/api/services/claude_code_session.py`
|
||||
|
||||
```python
|
||||
"""
|
||||
Claude Code CLI Session Manager
|
||||
|
||||
Spawns actual Claude Code CLI processes with full Atomizer access.
|
||||
This gives dashboard users the same power as terminal users.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import AsyncGenerator, Dict, List, Optional
|
||||
|
||||
ATOMIZER_ROOT = Path(__file__).parent.parent.parent.parent.parent
|
||||
|
||||
class ClaudeCodeSession:
|
||||
"""
|
||||
Manages a Claude Code CLI session.
|
||||
|
||||
Unlike MCP tools, this spawns the actual claude CLI which has:
|
||||
- Full file system access
|
||||
- Full command execution
|
||||
- Opus 4.5 model
|
||||
- All Claude Code capabilities
|
||||
"""
|
||||
|
||||
def __init__(self, session_id: str, study_id: Optional[str] = None):
|
||||
self.session_id = session_id
|
||||
self.study_id = study_id
|
||||
self.canvas_state: Optional[Dict] = None # Current canvas state from frontend
|
||||
self.working_dir = ATOMIZER_ROOT
|
||||
if study_id:
|
||||
study_path = ATOMIZER_ROOT / "studies" / study_id
|
||||
if study_path.exists():
|
||||
self.working_dir = study_path
|
||||
|
||||
def set_canvas_state(self, canvas_state: Dict):
|
||||
"""Update canvas state from frontend"""
|
||||
self.canvas_state = canvas_state
|
||||
|
||||
async def send_message(self, message: str) -> AsyncGenerator[str, None]:
|
||||
"""
|
||||
Send message to Claude Code CLI and stream response.
|
||||
|
||||
Uses claude CLI with:
|
||||
- --print for output
|
||||
- --dangerously-skip-permissions for full access (controlled environment)
|
||||
- Runs from Atomizer root to get CLAUDE.md context automatically
|
||||
- Study-specific context injected into prompt
|
||||
"""
|
||||
# Build study-specific context
|
||||
study_context = self._build_study_context() if self.study_id else ""
|
||||
|
||||
# The user's message with study context prepended
|
||||
full_message = f"""## Current Study Context
|
||||
{study_context}
|
||||
|
||||
## User Request
|
||||
{message}
|
||||
|
||||
Remember: You have FULL power to edit files. Make the actual changes, don't just describe them."""
|
||||
|
||||
# Write prompt to a temp file (better than stdin for complex prompts)
|
||||
prompt_file = ATOMIZER_ROOT / f".claude-prompt-{self.session_id}.md"
|
||||
prompt_file.write_text(full_message)
|
||||
|
||||
try:
|
||||
# Spawn claude CLI from ATOMIZER_ROOT so it picks up CLAUDE.md
|
||||
# This gives it full Atomizer context automatically
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
"claude",
|
||||
"--print",
|
||||
"--dangerously-skip-permissions", # Full access in controlled env
|
||||
"-p", str(prompt_file), # Read prompt from file
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
cwd=str(ATOMIZER_ROOT), # Run from root to get CLAUDE.md
|
||||
env={
|
||||
**os.environ,
|
||||
"ATOMIZER_STUDY": self.study_id or "",
|
||||
"ATOMIZER_STUDY_PATH": str(self.working_dir),
|
||||
}
|
||||
)
|
||||
|
||||
# Stream output
|
||||
stdout, stderr = await process.communicate()
|
||||
|
||||
if stdout:
|
||||
yield stdout.decode()
|
||||
if stderr and process.returncode != 0:
|
||||
yield f"\n[Error]: {stderr.decode()}"
|
||||
|
||||
finally:
|
||||
# Clean up prompt file
|
||||
if prompt_file.exists():
|
||||
prompt_file.unlink()
|
||||
|
||||
def _build_system_prompt(self) -> str:
|
||||
"""Build Atomizer-aware system prompt with full context"""
|
||||
|
||||
# Load CLAUDE.md for Atomizer system instructions
|
||||
claude_md_path = ATOMIZER_ROOT / "CLAUDE.md"
|
||||
claude_md_content = ""
|
||||
if claude_md_path.exists():
|
||||
claude_md_content = claude_md_path.read_text()
|
||||
|
||||
# Load study-specific context
|
||||
study_context = ""
|
||||
if self.study_id:
|
||||
study_context = self._build_study_context()
|
||||
|
||||
prompt = f"""# Atomizer Dashboard Assistant
|
||||
|
||||
You are running as the Atomizer Dashboard Assistant with FULL Claude Code CLI capabilities.
|
||||
You have the same power as a terminal Claude Code session.
|
||||
|
||||
## Atomizer System Instructions
|
||||
{claude_md_content[:8000]} # Truncate if too long
|
||||
|
||||
## Your Capabilities
|
||||
|
||||
You can and MUST:
|
||||
- Read and EDIT any file in the codebase
|
||||
- Modify optimization_config.json directly
|
||||
- Update run_optimization.py
|
||||
- Run Python scripts
|
||||
- Execute git commands
|
||||
- Create new studies
|
||||
- Modify existing studies
|
||||
|
||||
When the user asks to add design variables, objectives, or other config changes:
|
||||
1. Read the current config file
|
||||
2. Make the actual modifications using Edit tool
|
||||
3. Save the file
|
||||
4. Report what you changed with a diff
|
||||
|
||||
DO NOT just return instructions - ACTUALLY MAKE THE CHANGES.
|
||||
|
||||
## Current Context
|
||||
|
||||
**Atomizer Root**: {ATOMIZER_ROOT}
|
||||
**Working Directory**: {self.working_dir}
|
||||
|
||||
{study_context}
|
||||
|
||||
## Important Paths
|
||||
|
||||
- Studies: {ATOMIZER_ROOT / 'studies'}
|
||||
- Extractors: {ATOMIZER_ROOT / 'optimization_engine' / 'extractors'}
|
||||
- Protocols: {ATOMIZER_ROOT / 'docs' / 'protocols'}
|
||||
|
||||
## After Making Changes
|
||||
|
||||
After modifying any study files:
|
||||
1. Confirm the changes were saved
|
||||
2. Show the relevant diff
|
||||
3. The dashboard canvas will auto-refresh to reflect your changes
|
||||
"""
|
||||
return prompt
|
||||
|
||||
def _build_study_context(self) -> str:
|
||||
"""Build detailed context for the active study"""
|
||||
context = f"## Active Study: {self.study_id}\n\n"
|
||||
|
||||
# Find and read optimization_config.json
|
||||
config_path = self.working_dir / "1_setup" / "optimization_config.json"
|
||||
if not config_path.exists():
|
||||
config_path = self.working_dir / "optimization_config.json"
|
||||
|
||||
if config_path.exists():
|
||||
import json
|
||||
try:
|
||||
config = json.loads(config_path.read_text())
|
||||
context += f"**Config File**: `{config_path}`\n\n"
|
||||
|
||||
# Design variables summary
|
||||
dvs = config.get("design_variables", [])
|
||||
if dvs:
|
||||
context += "### Design Variables\n\n"
|
||||
context += "| Name | Min | Max | Baseline | Unit |\n"
|
||||
context += "|------|-----|-----|----------|------|\n"
|
||||
for dv in dvs[:15]:
|
||||
name = dv.get("name", dv.get("expression_name", "?"))
|
||||
min_v = dv.get("min", dv.get("lower", "?"))
|
||||
max_v = dv.get("max", dv.get("upper", "?"))
|
||||
baseline = dv.get("baseline", "-")
|
||||
unit = dv.get("units", dv.get("unit", "-"))
|
||||
context += f"| {name} | {min_v} | {max_v} | {baseline} | {unit} |\n"
|
||||
if len(dvs) > 15:
|
||||
context += f"\n*... and {len(dvs) - 15} more*\n"
|
||||
context += "\n"
|
||||
|
||||
# Objectives
|
||||
objs = config.get("objectives", [])
|
||||
if objs:
|
||||
context += "### Objectives\n\n"
|
||||
for obj in objs:
|
||||
name = obj.get("name", "?")
|
||||
direction = obj.get("direction", "minimize")
|
||||
weight = obj.get("weight", 1)
|
||||
context += f"- **{name}**: {direction} (weight: {weight})\n"
|
||||
context += "\n"
|
||||
|
||||
# Extraction method (for Zernike)
|
||||
ext_method = config.get("extraction_method", {})
|
||||
if ext_method:
|
||||
context += "### Extraction Method\n\n"
|
||||
context += f"- Type: {ext_method.get('type', '?')}\n"
|
||||
context += f"- Class: {ext_method.get('class', '?')}\n"
|
||||
if ext_method.get("inner_radius"):
|
||||
context += f"- Inner Radius: {ext_method.get('inner_radius')}\n"
|
||||
context += "\n"
|
||||
|
||||
# Zernike settings
|
||||
zernike = config.get("zernike_settings", {})
|
||||
if zernike:
|
||||
context += "### Zernike Settings\n\n"
|
||||
context += f"- Modes: {zernike.get('n_modes', '?')}\n"
|
||||
context += f"- Filter Low Orders: {zernike.get('filter_low_orders', '?')}\n"
|
||||
context += f"- Subcases: {zernike.get('subcases', [])}\n"
|
||||
context += "\n"
|
||||
|
||||
# Algorithm
|
||||
method = config.get("method", config.get("optimization", {}).get("sampler", "TPE"))
|
||||
max_trials = config.get("max_trials", config.get("optimization", {}).get("n_trials", 100))
|
||||
context += f"### Algorithm\n\n"
|
||||
context += f"- Method: {method}\n"
|
||||
context += f"- Max Trials: {max_trials}\n\n"
|
||||
|
||||
except Exception as e:
|
||||
context += f"*Error reading config: {e}*\n\n"
|
||||
else:
|
||||
context += "*No optimization_config.json found*\n\n"
|
||||
|
||||
# Check for run_optimization.py
|
||||
run_opt_path = self.working_dir / "run_optimization.py"
|
||||
if run_opt_path.exists():
|
||||
context += f"**Run Script**: `{run_opt_path}` (exists)\n\n"
|
||||
|
||||
# Check results
|
||||
db_path = self.working_dir / "3_results" / "study.db"
|
||||
if db_path.exists():
|
||||
context += "**Results Database**: exists\n"
|
||||
# Could query trial count here
|
||||
else:
|
||||
context += "**Results Database**: not found (no optimization run yet)\n"
|
||||
|
||||
return context
|
||||
```
|
||||
|
||||
### Phase 2: WebSocket Handler for Claude Code
|
||||
|
||||
**File**: `atomizer-dashboard/backend/api/routes/claude_code.py`
|
||||
|
||||
```python
|
||||
"""
|
||||
Claude Code WebSocket Routes
|
||||
|
||||
Provides WebSocket endpoint that connects to actual Claude Code CLI.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
||||
from api.services.claude_code_session import ClaudeCodeSession
|
||||
import uuid
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Active sessions
|
||||
sessions: Dict[str, ClaudeCodeSession] = {}
|
||||
|
||||
@router.websocket("/ws/{study_id}")
|
||||
async def claude_code_websocket(websocket: WebSocket, study_id: str = None):
|
||||
"""
|
||||
WebSocket for full Claude Code CLI access.
|
||||
|
||||
This gives dashboard users the SAME power as terminal users.
|
||||
"""
|
||||
await websocket.accept()
|
||||
|
||||
session_id = str(uuid.uuid4())[:8]
|
||||
session = ClaudeCodeSession(session_id, study_id)
|
||||
sessions[session_id] = session
|
||||
|
||||
try:
|
||||
while True:
|
||||
data = await websocket.receive_json()
|
||||
|
||||
if data.get("type") == "message":
|
||||
content = data.get("content", "")
|
||||
|
||||
# Stream response from Claude Code CLI
|
||||
async for chunk in session.send_message(content):
|
||||
await websocket.send_json({
|
||||
"type": "text",
|
||||
"content": chunk,
|
||||
})
|
||||
|
||||
await websocket.send_json({"type": "done"})
|
||||
|
||||
# After response, trigger canvas refresh
|
||||
await websocket.send_json({
|
||||
"type": "refresh_canvas",
|
||||
"study_id": study_id,
|
||||
})
|
||||
|
||||
except WebSocketDisconnect:
|
||||
sessions.pop(session_id, None)
|
||||
```
|
||||
|
||||
### Phase 3: Frontend - Use Claude Code Endpoint
|
||||
|
||||
**File**: `atomizer-dashboard/frontend/src/hooks/useClaudeCode.ts`
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Hook for Claude Code CLI integration
|
||||
*
|
||||
* Connects to backend that spawns actual Claude Code CLI processes.
|
||||
* This gives full power: file editing, command execution, etc.
|
||||
*/
|
||||
|
||||
export function useClaudeCode(studyId?: string) {
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [isThinking, setIsThinking] = useState(false);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
|
||||
// Reload canvas after Claude makes changes
|
||||
const { loadFromConfig } = useCanvasStore();
|
||||
|
||||
useEffect(() => {
|
||||
// Connect to Claude Code WebSocket
|
||||
const ws = new WebSocket(`ws://${location.host}/api/claude-code/ws/${studyId || ''}`);
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.type === 'text') {
|
||||
// Stream Claude's response
|
||||
appendToLastMessage(data.content);
|
||||
}
|
||||
else if (data.type === 'done') {
|
||||
setIsThinking(false);
|
||||
}
|
||||
else if (data.type === 'refresh_canvas') {
|
||||
// Claude made file changes - reload canvas from config
|
||||
reloadCanvasFromStudy(data.study_id);
|
||||
}
|
||||
};
|
||||
|
||||
wsRef.current = ws;
|
||||
return () => ws.close();
|
||||
}, [studyId]);
|
||||
|
||||
const sendMessage = async (content: string) => {
|
||||
setIsThinking(true);
|
||||
addMessage({ role: 'user', content });
|
||||
addMessage({ role: 'assistant', content: '', isStreaming: true });
|
||||
|
||||
wsRef.current?.send(JSON.stringify({
|
||||
type: 'message',
|
||||
content,
|
||||
}));
|
||||
};
|
||||
|
||||
return { messages, isThinking, sendMessage };
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Canvas Auto-Refresh
|
||||
|
||||
When Claude modifies `optimization_config.json`, the canvas should automatically reload:
|
||||
|
||||
```typescript
|
||||
// In AtomizerCanvas.tsx or useCanvasChat.ts
|
||||
|
||||
const reloadCanvasFromStudy = async (studyId: string) => {
|
||||
// Fetch fresh config from backend
|
||||
const response = await fetch(`/api/studies/${studyId}/config`);
|
||||
const config = await response.json();
|
||||
|
||||
// Reload canvas
|
||||
loadFromConfig(config);
|
||||
|
||||
// Notify user
|
||||
showNotification('Canvas updated with Claude\'s changes');
|
||||
};
|
||||
```
|
||||
|
||||
### Phase 5: Smart Prompting for Canvas Context
|
||||
|
||||
When user sends a message from canvas view, include canvas state:
|
||||
|
||||
```typescript
|
||||
const sendCanvasMessage = (userMessage: string) => {
|
||||
const canvasContext = generateCanvasMarkdown();
|
||||
|
||||
const enrichedMessage = `
|
||||
## Current Canvas State
|
||||
${canvasContext}
|
||||
|
||||
## User Request
|
||||
${userMessage}
|
||||
|
||||
When making changes, modify the actual optimization_config.json file.
|
||||
After changes, the canvas will auto-refresh.
|
||||
`;
|
||||
|
||||
sendMessage(enrichedMessage);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Expected Behavior After Implementation
|
||||
|
||||
### Example 1: Add Design Variables
|
||||
```
|
||||
User: "Add 10 new design variables for hole diameters, range 5-25mm"
|
||||
|
||||
Claude Code (in dashboard):
|
||||
1. Reads studies/M1_Mirror/.../optimization_config.json
|
||||
2. Adds 10 entries to design_variables array:
|
||||
- hole_diameter_1: [5, 25] mm
|
||||
- hole_diameter_2: [5, 25] mm
|
||||
- ... (10 total)
|
||||
3. WRITES the file
|
||||
4. Reports: "Added 10 design variables to optimization_config.json"
|
||||
5. Frontend receives "refresh_canvas" signal
|
||||
6. Canvas reloads and shows 10 new nodes
|
||||
7. User sees actual changes
|
||||
```
|
||||
|
||||
### Example 2: Modify Optimization
|
||||
```
|
||||
User: "Change the algorithm to CMA-ES with 500 trials and add a stress constraint < 200 MPa"
|
||||
|
||||
Claude Code (in dashboard):
|
||||
1. Reads config
|
||||
2. Changes method: "TPE" -> "CMA-ES"
|
||||
3. Changes max_trials: 100 -> 500
|
||||
4. Adds constraint: {name: "stress_limit", operator: "<=", value: 200, unit: "MPa"}
|
||||
5. WRITES the file
|
||||
6. Reports changes
|
||||
7. Canvas refreshes with updated algorithm node and new constraint node
|
||||
```
|
||||
|
||||
### Example 3: Complex Multi-File Changes
|
||||
```
|
||||
User: "Add a new Zernike extractor for the secondary mirror and connect it to a new objective"
|
||||
|
||||
Claude Code (in dashboard):
|
||||
1. Reads config
|
||||
2. Adds extractor to extractors array
|
||||
3. Adds objective connected to extractor
|
||||
4. If needed, modifies run_optimization.py to import new extractor
|
||||
5. WRITES all modified files
|
||||
6. Canvas refreshes with new extractor and objective nodes, properly connected
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
### Phase 1: Backend Claude Code Session
|
||||
- [x] Create `claude_code_session.py` with session manager
|
||||
- [x] Implement `send_message()` with CLI spawning
|
||||
- [x] Build Atomizer-aware system prompt
|
||||
- [x] Handle study context (working directory)
|
||||
- [x] Stream output properly
|
||||
|
||||
### Phase 2: WebSocket Route
|
||||
- [x] Create `/api/claude-code/ws/{study_id}` endpoint
|
||||
- [x] Handle message routing
|
||||
- [x] Implement `refresh_canvas` signal
|
||||
- [x] Session cleanup on disconnect
|
||||
|
||||
### Phase 3: Frontend Hook
|
||||
- [x] Create `useClaudeCode.ts` hook
|
||||
- [x] Connect to Claude Code WebSocket
|
||||
- [x] Handle streaming responses
|
||||
- [x] Handle canvas refresh signals
|
||||
|
||||
### Phase 4: Canvas Auto-Refresh
|
||||
- [x] Add `reloadCanvasFromStudy()` function
|
||||
- [x] Wire refresh signal to canvas store
|
||||
- [x] Add loading state during refresh
|
||||
- [x] Show notification on refresh (system message)
|
||||
|
||||
### Phase 5: Chat Panel Integration
|
||||
- [x] Update ChatPanel to use `useClaudeCode`
|
||||
- [x] Include canvas context in messages
|
||||
- [x] Add "Claude Code" indicator in UI (mode toggle)
|
||||
- [x] Show when Claude is editing files
|
||||
|
||||
### Phase 6: Testing
|
||||
- [ ] Test adding design variables
|
||||
- [ ] Test modifying objectives
|
||||
- [ ] Test complex multi-file changes
|
||||
- [ ] Test canvas refresh after changes
|
||||
- [ ] Test error handling
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Files Created/Modified
|
||||
|
||||
**Backend:**
|
||||
- `atomizer-dashboard/backend/api/services/claude_code_session.py` - New session manager
|
||||
- `atomizer-dashboard/backend/api/routes/claude_code.py` - New WebSocket routes
|
||||
- `atomizer-dashboard/backend/api/main.py` - Added claude_code router
|
||||
|
||||
**Frontend:**
|
||||
- `atomizer-dashboard/frontend/src/hooks/useClaudeCode.ts` - New hook for Claude Code CLI
|
||||
- `atomizer-dashboard/frontend/src/components/canvas/AtomizerCanvas.tsx` - Added mode toggle
|
||||
- `atomizer-dashboard/frontend/src/components/chat/ChatMessage.tsx` - Added system message support
|
||||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
Claude Code CLI with `--dangerously-skip-permissions` has full system access. Mitigations:
|
||||
|
||||
1. **Sandboxed environment**: Dashboard runs on user's machine, not public server
|
||||
2. **Study-scoped working directory**: Claude starts in study folder
|
||||
3. **Audit logging**: Log all file modifications
|
||||
4. **User confirmation**: Option to require approval for destructive operations
|
||||
|
||||
---
|
||||
|
||||
## Cost Considerations
|
||||
|
||||
Using Opus 4.5 via Claude Code CLI is more expensive than Sonnet API. Options:
|
||||
|
||||
1. **Default to Sonnet, upgrade on request**: "Use full power mode" button
|
||||
2. **Per-session token budget**: Warn user when approaching limit
|
||||
3. **Cache common operations**: Pre-generate responses for common tasks
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. **Parity with terminal**: Dashboard chat can do everything Claude Code CLI can
|
||||
2. **Real modifications**: Files actually change, not just instructions
|
||||
3. **Canvas sync**: Canvas reflects file changes immediately
|
||||
4. **Intelligent defaults**: Claude makes smart choices without asking clarifying questions
|
||||
5. **Proactive behavior**: Claude anticipates needs and handles edge cases
|
||||
|
||||
---
|
||||
|
||||
*This document to be implemented by Claude Code CLI*
|
||||
863
docs/plans/SAAS_ATOMIZER_ROADMAP.md
Normal file
863
docs/plans/SAAS_ATOMIZER_ROADMAP.md
Normal file
@@ -0,0 +1,863 @@
|
||||
# SaaS-Level Atomizer Roadmap (Revised)
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This roadmap transforms Atomizer into a **SaaS-grade, LLM-assisted structural optimization platform** with the core innovation being **side-by-side Claude + Canvas** integration where:
|
||||
|
||||
1. **Claude talks → Canvas updates in real-time** (user sees nodes appear/change)
|
||||
2. **User tweaks Canvas → Claude sees changes** (bi-directional sync)
|
||||
3. **Full Claude Code-level power** through the dashboard chat
|
||||
4. **Interview-driven study creation** entirely through conversation
|
||||
|
||||
**Vision**: An engineer opens Atomizer, describes their optimization goal, watches the canvas build itself, makes quick tweaks, and starts optimization—all through natural conversation with full visual feedback.
|
||||
|
||||
---
|
||||
|
||||
## The Core Innovation: Unified Claude + Canvas
|
||||
|
||||
The power of Atomizer is the **side-by-side experience**:
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────────┐
|
||||
│ ATOMIZER DASHBOARD │
|
||||
├────────────────────────────┬──────────────────────────────────────┤
|
||||
│ CHAT PANEL │ CANVAS │
|
||||
│ (Atomizer Assistant) │ (SpecRenderer) │
|
||||
│ │ │
|
||||
│ "Create a bracket │ [DV: thickness] │
|
||||
│ optimization with │ │ │
|
||||
│ mass and stiffness" │ ▼ │
|
||||
│ │ │ [Model Node] │
|
||||
│ ▼ │ │ │
|
||||
│ 🔧 Adding thickness │ ▼ │
|
||||
│ 🔧 Adding mass ext ◄───┼──►[Ext: mass]──>[Obj: min mass] │
|
||||
│ 🔧 Adding stiffness ◄───┼──►[Ext: disp]──>[Obj: min disp] │
|
||||
│ ✓ Study configured! │ │
|
||||
│ │ (nodes appear in real-time) │
|
||||
│ User clicks a node ───────┼──► Claude sees the edit │
|
||||
│ │ │
|
||||
└────────────────────────────┴──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Current State vs Target
|
||||
|
||||
### What We Have
|
||||
|
||||
| Component | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| Power Mode WebSocket | ✅ Implemented | `/ws/power` endpoint with write tools |
|
||||
| Write Tools | ✅ Implemented | add_design_variable, add_extractor, etc. |
|
||||
| spec_modified Events | ✅ Implemented | Frontend receives notifications |
|
||||
| Canvas Auto-reload | ✅ Implemented | Triggers on spec_modified |
|
||||
| Streaming Responses | ❌ Missing | Currently waits for full response |
|
||||
| Canvas State → Claude | ❌ Missing | Claude doesn't see current canvas |
|
||||
| Interview Engine | ❌ Missing | No guided creation |
|
||||
| Unified WebSocket | ❌ Missing | Separate connections for chat/spec |
|
||||
|
||||
### Target Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ Unified WebSocket │
|
||||
│ /api/atomizer/ws │
|
||||
└─────────┬───────────┘
|
||||
│
|
||||
Bi-directional Sync
|
||||
│
|
||||
┌────────────────────┼────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Chat Panel │ │ Canvas │ │ Spec Store │
|
||||
│ │ │ │ │ │
|
||||
│ Send messages │ │ User edits → │ │ Single source │
|
||||
│ Stream text │ │ Notify Claude │ │ of truth │
|
||||
│ See tool calls │ │ Receive updates │ │ Validates all │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Unified WebSocket Hub (1-2 weeks)
|
||||
|
||||
### Goal: Single connection for chat + canvas + spec sync
|
||||
|
||||
### 1.1 Backend: Unified WebSocket Endpoint
|
||||
|
||||
```python
|
||||
# atomizer-dashboard/backend/api/routes/atomizer_ws.py
|
||||
|
||||
@router.websocket("/api/atomizer/ws")
|
||||
async def atomizer_websocket(websocket: WebSocket):
|
||||
"""
|
||||
Unified WebSocket for Atomizer Dashboard.
|
||||
|
||||
Handles:
|
||||
- Chat messages with streaming responses
|
||||
- Spec modifications with real-time canvas updates
|
||||
- Canvas edit notifications from user
|
||||
- Study context switching
|
||||
"""
|
||||
await websocket.accept()
|
||||
|
||||
agent = AtomizerClaudeAgent()
|
||||
conversation: List[Dict] = []
|
||||
current_spec: Optional[Dict] = None
|
||||
|
||||
try:
|
||||
while True:
|
||||
data = await websocket.receive_json()
|
||||
|
||||
if data["type"] == "message":
|
||||
# Chat message - stream response
|
||||
async for event in agent.chat_stream(
|
||||
message=data["content"],
|
||||
conversation=conversation,
|
||||
canvas_state=current_spec # Claude sees current canvas!
|
||||
):
|
||||
await websocket.send_json(event)
|
||||
|
||||
# If spec changed, update our local copy
|
||||
if event.get("type") == "spec_updated":
|
||||
current_spec = event["spec"]
|
||||
|
||||
elif data["type"] == "canvas_edit":
|
||||
# User made a manual edit - update spec and tell Claude
|
||||
current_spec = apply_patch(current_spec, data["patch"])
|
||||
# Next message to Claude will include updated spec
|
||||
|
||||
elif data["type"] == "set_study":
|
||||
# Switch study context
|
||||
agent.set_study(data["study_id"])
|
||||
current_spec = agent.load_spec()
|
||||
await websocket.send_json({
|
||||
"type": "spec_updated",
|
||||
"spec": current_spec
|
||||
})
|
||||
|
||||
except WebSocketDisconnect:
|
||||
pass
|
||||
```
|
||||
|
||||
### 1.2 Enhanced Claude Agent with Streaming
|
||||
|
||||
```python
|
||||
class AtomizerClaudeAgent:
|
||||
"""Full-power Claude agent with Claude Code-like capabilities"""
|
||||
|
||||
async def chat_stream(
|
||||
self,
|
||||
message: str,
|
||||
conversation: List[Dict],
|
||||
canvas_state: Optional[Dict] = None
|
||||
) -> AsyncGenerator[Dict, None]:
|
||||
"""Stream responses with tool calls"""
|
||||
|
||||
# Build system prompt with current canvas state
|
||||
system = self._build_system_prompt()
|
||||
if canvas_state:
|
||||
system += self._format_canvas_context(canvas_state)
|
||||
|
||||
messages = conversation + [{"role": "user", "content": message}]
|
||||
|
||||
# Use streaming API
|
||||
with self.client.messages.stream(
|
||||
model="claude-sonnet-4-20250514",
|
||||
max_tokens=8192,
|
||||
system=system,
|
||||
messages=messages,
|
||||
tools=self.tools
|
||||
) as stream:
|
||||
current_text = ""
|
||||
|
||||
for event in stream:
|
||||
if event.type == "content_block_delta":
|
||||
if hasattr(event.delta, "text"):
|
||||
current_text += event.delta.text
|
||||
yield {"type": "text", "content": event.delta.text, "done": False}
|
||||
|
||||
# Get final message for tool calls
|
||||
response = stream.get_final_message()
|
||||
|
||||
# Process tool calls
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
yield {"type": "tool_start", "tool": block.name, "input": block.input}
|
||||
|
||||
result = await self._execute_tool(block.name, block.input)
|
||||
|
||||
yield {"type": "tool_result", "tool": block.name, "result": result["preview"]}
|
||||
|
||||
if result.get("spec_changed"):
|
||||
yield {"type": "spec_updated", "spec": self.spec_store.get()}
|
||||
|
||||
yield {"type": "done"}
|
||||
|
||||
def _format_canvas_context(self, spec: Dict) -> str:
|
||||
"""Format current canvas state for Claude's context"""
|
||||
lines = ["\n## Current Canvas State\n"]
|
||||
|
||||
if spec.get("design_variables"):
|
||||
lines.append(f"**Design Variables ({len(spec['design_variables'])}):**")
|
||||
for dv in spec["design_variables"]:
|
||||
lines.append(f" - {dv['name']}: [{dv['bounds']['min']}, {dv['bounds']['max']}]")
|
||||
|
||||
if spec.get("extractors"):
|
||||
lines.append(f"\n**Extractors ({len(spec['extractors'])}):**")
|
||||
for ext in spec["extractors"]:
|
||||
lines.append(f" - {ext['name']} ({ext['type']})")
|
||||
|
||||
if spec.get("objectives"):
|
||||
lines.append(f"\n**Objectives ({len(spec['objectives'])}):**")
|
||||
for obj in spec["objectives"]:
|
||||
lines.append(f" - {obj['name']} ({obj['direction']})")
|
||||
|
||||
if spec.get("constraints"):
|
||||
lines.append(f"\n**Constraints ({len(spec['constraints'])}):**")
|
||||
for con in spec["constraints"]:
|
||||
lines.append(f" - {con['name']} {con['operator']} {con['threshold']}")
|
||||
|
||||
lines.append("\nThe user can see this canvas. When you modify it, they see changes in real-time.")
|
||||
|
||||
return "\n".join(lines)
|
||||
```
|
||||
|
||||
### 1.3 Frontend: Unified Hook
|
||||
|
||||
```typescript
|
||||
// hooks/useAtomizerSocket.ts
|
||||
export function useAtomizerSocket(studyId?: string) {
|
||||
const [spec, setSpec] = useState<AtomizerSpec | null>(null);
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [isThinking, setIsThinking] = useState(false);
|
||||
const [streamingText, setStreamingText] = useState("");
|
||||
const [activeTool, setActiveTool] = useState<string | null>(null);
|
||||
|
||||
const ws = useRef<WebSocket | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const url = `ws://localhost:8001/api/atomizer/ws`;
|
||||
ws.current = new WebSocket(url);
|
||||
|
||||
ws.current.onopen = () => {
|
||||
if (studyId) {
|
||||
ws.current?.send(JSON.stringify({ type: "set_study", study_id: studyId }));
|
||||
}
|
||||
};
|
||||
|
||||
ws.current.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
switch (data.type) {
|
||||
case "text":
|
||||
setStreamingText(prev => prev + data.content);
|
||||
break;
|
||||
|
||||
case "tool_start":
|
||||
setActiveTool(data.tool);
|
||||
break;
|
||||
|
||||
case "tool_result":
|
||||
setActiveTool(null);
|
||||
break;
|
||||
|
||||
case "spec_updated":
|
||||
setSpec(data.spec); // Canvas updates automatically!
|
||||
break;
|
||||
|
||||
case "done":
|
||||
// Finalize message
|
||||
setMessages(prev => [...prev, {
|
||||
role: "assistant",
|
||||
content: streamingText
|
||||
}]);
|
||||
setStreamingText("");
|
||||
setIsThinking(false);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return () => ws.current?.close();
|
||||
}, [studyId]);
|
||||
|
||||
const sendMessage = useCallback((content: string) => {
|
||||
setIsThinking(true);
|
||||
setMessages(prev => [...prev, { role: "user", content }]);
|
||||
ws.current?.send(JSON.stringify({ type: "message", content }));
|
||||
}, []);
|
||||
|
||||
const notifyCanvasEdit = useCallback((path: string, value: any) => {
|
||||
ws.current?.send(JSON.stringify({
|
||||
type: "canvas_edit",
|
||||
patch: { path, value }
|
||||
}));
|
||||
}, []);
|
||||
|
||||
return {
|
||||
spec,
|
||||
messages,
|
||||
streamingText,
|
||||
isThinking,
|
||||
activeTool,
|
||||
sendMessage,
|
||||
notifyCanvasEdit
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Full Tool Set (1-2 weeks)
|
||||
|
||||
### Goal: Claude Code-level power through dashboard
|
||||
|
||||
### 2.1 Tool Categories
|
||||
|
||||
```python
|
||||
ATOMIZER_TOOLS = {
|
||||
# === SPEC MODIFICATION (Already Implemented) ===
|
||||
"add_design_variable": "Add a design variable to the optimization",
|
||||
"add_extractor": "Add a physics extractor (mass, stress, displacement, custom)",
|
||||
"add_objective": "Add an optimization objective",
|
||||
"add_constraint": "Add a constraint",
|
||||
"update_spec_field": "Update any field in the spec by JSON path",
|
||||
"remove_node": "Remove a node from the spec",
|
||||
|
||||
# === READ/QUERY ===
|
||||
"read_study_config": "Read the full atomizer_spec.json",
|
||||
"query_trials": "Query optimization trial data",
|
||||
"list_studies": "List all available studies",
|
||||
"get_optimization_status": "Check if optimization is running",
|
||||
|
||||
# === STUDY MANAGEMENT (New) ===
|
||||
"create_study": "Create a new study directory with atomizer_spec.json",
|
||||
"clone_study": "Clone an existing study as a starting point",
|
||||
"validate_spec": "Validate the current spec for errors",
|
||||
|
||||
# === NX INTEGRATION (New) ===
|
||||
"introspect_model": "Analyze NX model for expressions and features",
|
||||
"suggest_design_vars": "AI-suggest design variables from model",
|
||||
"list_model_expressions": "List all expressions in the NX model",
|
||||
|
||||
# === OPTIMIZATION CONTROL (New) ===
|
||||
"start_optimization": "Start the optimization run",
|
||||
"stop_optimization": "Stop a running optimization",
|
||||
|
||||
# === INTERVIEW (New) ===
|
||||
"start_interview": "Begin guided study creation interview",
|
||||
"get_interview_progress": "Get current interview state",
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Create Study Tool
|
||||
|
||||
```python
|
||||
async def _tool_create_study(self, params: Dict) -> Dict:
|
||||
"""Create a new study with atomizer_spec.json"""
|
||||
study_name = params["name"]
|
||||
study_dir = STUDIES_DIR / study_name
|
||||
|
||||
# Create directory structure
|
||||
study_dir.mkdir(parents=True, exist_ok=True)
|
||||
(study_dir / "1_setup").mkdir(exist_ok=True)
|
||||
(study_dir / "2_iterations").mkdir(exist_ok=True)
|
||||
(study_dir / "3_results").mkdir(exist_ok=True)
|
||||
|
||||
# Create initial spec
|
||||
spec = {
|
||||
"meta": {
|
||||
"version": "2.0",
|
||||
"study_name": study_name,
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"created_by": "claude_agent"
|
||||
},
|
||||
"model": {
|
||||
"sim": {"path": params.get("model_path", ""), "solver": "nastran"}
|
||||
},
|
||||
"design_variables": [],
|
||||
"extractors": [],
|
||||
"objectives": [],
|
||||
"constraints": [],
|
||||
"optimization": {
|
||||
"algorithm": {"type": "TPE"},
|
||||
"budget": {"max_trials": 100}
|
||||
},
|
||||
"canvas": {"edges": [], "layout_version": "2.0"}
|
||||
}
|
||||
|
||||
# Save spec
|
||||
spec_path = study_dir / "atomizer_spec.json"
|
||||
with open(spec_path, "w") as f:
|
||||
json.dump(spec, f, indent=2)
|
||||
|
||||
# Update agent's study context
|
||||
self.study_id = study_name
|
||||
self.study_dir = study_dir
|
||||
|
||||
return {
|
||||
"preview": f"✓ Created study '{study_name}' at {study_dir}",
|
||||
"spec_changed": True,
|
||||
"study_id": study_name
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 NX Introspection Tool
|
||||
|
||||
```python
|
||||
async def _tool_introspect_model(self, params: Dict) -> Dict:
|
||||
"""Analyze NX model for design variable candidates"""
|
||||
model_path = params.get("model_path") or self._find_model_path()
|
||||
|
||||
if not model_path or not Path(model_path).exists():
|
||||
return {"preview": "✗ Model file not found", "spec_changed": False}
|
||||
|
||||
# Use NX session to get expressions
|
||||
expressions = await self._get_nx_expressions(model_path)
|
||||
|
||||
# Classify expressions as potential DVs
|
||||
candidates = []
|
||||
for expr in expressions:
|
||||
score = self._score_dv_candidate(expr)
|
||||
if score > 0.5:
|
||||
candidates.append({
|
||||
"name": expr["name"],
|
||||
"value": expr["value"],
|
||||
"formula": expr.get("formula", ""),
|
||||
"score": score,
|
||||
"suggested_bounds": self._suggest_bounds(expr)
|
||||
})
|
||||
|
||||
# Sort by score
|
||||
candidates.sort(key=lambda x: x["score"], reverse=True)
|
||||
|
||||
return {
|
||||
"preview": f"Found {len(expressions)} expressions, {len(candidates)} are DV candidates",
|
||||
"expressions": expressions,
|
||||
"candidates": candidates[:10], # Top 10
|
||||
"spec_changed": False
|
||||
}
|
||||
|
||||
def _score_dv_candidate(self, expr: Dict) -> float:
|
||||
"""Score expression as design variable candidate"""
|
||||
score = 0.0
|
||||
name = expr["name"].lower()
|
||||
|
||||
# Geometric parameters score high
|
||||
if any(kw in name for kw in ["thickness", "width", "height", "radius", "diameter", "depth"]):
|
||||
score += 0.4
|
||||
|
||||
# Numeric with reasonable value
|
||||
if isinstance(expr["value"], (int, float)) and expr["value"] > 0:
|
||||
score += 0.2
|
||||
|
||||
# Not a formula (pure number)
|
||||
if not expr.get("formula") or expr["formula"] == str(expr["value"]):
|
||||
score += 0.2
|
||||
|
||||
# Common design parameter names
|
||||
if any(kw in name for kw in ["rib", "web", "flange", "support", "angle"]):
|
||||
score += 0.2
|
||||
|
||||
return min(score, 1.0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Interview Engine (1-2 weeks)
|
||||
|
||||
### Goal: Guided study creation through conversation
|
||||
|
||||
### 3.1 Interview Engine
|
||||
|
||||
```python
|
||||
class InterviewEngine:
|
||||
"""Guided study creation through conversation"""
|
||||
|
||||
PHASES = [
|
||||
("welcome", "What kind of optimization do you want to set up?"),
|
||||
("model", "What's the path to your NX simulation file (.sim)?"),
|
||||
("objectives", "What do you want to optimize? (e.g., minimize mass, minimize displacement)"),
|
||||
("design_vars", "Which parameters should vary? I can suggest some from your model."),
|
||||
("constraints", "Any constraints to respect? (e.g., max stress ≤ 200 MPa)"),
|
||||
("method", "I recommend {method} for this. Sound good?"),
|
||||
("review", "Here's your configuration. Ready to create the study?"),
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self.phase_index = 0
|
||||
self.collected = {}
|
||||
self.spec_builder = SpecBuilder()
|
||||
|
||||
def get_current_question(self) -> str:
|
||||
phase, question = self.PHASES[self.phase_index]
|
||||
|
||||
# Dynamic question customization
|
||||
if phase == "method":
|
||||
method = self._recommend_method()
|
||||
question = question.format(method=method)
|
||||
elif phase == "design_vars" and self.collected.get("model_expressions"):
|
||||
candidates = self.collected["model_expressions"][:5]
|
||||
question += f"\n\nI found these candidates: {', '.join(c['name'] for c in candidates)}"
|
||||
|
||||
return question
|
||||
|
||||
def process_answer(self, answer: str) -> Dict:
|
||||
"""Process user's answer and advance interview"""
|
||||
phase, _ = self.PHASES[self.phase_index]
|
||||
|
||||
# Extract structured data based on phase
|
||||
extracted = self._extract_for_phase(phase, answer)
|
||||
self.collected[phase] = extracted
|
||||
|
||||
# Build spec incrementally
|
||||
spec_changes = self.spec_builder.apply(phase, extracted)
|
||||
|
||||
# Advance
|
||||
self.phase_index += 1
|
||||
complete = self.phase_index >= len(self.PHASES)
|
||||
|
||||
return {
|
||||
"phase_completed": phase,
|
||||
"extracted": extracted,
|
||||
"spec_changes": spec_changes,
|
||||
"next_question": None if complete else self.get_current_question(),
|
||||
"complete": complete,
|
||||
"spec": self.spec_builder.get_spec() if complete else None
|
||||
}
|
||||
|
||||
def _extract_for_phase(self, phase: str, answer: str) -> Dict:
|
||||
"""Extract structured data from natural language answer"""
|
||||
if phase == "model":
|
||||
# Extract file path
|
||||
return {"path": self._extract_path(answer)}
|
||||
|
||||
elif phase == "objectives":
|
||||
# Extract objectives
|
||||
objectives = []
|
||||
if "mass" in answer.lower() or "weight" in answer.lower():
|
||||
direction = "minimize" if "minimize" in answer.lower() or "reduce" in answer.lower() else "minimize"
|
||||
objectives.append({"name": "mass", "direction": direction})
|
||||
if "displacement" in answer.lower() or "stiff" in answer.lower():
|
||||
objectives.append({"name": "max_displacement", "direction": "minimize"})
|
||||
if "stress" in answer.lower():
|
||||
objectives.append({"name": "max_stress", "direction": "minimize"})
|
||||
if "wfe" in answer.lower() or "wavefront" in answer.lower():
|
||||
objectives.append({"name": "wfe", "direction": "minimize"})
|
||||
return {"objectives": objectives}
|
||||
|
||||
elif phase == "constraints":
|
||||
# Extract constraints
|
||||
constraints = []
|
||||
import re
|
||||
# Pattern: "stress < 200 MPa" or "max stress <= 200"
|
||||
stress_match = re.search(r'stress[^0-9]*([<>=]+)\s*(\d+)', answer.lower())
|
||||
if stress_match:
|
||||
constraints.append({
|
||||
"name": "max_stress",
|
||||
"operator": stress_match.group(1),
|
||||
"threshold": float(stress_match.group(2))
|
||||
})
|
||||
return {"constraints": constraints}
|
||||
|
||||
return {"raw": answer}
|
||||
|
||||
def _recommend_method(self) -> str:
|
||||
"""Recommend optimization method based on collected info"""
|
||||
objectives = self.collected.get("objectives", {}).get("objectives", [])
|
||||
if len(objectives) > 1:
|
||||
return "NSGA-II (multi-objective)"
|
||||
return "TPE (Bayesian optimization)"
|
||||
```
|
||||
|
||||
### 3.2 Interview Tool Integration
|
||||
|
||||
```python
|
||||
async def _tool_start_interview(self, params: Dict) -> Dict:
|
||||
"""Start guided study creation"""
|
||||
self.interview = InterviewEngine()
|
||||
question = self.interview.get_current_question()
|
||||
|
||||
return {
|
||||
"preview": f"Starting interview.\n\n{question}",
|
||||
"interview_started": True,
|
||||
"spec_changed": False
|
||||
}
|
||||
|
||||
async def _tool_interview_answer(self, params: Dict) -> Dict:
|
||||
"""Process interview answer"""
|
||||
if not self.interview:
|
||||
return {"preview": "No interview in progress", "spec_changed": False}
|
||||
|
||||
result = self.interview.process_answer(params["answer"])
|
||||
|
||||
response = f"Got it: {result['phase_completed']}\n\n"
|
||||
|
||||
if result["spec_changes"]:
|
||||
response += "Updated configuration:\n"
|
||||
for change in result["spec_changes"]:
|
||||
response += f" ✓ {change}\n"
|
||||
|
||||
if result["next_question"]:
|
||||
response += f"\n{result['next_question']}"
|
||||
elif result["complete"]:
|
||||
response += "\n✓ Interview complete! Creating study..."
|
||||
# Auto-create the study
|
||||
self.spec_store.set(result["spec"])
|
||||
|
||||
return {
|
||||
"preview": response,
|
||||
"spec_changed": result["complete"],
|
||||
"complete": result["complete"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Visual Polish (1 week)
|
||||
|
||||
### Goal: Beautiful, responsive canvas updates
|
||||
|
||||
### 4.1 Tool Call Visualization
|
||||
|
||||
```typescript
|
||||
// components/chat/ToolCallIndicator.tsx
|
||||
function ToolCallIndicator({ tool, status }: { tool: string; status: 'running' | 'complete' }) {
|
||||
const icons: Record<string, JSX.Element> = {
|
||||
add_design_variable: <Variable className="w-4 h-4" />,
|
||||
add_extractor: <Cpu className="w-4 h-4" />,
|
||||
add_objective: <Target className="w-4 h-4" />,
|
||||
add_constraint: <Lock className="w-4 h-4" />,
|
||||
create_study: <FolderPlus className="w-4 h-4" />,
|
||||
introspect_model: <Search className="w-4 h-4" />,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`flex items-center gap-2 px-3 py-2 rounded-lg ${
|
||||
status === 'running'
|
||||
? 'bg-amber-500/10 text-amber-400 border border-amber-500/20'
|
||||
: 'bg-green-500/10 text-green-400 border border-green-500/20'
|
||||
}`}>
|
||||
{status === 'running' ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<Check className="w-4 h-4" />
|
||||
)}
|
||||
{icons[tool] || <Wrench className="w-4 h-4" />}
|
||||
<span className="text-sm font-medium">
|
||||
{formatToolName(tool)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Canvas Node Animation
|
||||
|
||||
```typescript
|
||||
// components/canvas/AnimatedNode.tsx
|
||||
function AnimatedNode({ data, isNew, isHighlighted }) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={isNew ? { scale: 0, opacity: 0 } : false}
|
||||
animate={{
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
boxShadow: isHighlighted
|
||||
? '0 0 0 3px rgba(245, 158, 11, 0.5)'
|
||||
: 'none'
|
||||
}}
|
||||
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
|
||||
className="node-container"
|
||||
>
|
||||
{/* Node content */}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 Connection Line Animation
|
||||
|
||||
```typescript
|
||||
// Draw animated line when Claude adds an edge
|
||||
function AnimatedEdge({ source, target, isNew }) {
|
||||
return (
|
||||
<motion.path
|
||||
d={getBezierPath({ source, target })}
|
||||
initial={isNew ? { pathLength: 0, opacity: 0 } : false}
|
||||
animate={{ pathLength: 1, opacity: 1 }}
|
||||
transition={{ duration: 0.5, ease: 'easeOut' }}
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
fill="none"
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## User Experience Flows
|
||||
|
||||
### Flow 1: Quick Creation (Experienced User)
|
||||
|
||||
```
|
||||
User: "Create bracket optimization, minimize mass, thickness 2-10mm, max stress 200 MPa"
|
||||
|
||||
Claude: [Parses complete intent]
|
||||
🔧 Creating study "bracket_optimization"
|
||||
🔧 Adding design variable: thickness [2mm - 10mm]
|
||||
🔧 Adding extractor: mass
|
||||
🔧 Adding extractor: max_stress
|
||||
🔧 Adding objective: minimize mass
|
||||
🔧 Adding constraint: stress ≤ 200 MPa
|
||||
|
||||
✓ Created! Canvas shows your setup. Click any node to adjust.
|
||||
|
||||
[Canvas animates: DV appears → Model → Extractors → Objectives/Constraints]
|
||||
[Total time: ~5 seconds]
|
||||
```
|
||||
|
||||
### Flow 2: Guided Interview (New User)
|
||||
|
||||
```
|
||||
User: "Help me set up an optimization"
|
||||
|
||||
Claude: What kind of optimization do you want to set up?
|
||||
(e.g., bracket stiffness, mirror WFE, beam stress)
|
||||
|
||||
User: "Mirror surface quality optimization"
|
||||
|
||||
Claude: Got it - mirror optimization!
|
||||
|
||||
What's the path to your NX simulation file?
|
||||
(You can drag & drop or paste the path)
|
||||
|
||||
User: "studies/M1_Mirror/model.sim"
|
||||
|
||||
Claude: 🔧 Loading model...
|
||||
|
||||
Found your model with 15 expressions. These look like good design variables:
|
||||
- rib_thickness (8mm)
|
||||
- mirror_depth (50mm)
|
||||
- support_angle (45°)
|
||||
|
||||
What are you trying to optimize?
|
||||
|
||||
[Canvas: Model node appears]
|
||||
|
||||
User: "Minimize wavefront error and mass"
|
||||
|
||||
Claude: 🔧 Adding Zernike WFE extractor
|
||||
🔧 Adding mass extractor
|
||||
🔧 Adding objectives (multi-objective)
|
||||
|
||||
Multi-objective optimization! I recommend NSGA-II.
|
||||
|
||||
Which parameters should vary?
|
||||
|
||||
[Canvas: Extractors and objectives appear, connected to model]
|
||||
|
||||
User: "Use rib_thickness and mirror_depth, 50% range on each"
|
||||
|
||||
Claude: 🔧 Adding rib_thickness [4mm - 12mm]
|
||||
🔧 Adding mirror_depth [25mm - 75mm]
|
||||
|
||||
Any constraints?
|
||||
|
||||
[Canvas: DVs appear, connected to model]
|
||||
|
||||
User: "Keep stress under 150 MPa"
|
||||
|
||||
Claude: 🔧 Adding stress extractor
|
||||
🔧 Adding constraint: stress ≤ 150 MPa
|
||||
|
||||
✓ Configuration complete!
|
||||
|
||||
Your setup:
|
||||
- 2 design variables
|
||||
- 3 extractors (WFE, mass, stress)
|
||||
- 2 objectives (multi-objective)
|
||||
- 1 constraint
|
||||
|
||||
Ready to run? Say "start" or adjust the canvas first.
|
||||
|
||||
[Canvas: Full optimization flow visible]
|
||||
```
|
||||
|
||||
### Flow 3: User Edits, Claude Adapts
|
||||
|
||||
```
|
||||
[User clicks rib_thickness node, changes max from 12mm to 20mm]
|
||||
|
||||
User: "Is this a good range?"
|
||||
|
||||
Claude: I see you expanded rib_thickness to [4mm - 20mm].
|
||||
|
||||
That's a wide range! Considerations:
|
||||
- Thicker ribs = more mass, more stiffness
|
||||
- 20mm might exceed typical manufacturing limits
|
||||
- Wider range = more trials to explore
|
||||
|
||||
If you're exploring, this is fine. For production,
|
||||
I'd suggest [4mm - 15mm].
|
||||
|
||||
Want me to adjust, or keep your change?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
### Week 1-2: Unified WebSocket
|
||||
- [ ] Create `/api/atomizer/ws` endpoint
|
||||
- [ ] Implement streaming in `AtomizerClaudeAgent`
|
||||
- [ ] Create `useAtomizerSocket` hook
|
||||
- [ ] Wire canvas to receive spec updates
|
||||
- [ ] Add canvas edit notifications
|
||||
|
||||
### Week 3-4: Tools & Interview
|
||||
- [ ] Add `create_study` tool
|
||||
- [ ] Add `introspect_model` tool
|
||||
- [ ] Implement `InterviewEngine`
|
||||
- [ ] Add interview tools
|
||||
- [ ] Test guided creation flow
|
||||
|
||||
### Week 5: Polish
|
||||
- [ ] Tool call indicators in chat
|
||||
- [ ] Node appear/highlight animations
|
||||
- [ ] Edge draw animations
|
||||
- [ ] Error recovery & reconnection
|
||||
- [ ] Performance optimization
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
| Metric | Target |
|
||||
|--------|--------|
|
||||
| Study creation time (experienced) | < 30 seconds |
|
||||
| Study creation time (interview) | < 3 minutes |
|
||||
| Canvas update latency | < 200ms |
|
||||
| User edit → Claude context | < 100ms |
|
||||
| Interview completion rate | > 90% |
|
||||
|
||||
---
|
||||
|
||||
## Key Files to Modify
|
||||
|
||||
### Backend
|
||||
- `atomizer-dashboard/backend/api/routes/atomizer_ws.py` (new)
|
||||
- `atomizer-dashboard/backend/api/services/claude_agent.py` (enhance)
|
||||
- `atomizer-dashboard/backend/api/services/interview_engine.py` (new)
|
||||
|
||||
### Frontend
|
||||
- `atomizer-dashboard/frontend/src/hooks/useAtomizerSocket.ts` (new)
|
||||
- `atomizer-dashboard/frontend/src/pages/CanvasView.tsx` (update)
|
||||
- `atomizer-dashboard/frontend/src/components/canvas/SpecRenderer.tsx` (update)
|
||||
- `atomizer-dashboard/frontend/src/components/chat/ToolCallIndicator.tsx` (new)
|
||||
|
||||
---
|
||||
|
||||
*This architecture makes Atomizer uniquely powerful: natural language + visual feedback + full control, all in one seamless experience.*
|
||||
132
docs/plans/TODO_NXOPEN_MCP_SETUP.md
Normal file
132
docs/plans/TODO_NXOPEN_MCP_SETUP.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# NXOpen Documentation MCP Server - Setup TODO
|
||||
|
||||
**Created:** 2025-12-29
|
||||
**Status:** PENDING - Waiting for manual configuration
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
The NXOpen documentation MCP server exists on **dalidou** (192.168.86.50) but is not accessible from this Windows machine due to hostname resolution issues.
|
||||
|
||||
### What's Working
|
||||
- ✅ Dalidou server is online and reachable at `192.168.86.50`
|
||||
- ✅ Port 5000 (Documentation Proxy) is responding
|
||||
- ✅ Port 3000 (Gitea) is responding
|
||||
- ✅ MCP server code exists at `/srv/claude-assistant/` on dalidou
|
||||
|
||||
### What's NOT Working
|
||||
- ❌ `dalidou.local` hostname doesn't resolve (mDNS not configured on this machine)
|
||||
- ❌ MCP tools not integrated with Claude Code
|
||||
|
||||
---
|
||||
|
||||
## Steps to Complete
|
||||
|
||||
### Step 1: Fix Hostname Resolution (Manual - requires Admin)
|
||||
|
||||
**Option A: Run the script as Administrator**
|
||||
```powershell
|
||||
# Open PowerShell as Administrator, then:
|
||||
C:\Users\antoi\Atomizer\add_dalidou_host.ps1
|
||||
```
|
||||
|
||||
**Option B: Manually edit hosts file**
|
||||
1. Open Notepad as Administrator
|
||||
2. Open `C:\Windows\System32\drivers\etc\hosts`
|
||||
3. Add this line at the end:
|
||||
```
|
||||
192.168.86.50 dalidou.local dalidou
|
||||
```
|
||||
4. Save the file
|
||||
|
||||
**Verify:**
|
||||
```powershell
|
||||
ping dalidou.local
|
||||
```
|
||||
|
||||
### Step 2: Verify MCP Server is Running on Dalidou
|
||||
|
||||
SSH into dalidou and check:
|
||||
```bash
|
||||
ssh root@dalidou
|
||||
|
||||
# Check documentation proxy
|
||||
systemctl status siemensdocumentationproxyserver
|
||||
|
||||
# Check MCP server (if it's a service)
|
||||
# Or check what's running on port 5000
|
||||
ss -tlnp | grep 5000
|
||||
```
|
||||
|
||||
### Step 3: Configure Claude Code MCP Integration
|
||||
|
||||
The MCP server on dalidou uses **stdio-based MCP protocol**, not HTTP. To connect from Claude Code, you'll need one of:
|
||||
|
||||
**Option A: SSH-based MCP (if supported)**
|
||||
Configure in `.claude/settings.json` or MCP config to connect via SSH tunnel.
|
||||
|
||||
**Option B: Local Proxy**
|
||||
Run a local MCP proxy that connects to dalidou's MCP server.
|
||||
|
||||
**Option C: HTTP Wrapper**
|
||||
The current port 5000 service may already expose HTTP endpoints - need to verify once hostname is fixed.
|
||||
|
||||
---
|
||||
|
||||
## Server Documentation Reference
|
||||
|
||||
Full documentation is in the SERVtomaste repo:
|
||||
- **URL:** http://192.168.86.50:3000/Antoine/SERVtomaste
|
||||
- **File:** `docs/SIEMENS-DOCS-SERVER.md`
|
||||
|
||||
### Key Server Paths (on dalidou)
|
||||
```
|
||||
/srv/siemens-docs/proxy/ # Documentation Proxy (port 5000)
|
||||
/srv/claude-assistant/ # MCP Server
|
||||
/srv/claude-assistant/mcp-server/ # MCP server code
|
||||
/srv/claude-assistant/tools/ # Tool implementations
|
||||
├── siemens-auth.js # Puppeteer authentication
|
||||
├── siemens-docs.js # Documentation fetching
|
||||
└── ...
|
||||
/srv/claude-assistant/vault/ # Credentials (secured)
|
||||
```
|
||||
|
||||
### Available MCP Tools (once connected)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `siemens_docs_search` | Search NX Open, Simcenter docs |
|
||||
| `siemens_docs_fetch` | Fetch specific documentation page |
|
||||
| `siemens_auth_status` | Check if auth session is active |
|
||||
| `siemens_login` | Re-login if session expired |
|
||||
| `siemens_docs_list` | List documentation categories |
|
||||
|
||||
---
|
||||
|
||||
## Files Created During Investigation
|
||||
|
||||
- `C:\Users\antoi\Atomizer\add_dalidou_host.ps1` - Script to add hosts entry (run as Admin)
|
||||
- `C:\Users\antoi\Atomizer\test_mcp.py` - Test script for probing MCP server (can be deleted)
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- `.claude/skills/modules/nx-docs-lookup.md` - How to use MCP tools once configured
|
||||
- `docs/08_ARCHIVE/historical/NXOPEN_DOCUMENTATION_INTEGRATION_STRATEGY.md` - Full strategy doc
|
||||
- `docs/05_API_REFERENCE/NXOPEN_RESOURCES.md` - Alternative NXOpen resources
|
||||
|
||||
---
|
||||
|
||||
## Workaround Until Fixed
|
||||
|
||||
Without the MCP server, you can still look up NXOpen documentation by:
|
||||
|
||||
1. **Using web search** - I can search for NXOpen API documentation online
|
||||
2. **Using local stub files** - Python stubs at `C:\Program Files\Siemens\NX2412\UGOPEN\pythonStubs\`
|
||||
3. **Using existing extractors** - Check `optimization_engine/extractors/` for patterns
|
||||
4. **Recording NX journals** - Record operations in NX to learn the API calls
|
||||
|
||||
---
|
||||
|
||||
*To continue setup, run the hosts file fix and let me know when ready.*
|
||||
1697
docs/plans/UNIFIED_CONFIGURATION_ARCHITECTURE.md
Normal file
1697
docs/plans/UNIFIED_CONFIGURATION_ARCHITECTURE.md
Normal file
File diff suppressed because it is too large
Load Diff
495
docs/plans/UNIFIED_CONFIG_EXECUTION_PLAN.md
Normal file
495
docs/plans/UNIFIED_CONFIG_EXECUTION_PLAN.md
Normal file
@@ -0,0 +1,495 @@
|
||||
# Unified Configuration Architecture - Execution Plan
|
||||
|
||||
**Project**: AtomizerSpec v2.0 Implementation
|
||||
**Reference Document**: `docs/plans/UNIFIED_CONFIGURATION_ARCHITECTURE.md`
|
||||
**Schema Definition**: `optimization_engine/schemas/atomizer_spec_v2.json`
|
||||
**Status**: Ready for Implementation
|
||||
|
||||
---
|
||||
|
||||
## Project Overview
|
||||
|
||||
Transform Atomizer's fragmented configuration system into a unified architecture where:
|
||||
- One JSON schema (AtomizerSpec v2.0) is the single source of truth
|
||||
- Canvas, Backend, Claude, and Optimization Engine all use the same spec
|
||||
- Real-time WebSocket sync keeps all clients updated
|
||||
- Claude can dynamically modify specs and add custom functions
|
||||
|
||||
---
|
||||
|
||||
## Phase Structure
|
||||
|
||||
| Phase | Name | Duration | Focus |
|
||||
|-------|------|----------|-------|
|
||||
| 1 | Foundation | Week 1-3 | Backend SpecManager, REST API, Migration |
|
||||
| 2 | Frontend | Week 4-6 | SpecRenderer, WebSocket Sync, Store |
|
||||
| 3 | Claude Integration | Week 7-9 | MCP Tools, Custom Functions |
|
||||
| 4 | Polish & Testing | Week 10-12 | Migration, Testing, Documentation |
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order (Critical Path)
|
||||
|
||||
```
|
||||
1. Schema & Types (P1.1-P1.3)
|
||||
└── 2. SpecManager Service (P1.4-P1.7)
|
||||
└── 3. REST Endpoints (P1.8-P1.12)
|
||||
├── 4. Migration Script (P1.13-P1.16)
|
||||
└── 5. Frontend Store (P2.1-P2.4)
|
||||
└── 6. SpecRenderer (P2.5-P2.10)
|
||||
└── 7. WebSocket Sync (P2.11-P2.15)
|
||||
└── 8. MCP Tools (P3.1-P3.8)
|
||||
└── 9. Custom Functions (P3.9-P3.14)
|
||||
└── 10. Testing & Polish (P4.1-P4.12)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PHASE 1: Foundation (Backend)
|
||||
|
||||
### P1.1 - Create TypeScript types from JSON Schema
|
||||
- **File**: `atomizer-dashboard/frontend/src/types/atomizer-spec.ts`
|
||||
- **Action**: Generate TypeScript interfaces matching `atomizer_spec_v2.json`
|
||||
- **Reference**: Schema at `optimization_engine/schemas/atomizer_spec_v2.json`
|
||||
- **Acceptance**: Types compile, cover all schema definitions
|
||||
|
||||
### P1.2 - Create Python Pydantic models from JSON Schema
|
||||
- **File**: `optimization_engine/config/spec_models.py`
|
||||
- **Action**: Create Pydantic models for AtomizerSpec validation
|
||||
- **Reference**: Schema at `optimization_engine/schemas/atomizer_spec_v2.json`
|
||||
- **Acceptance**: Models validate example specs correctly
|
||||
|
||||
### P1.3 - Create spec validation utility
|
||||
- **File**: `optimization_engine/config/spec_validator.py`
|
||||
- **Action**: JSON Schema validation + semantic validation (bounds, references)
|
||||
- **Dependencies**: P1.2
|
||||
- **Acceptance**: Validates good specs, rejects invalid with clear errors
|
||||
|
||||
### P1.4 - Create SpecManager core class
|
||||
- **File**: `atomizer-dashboard/backend/api/services/spec_manager.py`
|
||||
- **Action**: Implement `load()`, `save()`, `_validate()`, `_compute_hash()`
|
||||
- **Reference**: Design in UNIFIED_CONFIGURATION_ARCHITECTURE.md Section 5.1
|
||||
- **Dependencies**: P1.3
|
||||
- **Acceptance**: Can load/save/validate atomizer_spec.json files
|
||||
|
||||
### P1.5 - Add SpecManager patch functionality
|
||||
- **File**: `atomizer-dashboard/backend/api/services/spec_manager.py`
|
||||
- **Action**: Implement `patch()` method for JSONPath-style updates
|
||||
- **Dependencies**: P1.4
|
||||
- **Acceptance**: Can update nested fields with conflict detection
|
||||
|
||||
### P1.6 - Add SpecManager node operations
|
||||
- **File**: `atomizer-dashboard/backend/api/services/spec_manager.py`
|
||||
- **Action**: Implement `add_node()`, `remove_node()`, `_generate_id()`, `_auto_position()`
|
||||
- **Dependencies**: P1.5
|
||||
- **Acceptance**: Can add/remove design vars, extractors, objectives, constraints
|
||||
|
||||
### P1.7 - Add SpecManager custom function support
|
||||
- **File**: `atomizer-dashboard/backend/api/services/spec_manager.py`
|
||||
- **Action**: Implement `add_custom_function()` with Python syntax validation
|
||||
- **Dependencies**: P1.6
|
||||
- **Acceptance**: Can add custom extractors with validated Python code
|
||||
|
||||
### P1.8 - Create spec REST router
|
||||
- **File**: `atomizer-dashboard/backend/api/routes/spec.py`
|
||||
- **Action**: Create FastAPI router for spec endpoints
|
||||
- **Dependencies**: P1.7
|
||||
- **Acceptance**: Router imports and mounts correctly
|
||||
|
||||
### P1.9 - Implement GET /studies/{study_id}/spec
|
||||
- **File**: `atomizer-dashboard/backend/api/routes/spec.py`
|
||||
- **Action**: Return full AtomizerSpec for a study
|
||||
- **Dependencies**: P1.8
|
||||
- **Acceptance**: Returns valid spec JSON, 404 for missing studies
|
||||
|
||||
### P1.10 - Implement PUT /studies/{study_id}/spec
|
||||
- **File**: `atomizer-dashboard/backend/api/routes/spec.py`
|
||||
- **Action**: Replace entire spec with validation
|
||||
- **Dependencies**: P1.9
|
||||
- **Acceptance**: Validates, saves, returns new hash
|
||||
|
||||
### P1.11 - Implement PATCH /studies/{study_id}/spec
|
||||
- **File**: `atomizer-dashboard/backend/api/routes/spec.py`
|
||||
- **Action**: Partial update with JSONPath
|
||||
- **Dependencies**: P1.10
|
||||
- **Acceptance**: Updates specific fields, broadcasts change
|
||||
|
||||
### P1.12 - Implement POST /studies/{study_id}/spec/validate
|
||||
- **File**: `atomizer-dashboard/backend/api/routes/spec.py`
|
||||
- **Action**: Validate spec and return detailed report
|
||||
- **Dependencies**: P1.11
|
||||
- **Acceptance**: Returns errors, warnings, summary
|
||||
|
||||
### P1.13 - Create config migration base
|
||||
- **File**: `optimization_engine/config/migrator.py`
|
||||
- **Action**: Create SpecMigrator class with field mapping
|
||||
- **Reference**: Design in UNIFIED_CONFIGURATION_ARCHITECTURE.md Section 8
|
||||
- **Acceptance**: Class structure ready for migration logic
|
||||
|
||||
### P1.14 - Implement design variable migration
|
||||
- **File**: `optimization_engine/config/migrator.py`
|
||||
- **Action**: Migrate `bounds[]` → `bounds.min/max`, `parameter` → `expression_name`
|
||||
- **Dependencies**: P1.13
|
||||
- **Acceptance**: All DV formats convert correctly
|
||||
|
||||
### P1.15 - Implement objective/constraint migration
|
||||
- **File**: `optimization_engine/config/migrator.py`
|
||||
- **Action**: Migrate `goal` → `direction`, extraction configs to new format
|
||||
- **Dependencies**: P1.14
|
||||
- **Acceptance**: Objectives and constraints convert correctly
|
||||
|
||||
### P1.16 - Implement full config migration
|
||||
- **File**: `optimization_engine/config/migrator.py`
|
||||
- **Action**: Complete migration including canvas positions, extractors inference
|
||||
- **Dependencies**: P1.15
|
||||
- **Acceptance**: Can migrate any existing optimization_config.json to AtomizerSpec
|
||||
|
||||
### P1.17 - Create migration CLI tool
|
||||
- **File**: `tools/migrate_to_spec_v2.py`
|
||||
- **Action**: CLI for batch migration with dry-run support
|
||||
- **Dependencies**: P1.16
|
||||
- **Acceptance**: `python tools/migrate_to_spec_v2.py --dry-run studies/*`
|
||||
|
||||
---
|
||||
|
||||
## PHASE 2: Frontend Integration
|
||||
|
||||
### P2.1 - Create useSpecStore hook
|
||||
- **File**: `atomizer-dashboard/frontend/src/hooks/useSpecStore.ts`
|
||||
- **Action**: Zustand store for AtomizerSpec state management
|
||||
- **Dependencies**: P1.1
|
||||
- **Acceptance**: Store holds spec, provides typed accessors
|
||||
|
||||
### P2.2 - Add spec loading to useSpecStore
|
||||
- **File**: `atomizer-dashboard/frontend/src/hooks/useSpecStore.ts`
|
||||
- **Action**: Implement `loadSpec(studyId)` fetching from API
|
||||
- **Dependencies**: P2.1, P1.9
|
||||
- **Acceptance**: Loads spec from backend, updates store
|
||||
|
||||
### P2.3 - Add spec modification to useSpecStore
|
||||
- **File**: `atomizer-dashboard/frontend/src/hooks/useSpecStore.ts`
|
||||
- **Action**: Implement `patchSpec()`, `addNode()`, `removeNode()` calling API
|
||||
- **Dependencies**: P2.2, P1.11
|
||||
- **Acceptance**: Modifications persist to backend
|
||||
|
||||
### P2.4 - Add optimistic updates to useSpecStore
|
||||
- **File**: `atomizer-dashboard/frontend/src/hooks/useSpecStore.ts`
|
||||
- **Action**: Update local state immediately, rollback on error
|
||||
- **Dependencies**: P2.3
|
||||
- **Acceptance**: UI feels instant, handles errors gracefully
|
||||
|
||||
### P2.5 - Create specToNodes converter
|
||||
- **File**: `atomizer-dashboard/frontend/src/lib/spec/converter.ts`
|
||||
- **Action**: Convert AtomizerSpec to ReactFlow nodes array
|
||||
- **Reference**: Design in UNIFIED_CONFIGURATION_ARCHITECTURE.md Section 5.2
|
||||
- **Dependencies**: P1.1
|
||||
- **Acceptance**: All node types render correctly
|
||||
|
||||
### P2.6 - Create specToEdges converter
|
||||
- **File**: `atomizer-dashboard/frontend/src/lib/spec/converter.ts`
|
||||
- **Action**: Convert spec.canvas.edges to ReactFlow edges
|
||||
- **Dependencies**: P2.5
|
||||
- **Acceptance**: All connections render correctly
|
||||
|
||||
### P2.7 - Create SpecRenderer component
|
||||
- **File**: `atomizer-dashboard/frontend/src/components/canvas/SpecRenderer.tsx`
|
||||
- **Action**: ReactFlow component that renders from useSpecStore
|
||||
- **Dependencies**: P2.5, P2.6, P2.4
|
||||
- **Acceptance**: Canvas displays spec correctly
|
||||
|
||||
### P2.8 - Wire node editing to spec updates
|
||||
- **File**: `atomizer-dashboard/frontend/src/components/canvas/SpecRenderer.tsx`
|
||||
- **Action**: Node data changes call useSpecStore.patchSpec()
|
||||
- **Dependencies**: P2.7
|
||||
- **Acceptance**: Editing node properties persists to spec
|
||||
|
||||
### P2.9 - Wire node position to spec updates
|
||||
- **File**: `atomizer-dashboard/frontend/src/components/canvas/SpecRenderer.tsx`
|
||||
- **Action**: Drag-drop updates canvas_position in spec
|
||||
- **Dependencies**: P2.8
|
||||
- **Acceptance**: Layout persists across reloads
|
||||
|
||||
### P2.10 - Update node panels for full spec fields
|
||||
- **Files**: `atomizer-dashboard/frontend/src/components/canvas/panels/*.tsx`
|
||||
- **Action**: Update all node config panels to show/edit full spec fields
|
||||
- **Dependencies**: P2.8
|
||||
- **Acceptance**: All spec fields are editable in UI
|
||||
|
||||
### P2.11 - Create WebSocket connection hook
|
||||
- **File**: `atomizer-dashboard/frontend/src/hooks/useSpecSync.ts`
|
||||
- **Action**: WebSocket connection to `/api/studies/{id}/sync`
|
||||
- **Dependencies**: P2.4
|
||||
- **Acceptance**: Connects, handles reconnection
|
||||
|
||||
### P2.12 - Create WebSocket backend endpoint
|
||||
- **File**: `atomizer-dashboard/backend/api/routes/spec.py`
|
||||
- **Action**: WebSocket endpoint for spec sync
|
||||
- **Dependencies**: P1.12
|
||||
- **Acceptance**: Accepts connections, tracks subscribers
|
||||
|
||||
### P2.13 - Implement spec_updated broadcast
|
||||
- **File**: `atomizer-dashboard/backend/api/services/spec_manager.py`
|
||||
- **Action**: SpecManager broadcasts to all subscribers on save
|
||||
- **Dependencies**: P2.12
|
||||
- **Acceptance**: All connected clients receive updates
|
||||
|
||||
### P2.14 - Handle spec_updated in frontend
|
||||
- **File**: `atomizer-dashboard/frontend/src/hooks/useSpecSync.ts`
|
||||
- **Action**: On spec_updated message, refresh spec from store
|
||||
- **Dependencies**: P2.11, P2.13
|
||||
- **Acceptance**: Changes from other clients appear in real-time
|
||||
|
||||
### P2.15 - Add conflict detection
|
||||
- **File**: `atomizer-dashboard/frontend/src/hooks/useSpecStore.ts`
|
||||
- **Action**: Compare hashes, warn on conflict, offer merge/overwrite
|
||||
- **Dependencies**: P2.14
|
||||
- **Acceptance**: Concurrent edits don't silently overwrite
|
||||
|
||||
### P2.16 - Replace CanvasView with SpecRenderer
|
||||
- **File**: `atomizer-dashboard/frontend/src/pages/CanvasView.tsx`
|
||||
- **Action**: Switch from useCanvasStore to useSpecStore + SpecRenderer
|
||||
- **Dependencies**: P2.10, P2.15
|
||||
- **Acceptance**: Canvas page uses new spec-based system
|
||||
|
||||
---
|
||||
|
||||
## PHASE 3: Claude Integration
|
||||
|
||||
### P3.1 - Create spec_get MCP tool
|
||||
- **File**: `mcp-server/atomizer-tools/src/tools/spec_tools.ts`
|
||||
- **Action**: Tool to retrieve full AtomizerSpec
|
||||
- **Reference**: Design in UNIFIED_CONFIGURATION_ARCHITECTURE.md Section 5.3
|
||||
- **Dependencies**: P1.9
|
||||
- **Acceptance**: Claude can read spec via MCP
|
||||
|
||||
### P3.2 - Create spec_modify MCP tool
|
||||
- **File**: `mcp-server/atomizer-tools/src/tools/spec_tools.ts`
|
||||
- **Action**: Tool to apply modifications (set, add, remove operations)
|
||||
- **Dependencies**: P3.1, P1.11
|
||||
- **Acceptance**: Claude can modify spec fields
|
||||
|
||||
### P3.3 - Create spec_add_node MCP tool
|
||||
- **File**: `mcp-server/atomizer-tools/src/tools/spec_tools.ts`
|
||||
- **Action**: Tool to add design vars, extractors, objectives, constraints
|
||||
- **Dependencies**: P3.2
|
||||
- **Acceptance**: Claude can add nodes to canvas
|
||||
|
||||
### P3.4 - Create spec_remove_node MCP tool
|
||||
- **File**: `mcp-server/atomizer-tools/src/tools/spec_tools.ts`
|
||||
- **Action**: Tool to remove nodes (with edge cleanup)
|
||||
- **Dependencies**: P3.3
|
||||
- **Acceptance**: Claude can remove nodes
|
||||
|
||||
### P3.5 - Create spec_validate MCP tool
|
||||
- **File**: `mcp-server/atomizer-tools/src/tools/spec_tools.ts`
|
||||
- **Action**: Tool to validate spec and return report
|
||||
- **Dependencies**: P1.12
|
||||
- **Acceptance**: Claude can check spec validity
|
||||
|
||||
### P3.6 - Create spec_add_custom_extractor MCP tool
|
||||
- **File**: `mcp-server/atomizer-tools/src/tools/spec_tools.ts`
|
||||
- **Action**: Tool to add custom Python functions as extractors
|
||||
- **Dependencies**: P1.7, P3.3
|
||||
- **Acceptance**: Claude can add custom extraction logic
|
||||
|
||||
### P3.7 - Register spec tools in MCP server
|
||||
- **File**: `mcp-server/atomizer-tools/src/index.ts`
|
||||
- **Action**: Import and register all spec_* tools
|
||||
- **Dependencies**: P3.1-P3.6
|
||||
- **Acceptance**: Tools appear in MCP tool list
|
||||
|
||||
### P3.8 - Update ContextBuilder for spec awareness
|
||||
- **File**: `atomizer-dashboard/backend/api/services/context_builder.py`
|
||||
- **Action**: Include spec summary in Claude context, mention spec tools
|
||||
- **Dependencies**: P3.7
|
||||
- **Acceptance**: Claude knows about spec tools in context
|
||||
|
||||
### P3.9 - Create custom extractor runtime loader
|
||||
- **File**: `optimization_engine/extractors/custom_loader.py`
|
||||
- **Action**: Dynamically load and execute custom functions from spec
|
||||
- **Dependencies**: P1.7
|
||||
- **Acceptance**: Custom functions execute during optimization
|
||||
|
||||
### P3.10 - Integrate custom extractors into optimization runner
|
||||
- **File**: `optimization_engine/core/runner.py`
|
||||
- **Action**: Check spec for custom extractors, load and use them
|
||||
- **Dependencies**: P3.9
|
||||
- **Acceptance**: Optimization uses custom extractors defined in spec
|
||||
|
||||
### P3.11 - Add custom extractor node type to canvas
|
||||
- **File**: `atomizer-dashboard/frontend/src/components/canvas/nodes/CustomExtractorNode.tsx`
|
||||
- **Action**: New node type showing custom function with code preview
|
||||
- **Dependencies**: P2.10
|
||||
- **Acceptance**: Custom extractors display distinctly in canvas
|
||||
|
||||
### P3.12 - Add code editor for custom extractors
|
||||
- **File**: `atomizer-dashboard/frontend/src/components/canvas/panels/CustomExtractorPanel.tsx`
|
||||
- **Action**: Monaco editor for viewing/editing custom Python code
|
||||
- **Dependencies**: P3.11
|
||||
- **Acceptance**: Users can view and edit custom function code
|
||||
|
||||
### P3.13 - Create spec_create_from_description MCP tool
|
||||
- **File**: `mcp-server/atomizer-tools/src/tools/spec_tools.ts`
|
||||
- **Action**: Tool to create new spec from natural language + model path
|
||||
- **Dependencies**: P3.6, P1.16
|
||||
- **Acceptance**: Claude can create studies from descriptions
|
||||
|
||||
### P3.14 - Update Claude prompts for spec workflow
|
||||
- **File**: `atomizer-dashboard/backend/api/services/context_builder.py`
|
||||
- **Action**: Update system prompts to guide Claude on using spec tools
|
||||
- **Dependencies**: P3.13
|
||||
- **Acceptance**: Claude naturally uses spec tools for modifications
|
||||
|
||||
---
|
||||
|
||||
## PHASE 4: Polish & Testing
|
||||
|
||||
### P4.1 - Migrate m1_mirror studies
|
||||
- **Action**: Run migration on all m1_mirror_* studies
|
||||
- **Dependencies**: P1.17
|
||||
- **Acceptance**: All studies have valid atomizer_spec.json
|
||||
|
||||
### P4.2 - Migrate drone_gimbal study
|
||||
- **Action**: Run migration on drone_gimbal study
|
||||
- **Dependencies**: P1.17
|
||||
- **Acceptance**: Study has valid atomizer_spec.json
|
||||
|
||||
### P4.3 - Migrate all remaining studies
|
||||
- **Action**: Run migration on all studies in studies/
|
||||
- **Dependencies**: P4.1, P4.2
|
||||
- **Acceptance**: All studies migrated, validated
|
||||
|
||||
### P4.4 - Create spec unit tests
|
||||
- **File**: `tests/test_spec_manager.py`
|
||||
- **Action**: Unit tests for SpecManager operations
|
||||
- **Dependencies**: P1.7
|
||||
- **Acceptance**: All SpecManager methods tested
|
||||
|
||||
### P4.5 - Create spec API integration tests
|
||||
- **File**: `tests/test_spec_api.py`
|
||||
- **Action**: Integration tests for REST endpoints
|
||||
- **Dependencies**: P1.12
|
||||
- **Acceptance**: All endpoints tested
|
||||
|
||||
### P4.6 - Create migration tests
|
||||
- **File**: `tests/test_migrator.py`
|
||||
- **Action**: Test migration with various config formats
|
||||
- **Dependencies**: P1.16
|
||||
- **Acceptance**: All config variants migrate correctly
|
||||
|
||||
### P4.7 - Create frontend component tests
|
||||
- **File**: `atomizer-dashboard/frontend/src/__tests__/SpecRenderer.test.tsx`
|
||||
- **Action**: Test SpecRenderer with various specs
|
||||
- **Dependencies**: P2.16
|
||||
- **Acceptance**: Canvas renders correctly for all spec types
|
||||
|
||||
### P4.8 - Create WebSocket sync tests
|
||||
- **File**: `tests/test_spec_sync.py`
|
||||
- **Action**: Test real-time sync between multiple clients
|
||||
- **Dependencies**: P2.15
|
||||
- **Acceptance**: Changes propagate correctly
|
||||
|
||||
### P4.9 - Create MCP tools tests
|
||||
- **File**: `mcp-server/atomizer-tools/src/__tests__/spec_tools.test.ts`
|
||||
- **Action**: Test all spec_* MCP tools
|
||||
- **Dependencies**: P3.7
|
||||
- **Acceptance**: All tools work correctly
|
||||
|
||||
### P4.10 - End-to-end testing
|
||||
- **Action**: Full workflow test: create study in canvas, modify via Claude, run optimization
|
||||
- **Dependencies**: P4.1-P4.9
|
||||
- **Acceptance**: Complete workflow works
|
||||
|
||||
### P4.11 - Update documentation
|
||||
- **Files**: `docs/04_USER_GUIDES/CANVAS.md`, `docs/04_USER_GUIDES/DASHBOARD.md`
|
||||
- **Action**: Document new spec-based workflow
|
||||
- **Dependencies**: P4.10
|
||||
- **Acceptance**: Documentation reflects new system
|
||||
|
||||
### P4.12 - Update CLAUDE.md
|
||||
- **File**: `CLAUDE.md`
|
||||
- **Action**: Add spec tools documentation, update context loading
|
||||
- **Dependencies**: P4.11
|
||||
- **Acceptance**: Claude Code sessions know about spec system
|
||||
|
||||
---
|
||||
|
||||
## File Summary
|
||||
|
||||
### New Files to Create
|
||||
|
||||
| File | Phase | Purpose |
|
||||
|------|-------|---------|
|
||||
| `atomizer-dashboard/frontend/src/types/atomizer-spec.ts` | P1 | TypeScript types |
|
||||
| `optimization_engine/config/spec_models.py` | P1 | Pydantic models |
|
||||
| `optimization_engine/config/spec_validator.py` | P1 | Validation logic |
|
||||
| `atomizer-dashboard/backend/api/services/spec_manager.py` | P1 | Core spec service |
|
||||
| `atomizer-dashboard/backend/api/routes/spec.py` | P1 | REST endpoints |
|
||||
| `optimization_engine/config/migrator.py` | P1 | Config migration |
|
||||
| `tools/migrate_to_spec_v2.py` | P1 | Migration CLI |
|
||||
| `atomizer-dashboard/frontend/src/hooks/useSpecStore.ts` | P2 | Spec state store |
|
||||
| `atomizer-dashboard/frontend/src/hooks/useSpecSync.ts` | P2 | WebSocket sync |
|
||||
| `atomizer-dashboard/frontend/src/lib/spec/converter.ts` | P2 | Spec ↔ ReactFlow |
|
||||
| `atomizer-dashboard/frontend/src/components/canvas/SpecRenderer.tsx` | P2 | New canvas component |
|
||||
| `mcp-server/atomizer-tools/src/tools/spec_tools.ts` | P3 | MCP tools |
|
||||
| `optimization_engine/extractors/custom_loader.py` | P3 | Custom function loader |
|
||||
| `atomizer-dashboard/frontend/src/components/canvas/nodes/CustomExtractorNode.tsx` | P3 | Custom node type |
|
||||
| `atomizer-dashboard/frontend/src/components/canvas/panels/CustomExtractorPanel.tsx` | P3 | Code editor panel |
|
||||
|
||||
### Files to Modify
|
||||
|
||||
| File | Phase | Changes |
|
||||
|------|-------|---------|
|
||||
| `atomizer-dashboard/backend/api/main.py` | P1 | Mount spec router |
|
||||
| `mcp-server/atomizer-tools/src/index.ts` | P3 | Register spec tools |
|
||||
| `atomizer-dashboard/backend/api/services/context_builder.py` | P3 | Update Claude context |
|
||||
| `optimization_engine/core/runner.py` | P3 | Custom extractor support |
|
||||
| `atomizer-dashboard/frontend/src/pages/CanvasView.tsx` | P2 | Use SpecRenderer |
|
||||
| `CLAUDE.md` | P4 | Document spec system |
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Phase 1 Complete When:
|
||||
- [ ] SpecManager can load/save/validate specs
|
||||
- [ ] All REST endpoints return correct responses
|
||||
- [ ] Migration tool converts existing configs
|
||||
|
||||
### Phase 2 Complete When:
|
||||
- [ ] Canvas renders from AtomizerSpec
|
||||
- [ ] Edits persist to spec file
|
||||
- [ ] WebSocket sync works between clients
|
||||
|
||||
### Phase 3 Complete When:
|
||||
- [ ] Claude can read and modify specs via MCP
|
||||
- [ ] Custom extractors work in optimization
|
||||
- [ ] Claude can create studies from descriptions
|
||||
|
||||
### Phase 4 Complete When:
|
||||
- [ ] All existing studies migrated
|
||||
- [ ] All tests pass
|
||||
- [ ] Documentation updated
|
||||
|
||||
---
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| Existing studies break | Migration tool with dry-run, keep old configs as backup |
|
||||
| WebSocket complexity | Start with polling, add WebSocket as enhancement |
|
||||
| Custom code security | Sandbox execution, syntax validation, no imports |
|
||||
| Performance with large specs | Lazy loading, incremental updates |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Master Design**: `docs/plans/UNIFIED_CONFIGURATION_ARCHITECTURE.md`
|
||||
**Schema**: `optimization_engine/schemas/atomizer_spec_v2.json`
|
||||
**This Plan**: `docs/plans/UNIFIED_CONFIG_EXECUTION_PLAN.md`
|
||||
|
||||
---
|
||||
|
||||
*Execute tasks in order. Each task has clear acceptance criteria. Reference the master design document for detailed specifications.*
|
||||
92
docs/plans/UNIFIED_CONFIG_QUICKSTART.md
Normal file
92
docs/plans/UNIFIED_CONFIG_QUICKSTART.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# AtomizerSpec v2.0 - Quick Start Guide
|
||||
|
||||
## TL;DR
|
||||
|
||||
**Goal**: Replace 4+ config formats with one unified `atomizer_spec.json` that Canvas, Backend, Claude, and Optimization Engine all use.
|
||||
|
||||
**Effort**: ~50 tasks across 4 phases (~12 weeks)
|
||||
|
||||
**Key Files**:
|
||||
- Design: `docs/plans/UNIFIED_CONFIGURATION_ARCHITECTURE.md`
|
||||
- Tasks: `docs/plans/UNIFIED_CONFIG_EXECUTION_PLAN.md`
|
||||
- Schema: `optimization_engine/schemas/atomizer_spec_v2.json`
|
||||
- Prompts: `docs/plans/UNIFIED_CONFIG_RALPH_PROMPT.md`
|
||||
|
||||
---
|
||||
|
||||
## Start Autonomous Implementation
|
||||
|
||||
### 1. Open Claude CLI
|
||||
```bash
|
||||
claude --dangerously-skip-permissions
|
||||
```
|
||||
|
||||
### 2. Paste Initial Prompt
|
||||
```
|
||||
You are implementing the AtomizerSpec v2.0 Unified Configuration Architecture for Atomizer.
|
||||
|
||||
Read these documents before starting:
|
||||
1. `docs/plans/UNIFIED_CONFIGURATION_ARCHITECTURE.md` - Master design
|
||||
2. `docs/plans/UNIFIED_CONFIG_EXECUTION_PLAN.md` - Task list (50+ tasks)
|
||||
3. `optimization_engine/schemas/atomizer_spec_v2.json` - JSON Schema
|
||||
|
||||
Implement tasks in order (P1.1 → P4.12). Use TodoWrite to track progress. Commit after each logical group. Reference the design document for code examples.
|
||||
|
||||
Begin by reading the documents and loading tasks into TodoWrite. Start with P1.1.
|
||||
```
|
||||
|
||||
### 3. Let It Run
|
||||
Claude will work through the task list autonomously, committing as it goes.
|
||||
|
||||
---
|
||||
|
||||
## Phase Summary
|
||||
|
||||
| Phase | Tasks | Focus | Key Deliverables |
|
||||
|-------|-------|-------|------------------|
|
||||
| **1** | P1.1-P1.17 | Backend | SpecManager, REST API, Migration |
|
||||
| **2** | P2.1-P2.16 | Frontend | SpecRenderer, WebSocket Sync |
|
||||
| **3** | P3.1-P3.14 | Claude | MCP Tools, Custom Functions |
|
||||
| **4** | P4.1-P4.12 | Polish | Tests, Migration, Docs |
|
||||
|
||||
---
|
||||
|
||||
## Key Concepts
|
||||
|
||||
**AtomizerSpec**: Single JSON file containing everything needed for optimization AND canvas display.
|
||||
|
||||
**SpecManager**: Backend service that validates, saves, and broadcasts spec changes.
|
||||
|
||||
**SpecRenderer**: Frontend component that renders canvas directly from spec.
|
||||
|
||||
**Real-time Sync**: WebSocket broadcasts changes to all connected clients.
|
||||
|
||||
**Custom Functions**: Python code stored in spec, loaded dynamically during optimization.
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
- [ ] Canvas loads/saves without data loss
|
||||
- [ ] Claude can modify specs via MCP tools
|
||||
- [ ] All existing studies migrated
|
||||
- [ ] WebSocket sync works between clients
|
||||
- [ ] Custom extractors execute in optimization
|
||||
- [ ] All tests pass
|
||||
|
||||
---
|
||||
|
||||
## If Resuming
|
||||
|
||||
```
|
||||
Continue implementing AtomizerSpec v2.0.
|
||||
|
||||
Reference:
|
||||
- `docs/plans/UNIFIED_CONFIG_EXECUTION_PLAN.md`
|
||||
|
||||
Check current todo list, find last completed task, continue from there.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Full details in the referenced documents.*
|
||||
286
docs/plans/UNIFIED_CONFIG_RALPH_PROMPT.md
Normal file
286
docs/plans/UNIFIED_CONFIG_RALPH_PROMPT.md
Normal file
@@ -0,0 +1,286 @@
|
||||
# Ralph Loop Prompt: AtomizerSpec v2.0 Implementation
|
||||
|
||||
**Copy this prompt to start the autonomous implementation loop.**
|
||||
|
||||
---
|
||||
|
||||
## Claude CLI Command
|
||||
|
||||
```bash
|
||||
claude --dangerously-skip-permissions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Initial Prompt (Copy This)
|
||||
|
||||
```
|
||||
You are implementing the AtomizerSpec v2.0 Unified Configuration Architecture for Atomizer.
|
||||
|
||||
## Project Context
|
||||
|
||||
Read these documents before starting:
|
||||
1. `docs/plans/UNIFIED_CONFIGURATION_ARCHITECTURE.md` - Master design document with architecture, schemas, and component designs
|
||||
2. `docs/plans/UNIFIED_CONFIG_EXECUTION_PLAN.md` - Detailed task list with 50+ tasks across 4 phases
|
||||
3. `optimization_engine/schemas/atomizer_spec_v2.json` - The JSON Schema definition
|
||||
|
||||
## Your Mission
|
||||
|
||||
Implement the AtomizerSpec v2.0 system following the execution plan. Work through tasks in order (P1.1 → P1.2 → ... → P4.12).
|
||||
|
||||
## Rules
|
||||
|
||||
1. **Follow the plan**: Execute tasks in the order specified in UNIFIED_CONFIG_EXECUTION_PLAN.md
|
||||
2. **Check dependencies**: Don't start a task until its dependencies are complete
|
||||
3. **Use TodoWrite**: Track all tasks, mark in_progress when starting, completed when done
|
||||
4. **Test as you go**: Verify each task meets its acceptance criteria before moving on
|
||||
5. **Commit regularly**: Commit after completing each logical group of tasks (e.g., after P1.7, after P1.12)
|
||||
6. **Reference the design**: The master document has code examples - use them
|
||||
7. **Ask if blocked**: If you encounter a blocker, explain what's wrong and what you need
|
||||
|
||||
## Starting Point
|
||||
|
||||
1. Read the three reference documents listed above
|
||||
2. Load the current task list into TodoWrite
|
||||
3. Start with P1.1 (Create TypeScript types from JSON Schema)
|
||||
4. Work through each task, marking completion as you go
|
||||
|
||||
## Commit Message Format
|
||||
|
||||
Use conventional commits:
|
||||
- `feat(spec): Add SpecManager core class (P1.4)`
|
||||
- `feat(spec-api): Implement GET /studies/{id}/spec (P1.9)`
|
||||
- `feat(frontend): Create useSpecStore hook (P2.1)`
|
||||
- `test(spec): Add SpecManager unit tests (P4.4)`
|
||||
|
||||
## Progress Tracking
|
||||
|
||||
After completing each phase, summarize:
|
||||
- Tasks completed
|
||||
- Files created/modified
|
||||
- Any deviations from plan
|
||||
- Blockers encountered
|
||||
|
||||
Begin by reading the reference documents and loading tasks into TodoWrite.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Continuation Prompt (When Resuming)
|
||||
|
||||
```
|
||||
Continue implementing the AtomizerSpec v2.0 system.
|
||||
|
||||
Reference documents:
|
||||
- `docs/plans/UNIFIED_CONFIGURATION_ARCHITECTURE.md` - Master design
|
||||
- `docs/plans/UNIFIED_CONFIG_EXECUTION_PLAN.md` - Task list
|
||||
|
||||
Check where we left off:
|
||||
1. Read the current todo list
|
||||
2. Find the last completed task
|
||||
3. Continue with the next task in sequence
|
||||
|
||||
If you need context on what was done, check recent git commits.
|
||||
|
||||
Resume implementation.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase-Specific Prompts
|
||||
|
||||
### Start Phase 1 (Foundation)
|
||||
```
|
||||
Begin Phase 1 of AtomizerSpec implementation - Foundation/Backend.
|
||||
|
||||
Reference: `docs/plans/UNIFIED_CONFIG_EXECUTION_PLAN.md` section "PHASE 1: Foundation"
|
||||
|
||||
Tasks P1.1 through P1.17 focus on:
|
||||
- TypeScript and Python types from JSON Schema
|
||||
- SpecManager service (load, save, validate, patch, nodes)
|
||||
- REST API endpoints
|
||||
- Migration tool
|
||||
|
||||
Start with P1.1: Create TypeScript types from the JSON Schema at `optimization_engine/schemas/atomizer_spec_v2.json`.
|
||||
|
||||
Work through each task in order, tracking with TodoWrite.
|
||||
```
|
||||
|
||||
### Start Phase 2 (Frontend)
|
||||
```
|
||||
Begin Phase 2 of AtomizerSpec implementation - Frontend Integration.
|
||||
|
||||
Reference: `docs/plans/UNIFIED_CONFIG_EXECUTION_PLAN.md` section "PHASE 2: Frontend Integration"
|
||||
|
||||
Tasks P2.1 through P2.16 focus on:
|
||||
- useSpecStore hook (Zustand state management)
|
||||
- Spec ↔ ReactFlow conversion
|
||||
- SpecRenderer component
|
||||
- WebSocket real-time sync
|
||||
- Replacing old canvas with spec-based canvas
|
||||
|
||||
Prerequisites: Phase 1 must be complete (P1.1-P1.17).
|
||||
|
||||
Start with P2.1: Create useSpecStore hook.
|
||||
|
||||
Work through each task in order, tracking with TodoWrite.
|
||||
```
|
||||
|
||||
### Start Phase 3 (Claude Integration)
|
||||
```
|
||||
Begin Phase 3 of AtomizerSpec implementation - Claude Integration.
|
||||
|
||||
Reference: `docs/plans/UNIFIED_CONFIG_EXECUTION_PLAN.md` section "PHASE 3: Claude Integration"
|
||||
|
||||
Tasks P3.1 through P3.14 focus on:
|
||||
- MCP tools for spec operations (spec_get, spec_modify, spec_add_node, etc.)
|
||||
- Custom extractor support (add Python functions via Claude)
|
||||
- Runtime loading of custom functions
|
||||
- Natural language study creation
|
||||
|
||||
Prerequisites: Phases 1 and 2 must be complete.
|
||||
|
||||
Start with P3.1: Create spec_get MCP tool.
|
||||
|
||||
Work through each task in order, tracking with TodoWrite.
|
||||
```
|
||||
|
||||
### Start Phase 4 (Polish & Testing)
|
||||
```
|
||||
Begin Phase 4 of AtomizerSpec implementation - Polish & Testing.
|
||||
|
||||
Reference: `docs/plans/UNIFIED_CONFIG_EXECUTION_PLAN.md` section "PHASE 4: Polish & Testing"
|
||||
|
||||
Tasks P4.1 through P4.12 focus on:
|
||||
- Migrating all existing studies to AtomizerSpec v2
|
||||
- Unit tests, integration tests, e2e tests
|
||||
- Documentation updates
|
||||
|
||||
Prerequisites: Phases 1, 2, and 3 must be complete.
|
||||
|
||||
Start with P4.1: Migrate m1_mirror studies.
|
||||
|
||||
Work through each task in order, tracking with TodoWrite.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting Prompts
|
||||
|
||||
### If Stuck on a Task
|
||||
```
|
||||
I'm blocked on task [TASK_ID].
|
||||
|
||||
The issue is: [describe the problem]
|
||||
|
||||
What I've tried: [list attempts]
|
||||
|
||||
Please help me resolve this so I can continue with the execution plan.
|
||||
```
|
||||
|
||||
### If Tests Fail
|
||||
```
|
||||
Tests are failing for [component].
|
||||
|
||||
Error: [paste error]
|
||||
|
||||
This is blocking task [TASK_ID]. Help me fix the tests so I can mark the task complete.
|
||||
```
|
||||
|
||||
### If Design Needs Clarification
|
||||
```
|
||||
Task [TASK_ID] requires [specific thing] but the design document doesn't specify [what's unclear].
|
||||
|
||||
Options I see:
|
||||
1. [option A]
|
||||
2. [option B]
|
||||
|
||||
Which approach should I take? Or should I update the design document?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Full Task List for TodoWrite
|
||||
|
||||
Copy this to initialize the todo list:
|
||||
|
||||
```
|
||||
P1.1 - Create TypeScript types from JSON Schema
|
||||
P1.2 - Create Python Pydantic models from JSON Schema
|
||||
P1.3 - Create spec validation utility
|
||||
P1.4 - Create SpecManager core class
|
||||
P1.5 - Add SpecManager patch functionality
|
||||
P1.6 - Add SpecManager node operations
|
||||
P1.7 - Add SpecManager custom function support
|
||||
P1.8 - Create spec REST router
|
||||
P1.9 - Implement GET /studies/{study_id}/spec
|
||||
P1.10 - Implement PUT /studies/{study_id}/spec
|
||||
P1.11 - Implement PATCH /studies/{study_id}/spec
|
||||
P1.12 - Implement POST /studies/{study_id}/spec/validate
|
||||
P1.13 - Create config migration base
|
||||
P1.14 - Implement design variable migration
|
||||
P1.15 - Implement objective/constraint migration
|
||||
P1.16 - Implement full config migration
|
||||
P1.17 - Create migration CLI tool
|
||||
P2.1 - Create useSpecStore hook
|
||||
P2.2 - Add spec loading to useSpecStore
|
||||
P2.3 - Add spec modification to useSpecStore
|
||||
P2.4 - Add optimistic updates to useSpecStore
|
||||
P2.5 - Create specToNodes converter
|
||||
P2.6 - Create specToEdges converter
|
||||
P2.7 - Create SpecRenderer component
|
||||
P2.8 - Wire node editing to spec updates
|
||||
P2.9 - Wire node position to spec updates
|
||||
P2.10 - Update node panels for full spec fields
|
||||
P2.11 - Create WebSocket connection hook
|
||||
P2.12 - Create WebSocket backend endpoint
|
||||
P2.13 - Implement spec_updated broadcast
|
||||
P2.14 - Handle spec_updated in frontend
|
||||
P2.15 - Add conflict detection
|
||||
P2.16 - Replace CanvasView with SpecRenderer
|
||||
P3.1 - Create spec_get MCP tool
|
||||
P3.2 - Create spec_modify MCP tool
|
||||
P3.3 - Create spec_add_node MCP tool
|
||||
P3.4 - Create spec_remove_node MCP tool
|
||||
P3.5 - Create spec_validate MCP tool
|
||||
P3.6 - Create spec_add_custom_extractor MCP tool
|
||||
P3.7 - Register spec tools in MCP server
|
||||
P3.8 - Update ContextBuilder for spec awareness
|
||||
P3.9 - Create custom extractor runtime loader
|
||||
P3.10 - Integrate custom extractors into optimization runner
|
||||
P3.11 - Add custom extractor node type to canvas
|
||||
P3.12 - Add code editor for custom extractors
|
||||
P3.13 - Create spec_create_from_description MCP tool
|
||||
P3.14 - Update Claude prompts for spec workflow
|
||||
P4.1 - Migrate m1_mirror studies
|
||||
P4.2 - Migrate drone_gimbal study
|
||||
P4.3 - Migrate all remaining studies
|
||||
P4.4 - Create spec unit tests
|
||||
P4.5 - Create spec API integration tests
|
||||
P4.6 - Create migration tests
|
||||
P4.7 - Create frontend component tests
|
||||
P4.8 - Create WebSocket sync tests
|
||||
P4.9 - Create MCP tools tests
|
||||
P4.10 - End-to-end testing
|
||||
P4.11 - Update documentation
|
||||
P4.12 - Update CLAUDE.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Expected Outcomes
|
||||
|
||||
After successful completion:
|
||||
|
||||
1. **Single Source of Truth**: `atomizer_spec.json` used everywhere
|
||||
2. **Bidirectional Sync**: Canvas ↔ Spec with no data loss
|
||||
3. **Real-time Updates**: WebSocket keeps all clients in sync
|
||||
4. **Claude Integration**: Claude can read/modify/create specs via MCP
|
||||
5. **Custom Functions**: Users can add Python extractors through UI/Claude
|
||||
6. **All Studies Migrated**: Existing configs converted to v2 format
|
||||
7. **Full Test Coverage**: Unit, integration, and e2e tests passing
|
||||
8. **Updated Documentation**: User guides reflect new workflow
|
||||
|
||||
---
|
||||
|
||||
*This prompt file enables autonomous implementation of the AtomizerSpec v2.0 system using the Ralph Loop pattern.*
|
||||
Reference in New Issue
Block a user