Mechanical Engineering Agent — PRD & Architecture

Comprehensive specification for the ME Agent using open-source tools (FreeCAD, CalculiX, OpenFOAM)

Status: Draft Phase: Phase 2 (v0.4-0.6) Discipline: #3 Mechanical Engineering

Table of contents

  1. 1. Problem Statement & Vision
    1. 1.1 The Bottleneck
    2. 1.2 Value Proposition
    3. 1.3 User Personas
  2. 2. Open-Source Tool Stack
    1. 2.1 Tool Replacement Matrix
    2. 2.2 Tool Capability Comparison
    3. 2.3 FreeCAD Headless Execution
  3. 3. Architecture
    1. 3.1 System Context
    2. 3.2 Multi-Provider LLM Layer
    3. 3.3 ME Agent Architecture
      1. 3.3.1 Task Types
      2. 3.3.2 Enclosure Generation Workflow
    4. 3.4 Tool Adapters
      1. 3.4.1 FreeCADAdapter
      2. 3.4.2 CalculiXAdapter
      3. 3.4.3 OpenFOAMAdapter
  4. 4. Data Model (Pydantic Schemas)
    1. 4.1 Input Schema
    2. 4.2 Output Schema
  5. 5. Feature Prioritization (MoSCoW)
    1. Must Have (P0) — Required for ME Agent MVP
    2. Should Have (P1) — Expected for Phase 2 release
    3. Could Have (P2) — If time permits
    4. Won’t Have (Out of Scope)
  6. 6. Integration Points
    1. 6.1 Upstream Dependencies
    2. 6.2 Downstream Consumers
    3. 6.3 Digital Thread Integration
  7. 7. Implementation Phases (14 Weeks)
    1. Phase A: Foundation (Weeks 1-3)
    2. Phase B: Core ME Agent (Weeks 4-6)
    3. Phase C: FEA Integration (Weeks 7-9)
    4. Phase D: CFD & DFA (Weeks 10-12)
    5. Phase E: Hardening & Documentation (Weeks 13-14)
    6. Timeline Visualization
  8. 8. Success Metrics & KPIs
    1. 8.1 Performance Targets
    2. 8.2 Quality Targets
    3. 8.3 Business Impact Targets
  9. 9. Risk Matrix
    1. Risk Mitigation Architecture
  10. 10. Material Library (Reference)
    1. 10.1 Polymers
    2. 10.2 Metals
    3. 10.3 Composites & Specialty
  11. 11. Reference: FreeCAD Enclosure Script
  12. 12. Glossary
  13. Related Documentation

1. Problem Statement & Vision

1.1 The Bottleneck

Mechanical engineering is a critical bottleneck in hardware product development:

Pain Point Current State Impact
Enclosure design 1-3 week manual design cycles Delays EVT gate by 2-4 weeks
Simulation licenses $30K-$100K/year for Ansys/SOLIDWORKS Prohibitive for startups and small teams
Tolerance analysis Manual spreadsheet-based, error-prone 15-20% of prototyping failures traced to tolerance stack-up errors
FEA validation Requires specialist CAE engineer $150-$200/hr contractor rate, 1-2 week lead time
Material selection Tribal knowledge, inconsistent documentation Suboptimal choices discovered late in DVT

1.2 Value Proposition

The ME Agent transforms mechanical engineering from a bottleneck into an accelerator:

Metric Before After Improvement
Enclosure design cycle 1-3 weeks 2-4 hours ~97% reduction
FEA validation 3-5 days (outsourced) 10-30 minutes ~99% reduction
Simulation license cost $30K-$100K/year $0 (open-source) 100% reduction
Tolerance analysis 1-2 days manual 5 minutes automated ~99% reduction
Material selection Ad-hoc, days 2 minutes, documented Consistent + traceable

1.3 User Personas

Persona 1: Hardware Engineer (Primary)

  • Has mechanical intuition but limited CAD/FEA time
  • Wants: “Generate an IP65 enclosure for this PCB with thermal vents”
  • Needs: Parametric STEP files, validated FEA results, tolerance reports

Persona 2: Startup Founder / Product Lead

  • Non-specialist making early-stage mechanical decisions
  • Wants: “Will this enclosure survive a 1m drop test?”
  • Needs: Pass/fail answers with safety factors, material recommendations

Persona 3: MetaForge Orchestrator (Machine-to-Machine)

  • Upstream agents (EE, SYS) triggering ME workflows automatically
  • Wants: Structured JSON inputs/outputs, deterministic execution
  • Needs: Pydantic-validated schemas, digital thread integration, artifact storage

2. Open-Source Tool Stack

The ME Agent replaces proprietary CAD/CAE tools with a fully open-source stack:

2.1 Tool Replacement Matrix

Replaces With Version Rationale
SOLIDWORKS / Fusion 360 FreeCAD 0.21+ 0.21+ (Part, PartDesign, FEM workbenches) Python scripting API, headless mode via freecadcmd, STEP/STL/IGES export, parametric modeling, FEM workbench
Ansys Mechanical FEA CalculiX 2.21+ 2.21+ Abaqus-compatible .inp file format, static/modal/thermal FEA, integrates with FreeCAD FEM workbench, GPL licensed
Ansys Fluent / CFX OpenFOAM v11+ v11+ (ESI-OpenCFD) CLI-driven with dictionary-file configuration, snappyHexMesh for meshing, buoyantSimpleFoam for thermal/flow, Apache 2.0 licensed

2.2 Tool Capability Comparison

graph LR
    subgraph "Proprietary Stack"
        A1[SOLIDWORKS<br/>$4K-$8K/yr]
        A2[Ansys FEA<br/>$30K-$50K/yr]
        A3[Ansys CFD<br/>$30K-$50K/yr]
    end

    subgraph "Open-Source Stack"
        B1[FreeCAD<br/>$0 / LGPL]
        B2[CalculiX<br/>$0 / GPL]
        B3[OpenFOAM<br/>$0 / Apache 2.0]
    end

    A1 -.->|replaced by| B1
    A2 -.->|replaced by| B2
    A3 -.->|replaced by| B3

    style A1 fill:#e74c3c,color:#fff
    style A2 fill:#e74c3c,color:#fff
    style A3 fill:#e74c3c,color:#fff
    style B1 fill:#27ae60,color:#fff
    style B2 fill:#27ae60,color:#fff
    style B3 fill:#27ae60,color:#fff

2.3 FreeCAD Headless Execution

FreeCAD supports headless operation via freecadcmd, enabling fully automated CAD generation:

# Execute a Python script headlessly (no GUI)
freecadcmd -c "exec(open('generate_enclosure.py').read())"

# Or via module execution
freecadcmd script.py -- --width 80 --depth 60 --height 25

Key APIs used by the ME Agent:

  • Part.makeBox(), Part.makeCylinder() — primitive geometry
  • Part.Shape.cut(), .fuse(), .common() — boolean operations
  • Part.Shape.exportStep() — STEP export
  • Mesh.export() — STL export
  • FemMeshGmsh — mesh generation for FEA
  • importOBJ, importSTL — format conversion

3. Architecture

3.1 System Context

flowchart TB
    subgraph "MetaForge Control Plane"
        GW[Gateway Service]
        OR[Orchestrator]
    end

    subgraph "ME Agent System"
        MA[MechanicalAgent]
        LLM[LLM Provider Layer]
        TR[Tool Registry]
    end

    subgraph "Tool Adapters"
        FC[FreeCADAdapter]
        CX[CalculiXAdapter]
        OF[OpenFOAMAdapter]
    end

    subgraph "External Tools"
        FCD[freecadcmd<br/>Headless CAD]
        CCX[ccx<br/>FEA Solver]
        OFM[OpenFOAM<br/>CFD Solver]
    end

    subgraph "Data Layer"
        N4J[Neo4j<br/>Digital Thread]
        MIO[MinIO<br/>Artifact Storage]
    end

    GW --> OR
    OR --> MA
    MA --> LLM
    MA --> TR
    TR --> FC
    TR --> CX
    TR --> OF
    FC --> FCD
    CX --> CCX
    OF --> OFM
    MA --> N4J
    MA --> MIO

    style MA fill:#9b59b6,color:#fff
    style LLM fill:#3498db,color:#fff
    style FC fill:#e67e22,color:#fff
    style CX fill:#e67e22,color:#fff
    style OF fill:#e67e22,color:#fff

3.2 Multi-Provider LLM Layer

The ME Agent uses the shared LLMProvider abstraction defined in lib/llm/. This layer provides a unified interface over multiple LLM backends.

Location: lib/llm/

// lib/llm/types.ts — Unified types across providers
interface ToolDefinition {
  name: string;
  description: string;
  parameters: JSONSchema;
}

interface ToolCall {
  id: string;
  name: string;
  arguments: Record<string, unknown>;
}

interface ToolResult {
  tool_call_id: string;
  content: string;
  is_error?: boolean;
}

interface Message {
  role: 'system' | 'user' | 'assistant' | 'tool';
  content: string | ContentBlock[];
  tool_calls?: ToolCall[];
  tool_results?: ToolResult[];
}

interface CompletionOptions {
  model?: string;
  tools?: ToolDefinition[];
  temperature?: number;
  maxTokens?: number;
  responseFormat?: { type: 'json_object' | 'text' };
  stop?: string[];
}

interface LLMResponse {
  content: string;
  tool_calls?: ToolCall[];
  usage: { prompt_tokens: number; completion_tokens: number };
  model: string;
  finish_reason: 'stop' | 'tool_use' | 'length';
}

interface LLMChunk {
  content?: string;
  tool_call?: Partial<ToolCall>;
  finish_reason?: string;
}
// lib/llm/provider.ts — Provider interface
interface LLMProvider {
  name: string;
  complete(messages: Message[], options?: CompletionOptions): Promise<LLMResponse>;
  stream(messages: Message[], options?: CompletionOptions): AsyncIterable<LLMChunk>;
}
// lib/llm/anthropic-provider.ts
import Anthropic from '@anthropic-ai/sdk';

class AnthropicProvider implements LLMProvider {
  name = 'anthropic';
  private client: Anthropic;

  constructor(config: { apiKey: string }) {
    this.client = new Anthropic({ apiKey: config.apiKey });
  }

  async complete(messages: Message[], options?: CompletionOptions): Promise<LLMResponse> {
    const response = await this.client.messages.create({
      model: options?.model ?? 'claude-sonnet-4-5-20250929',
      max_tokens: options?.maxTokens ?? 4096,
      temperature: options?.temperature ?? 0.3,
      system: this.extractSystem(messages),
      messages: this.mapMessages(messages),
      tools: options?.tools?.map(t => this.mapTool(t)),
    });

    return this.mapResponse(response);
  }

  // Maps MetaForge ToolDefinition → Anthropic tool_use format
  private mapTool(tool: ToolDefinition): Anthropic.Tool {
    return {
      name: tool.name,
      description: tool.description,
      input_schema: tool.parameters,
    };
  }

  async *stream(messages: Message[], options?: CompletionOptions): AsyncIterable<LLMChunk> {
    const stream = this.client.messages.stream({
      model: options?.model ?? 'claude-sonnet-4-5-20250929',
      max_tokens: options?.maxTokens ?? 4096,
      messages: this.mapMessages(messages),
    });

    for await (const event of stream) {
      yield this.mapChunk(event);
    }
  }
}
// lib/llm/openai-provider.ts
import OpenAI from 'openai';

class OpenAIProvider implements LLMProvider {
  name = 'openai';
  private client: OpenAI;

  constructor(config: { apiKey: string }) {
    this.client = new OpenAI({ apiKey: config.apiKey });
  }

  async complete(messages: Message[], options?: CompletionOptions): Promise<LLMResponse> {
    const response = await this.client.chat.completions.create({
      model: options?.model ?? 'gpt-4o',
      temperature: options?.temperature ?? 0.3,
      max_tokens: options?.maxTokens ?? 4096,
      messages: this.mapMessages(messages),
      tools: options?.tools?.map(t => this.mapTool(t)),
      response_format: options?.responseFormat,
    });

    return this.mapResponse(response);
  }

  // Maps MetaForge ToolDefinition → OpenAI function_calling format
  private mapTool(tool: ToolDefinition): OpenAI.ChatCompletionTool {
    return {
      type: 'function',
      function: {
        name: tool.name,
        description: tool.description,
        parameters: tool.parameters,
      },
    };
  }
}
// lib/llm/factory.ts
type ProviderType = 'anthropic' | 'openai';

interface LLMConfig {
  provider: ProviderType;
  apiKey: string;
  defaultModel?: string;
}

class LLMProviderFactory {
  static create(config: LLMConfig): LLMProvider {
    switch (config.provider) {
      case 'anthropic':
        return new AnthropicProvider({ apiKey: config.apiKey });
      case 'openai':
        return new OpenAIProvider({ apiKey: config.apiKey });
      default:
        throw new Error(`Unknown LLM provider: ${config.provider}`);
    }
  }
}

3.3 ME Agent Architecture

Location: agents/mechanical/

// agents/mechanical/mechanical-agent.ts
import { Agent, AgentConfig, ExecutionContext, AgentResult } from '@metaforge/core';
import { MechanicalInputSchema, MechanicalOutputSchema } from './schemas';

class MechanicalAgent implements Agent {
  name = 'mechanical-agent';
  version = '1.0.0';
  description = 'Mechanical engineering: CAD generation, FEA, CFD, tolerance analysis, material selection';

  private llm: LLMProvider;
  private tools: ToolRegistry;

  async initialize(config: AgentConfig): Promise<void> {
    this.llm = config.llmProvider;
    this.tools = config.toolRegistry;
  }

  registerTools(registry: ToolRegistry): void {
    registry.register(new FreeCADAdapter());
    registry.register(new CalculiXAdapter());
    registry.register(new OpenFOAMAdapter());
  }

  async execute(context: ExecutionContext): Promise<AgentResult> {
    const input = MechanicalInputSchema.parse(context.input);
    const taskType = this.classifyTask(input);

    switch (taskType) {
      case 'enclosure_generation':
        return await this.generateEnclosure(input);
      case 'fea_stress':
        return await this.runStressFEA(input);
      case 'fea_modal':
        return await this.runModalFEA(input);
      case 'fea_thermal':
        return await this.runThermalFEA(input);
      case 'cfd_thermal':
        return await this.runCFDThermal(input);
      case 'tolerance_analysis':
        return await this.runToleranceAnalysis(input);
      case 'material_selection':
        return await this.selectMaterial(input);
      case 'mechanical_bom':
        return await this.generateMechanicalBOM(input);
      case 'dfa_analysis':
        return await this.runDFAAnalysis(input);
      default:
        throw new Error(`Unknown task type: ${taskType}`);
    }
  }

  validateInput(input: unknown): ValidationResult {
    const result = MechanicalInputSchema.safeParse(input);
    return result.success
      ? { valid: true }
      : { valid: false, errors: result.error.issues };
  }

  validateOutput(output: unknown): ValidationResult {
    const result = MechanicalOutputSchema.safeParse(output);
    return result.success
      ? { valid: true }
      : { valid: false, errors: result.error.issues };
  }
}

3.3.1 Task Types

# Task Type Description Primary Tool Output Artifacts
1 enclosure_generation Generate parametric enclosure from PCB constraints FreeCAD .FCStd, .step, .stl
2 fea_stress Static stress analysis under mechanical loads CalculiX stress map, safety factor, .inp
3 fea_modal Natural frequency / modal analysis CalculiX mode shapes, frequencies
4 fea_thermal Steady-state / transient thermal FEA CalculiX temperature map, heat flux
5 cfd_thermal Computational fluid dynamics for airflow / cooling OpenFOAM velocity/temperature fields
6 tolerance_analysis Worst-case and RSS tolerance stack-up None (analytical) tolerance report JSON
7 material_selection Select material from library with rationale None (database) material spec JSON
8 mechanical_bom Generate mechanical BOM from CAD model FreeCAD BOM CSV/JSON
9 dfa_analysis Design for Assembly scoring and feedback None (analytical) DFA report JSON

3.3.2 Enclosure Generation Workflow

sequenceDiagram
    participant OR as Orchestrator
    participant MA as ME Agent
    participant LLM as LLM Provider
    participant FC as FreeCADAdapter
    participant CX as CalculiXAdapter
    participant S as Storage (MinIO)

    OR->>MA: Execute(enclosure_generation, constraints)
    MA->>LLM: "Generate FreeCAD Python script for enclosure"
    Note over LLM: LLM receives PCB dims, mounting holes,<br/>thermal constraints, IP rating, material

    LLM-->>MA: FreeCAD Python script

    MA->>MA: Validate script (AST check, sandbox rules)
    MA->>FC: executeScript(script)
    FC->>FC: freecadcmd (headless)
    FC-->>MA: .FCStd + .step + .stl

    MA->>MA: Extract geometry metadata
    MA->>CX: runStressFEA(mesh, loads)
    CX-->>MA: stress results, safety factor

    alt Safety factor < 1.5
        MA->>LLM: "Revise: wall too thin, SF=1.2"
        LLM-->>MA: Revised script
        MA->>FC: executeScript(revised)
    end

    MA->>S: Store artifacts (.step, .stl, FEA report)
    MA-->>OR: AgentResult {artifacts, metadata, traces}

3.4 Tool Adapters

3.4.1 FreeCADAdapter

Location: tools/freecad-adapter.ts

interface FreeCADAdapter extends ToolAdapter {
  name: 'freecad';
  version: string;
  capabilities: ['parametric_modeling', 'step_export', 'stl_export', 'mesh_generation', 'boolean_ops'];

  // Detection
  detect(): Promise<ToolInstall | null>;

  // Script execution (headless)
  executeScript(script: string, args?: Record<string, string>): Promise<FreeCADResult>;

  // Export operations
  exportSTEP(modelPath: string, outputPath: string): Promise<string>;
  exportSTL(modelPath: string, outputPath: string, meshParams?: MeshParams): Promise<string>;

  // Mesh generation (for FEA)
  generateMesh(modelPath: string, params: MeshParams): Promise<MeshResult>;

  // Model inspection
  getModelInfo(modelPath: string): Promise<ModelInfo>;
  getBoundingBox(modelPath: string): Promise<BoundingBox>;

  // Health
  healthCheck(): Promise<HealthStatus>;
}

interface FreeCADResult {
  exitCode: number;
  stdout: string;
  stderr: string;
  outputFiles: string[];       // paths to generated files
  modelInfo?: ModelInfo;
}

interface MeshParams {
  algorithm: 'gmsh' | 'netgen';
  maxElementSize: number;      // mm
  minElementSize: number;      // mm
  order: 1 | 2;               // linear or quadratic elements
}

interface MeshResult {
  meshFile: string;            // path to .inp or .unv mesh file
  nodeCount: number;
  elementCount: number;
  qualityMetrics: {
    minAngle: number;
    maxAspectRatio: number;
    jacobianRatio: number;
  };
}

interface ModelInfo {
  volume: number;              // mm³
  surfaceArea: number;         // mm²
  boundingBox: BoundingBox;
  centerOfMass: [number, number, number];
  bodyCount: number;
}

interface BoundingBox {
  min: [number, number, number];
  max: [number, number, number];
  dimensions: [number, number, number];  // width, depth, height in mm
}

Implementation sketch:

class FreeCADAdapter implements ToolAdapter {
  name = 'freecad';
  version = '1.0.0';

  async detect(): Promise<ToolInstall | null> {
    const searchPaths = [
      '/usr/bin/freecadcmd',
      '/usr/local/bin/freecadcmd',
      '/snap/bin/freecadcmd',
      '/Applications/FreeCAD.app/Contents/MacOS/FreeCADCmd',
      'C:\\Program Files\\FreeCAD 0.21\\bin\\FreeCADCmd.exe',
    ];

    for (const path of searchPaths) {
      try {
        const result = await exec(`${path} --version`);
        const version = this.parseVersion(result.stdout);
        if (semver.gte(version, '0.21.0')) {
          return { found: true, version, path };
        }
      } catch { /* not found at this path */ }
    }
    return null;
  }

  async executeScript(script: string, args?: Record<string, string>): Promise<FreeCADResult> {
    const scriptPath = await this.writeScript(script);      // temp file
    const argStr = args
      ? Object.entries(args).map(([k, v]) => `--${k} ${v}`).join(' ')
      : '';

    const result = await exec(
      `freecadcmd ${scriptPath} -- ${argStr}`,
      { timeout: 120_000, cwd: this.workDir }               // 2 min timeout
    );

    return {
      exitCode: result.exitCode,
      stdout: result.stdout,
      stderr: result.stderr,
      outputFiles: this.findOutputFiles(this.workDir),
    };
  }

  async exportSTEP(modelPath: string, outputPath: string): Promise<string> {
    const script = `
import FreeCAD
import Part
doc = FreeCAD.openDocument("${modelPath}")
Part.export(doc.Objects, "${outputPath}")
doc.close()
    `;
    await this.executeScript(script);
    return outputPath;
  }

  async generateMesh(modelPath: string, params: MeshParams): Promise<MeshResult> {
    const script = `
import FreeCAD
import Fem
import femmesh.femmesh2mesh

doc = FreeCAD.openDocument("${modelPath}")
shape = doc.Objects[0].Shape

femmesh = doc.addObject("Fem::FemMeshShapeNetgenObject", "FEMMesh")
femmesh.Shape = doc.Objects[0]
femmesh.MaxSize = ${params.maxElementSize}
femmesh.MinSize = ${params.minElementSize}
femmesh.SecondOrder = ${params.order === 2 ? 'True' : 'False'}
doc.recompute()

# Export as .inp for CalculiX
import feminout.importInpMesh
feminout.importInpMesh.export([femmesh], "${modelPath.replace('.FCStd', '.inp')}")
doc.close()
    `;

    const result = await this.executeScript(script);
    const meshFile = modelPath.replace('.FCStd', '.inp');

    return {
      meshFile,
      nodeCount: this.extractNodeCount(result.stdout),
      elementCount: this.extractElementCount(result.stdout),
      qualityMetrics: this.extractMeshQuality(result.stdout),
    };
  }
}

3.4.2 CalculiXAdapter

Location: tools/calculix-adapter.ts

interface CalculiXAdapter extends ToolAdapter {
  name: 'calculix';
  version: string;
  capabilities: ['static_stress', 'modal_analysis', 'thermal_steady', 'thermal_transient'];

  // Detection
  detect(): Promise<ToolInstall | null>;

  // FEA operations
  runStaticStress(config: StaticStressConfig): Promise<StressResult>;
  runModalAnalysis(config: ModalConfig): Promise<ModalResult>;
  runThermalAnalysis(config: ThermalConfig): Promise<ThermalResult>;

  // Low-level
  execute(inpFile: string): Promise<CalculiXResult>;
  parseResults(frdFile: string): Promise<FEAFieldData>;
}

interface StaticStressConfig {
  meshFile: string;            // .inp mesh file from FreeCAD
  material: MaterialProperties;
  loads: Load[];
  constraints: BoundaryCondition[];
}

interface StressResult {
  maxVonMises: number;         // MPa
  maxDisplacement: number;     // mm
  safetyFactor: number;        // yield_strength / max_stress
  stressField: string;         // path to .frd results file
  criticalLocations: CriticalLocation[];
  passed: boolean;             // safetyFactor >= minSafetyFactor
}

interface ModalConfig {
  meshFile: string;
  material: MaterialProperties;
  constraints: BoundaryCondition[];
  numModes: number;            // number of modes to extract (typically 6-10)
}

interface ModalResult {
  frequencies: number[];       // Hz, natural frequencies
  modeShapes: string;          // path to .frd mode shape data
  participationFactors: number[];
  passed: boolean;             // no modes within excitation frequency range
}

interface ThermalConfig {
  meshFile: string;
  material: MaterialProperties;
  heatSources: HeatSource[];
  convection: ConvectionBC[];
  radiation?: RadiationBC[];
  analysisType: 'steady_state' | 'transient';
  timeStep?: number;           // seconds (for transient)
  totalTime?: number;          // seconds (for transient)
}

interface ThermalResult {
  maxTemperature: number;      // °C
  minTemperature: number;      // °C
  temperatureField: string;    // path to .frd results
  heatFlux: number;            // W/m²
  thermalResistance: number;   // °C/W
  hotspots: Hotspot[];
  passed: boolean;             // maxTemp <= maxAllowable
}

interface Load {
  type: 'force' | 'pressure' | 'gravity' | 'centrifugal';
  magnitude: number;
  direction?: [number, number, number];
  surface?: string;            // surface set name
}

interface BoundaryCondition {
  type: 'fixed' | 'displacement' | 'symmetry';
  surface: string;             // surface set name
  values?: [number, number, number];
}

interface HeatSource {
  type: 'volume' | 'surface';
  power: number;               // Watts
  location: string;            // body or surface set name
}

interface ConvectionBC {
  surface: string;
  htc: number;                 // W/(m²·K), heat transfer coefficient
  ambientTemp: number;         // °C
}

interface Hotspot {
  location: [number, number, number];
  temperature: number;         // °C
  nearestComponent?: string;
}

Implementation sketch:

class CalculiXAdapter implements ToolAdapter {
  name = 'calculix';
  version = '1.0.0';

  async detect(): Promise<ToolInstall | null> {
    try {
      const result = await exec('ccx -v');
      const version = this.parseVersion(result.stdout);
      return { found: true, version, path: await which('ccx') };
    } catch {
      return null;
    }
  }

  async runStaticStress(config: StaticStressConfig): Promise<StressResult> {
    // 1. Generate .inp file with material, loads, BCs
    const inpContent = this.buildStaticInp(config);
    const inpPath = path.join(this.workDir, 'stress_analysis.inp');
    await writeFile(inpPath, inpContent);

    // 2. Execute CalculiX
    const result = await exec(`ccx -i ${inpPath.replace('.inp', '')}`, {
      timeout: 300_000,  // 5 min timeout
      cwd: this.workDir,
    });

    // 3. Parse .frd results file
    const frdPath = inpPath.replace('.inp', '.frd');
    const fieldData = await this.parseResults(frdPath);

    return {
      maxVonMises: fieldData.maxVonMises,
      maxDisplacement: fieldData.maxDisplacement,
      safetyFactor: config.material.yieldStrength / fieldData.maxVonMises,
      stressField: frdPath,
      criticalLocations: fieldData.criticalLocations,
      passed: (config.material.yieldStrength / fieldData.maxVonMises) >= 1.5,
    };
  }

  private buildStaticInp(config: StaticStressConfig): string {
    return `
*HEADING
MetaForge ME Agent - Static Stress Analysis
*INCLUDE, INPUT=${config.meshFile}
*MATERIAL, NAME=MATERIAL1
*ELASTIC
${config.material.youngsModulus}, ${config.material.poissonsRatio}
*DENSITY
${config.material.density}
*SOLID SECTION, ELSET=EALL, MATERIAL=MATERIAL1
*STEP
*STATIC
*BOUNDARY
${this.formatBoundaryConditions(config.constraints)}
*CLOAD
${this.formatLoads(config.loads)}
*NODE FILE
U
*EL FILE
S
*END STEP
    `.trim();
  }
}

3.4.3 OpenFOAMAdapter

Location: tools/openfoam-adapter.ts

interface OpenFOAMAdapter extends ToolAdapter {
  name: 'openfoam';
  version: string;
  capabilities: ['cfd_thermal', 'flow_simulation', 'mesh_generation'];

  // Detection
  detect(): Promise<ToolInstall | null>;

  // Case management
  setupCase(config: CFDCaseConfig): Promise<string>;  // returns case directory path

  // Meshing
  generateMesh(caseDir: string, geometry: string): Promise<OpenFOAMMeshResult>;

  // Solving
  runSolver(caseDir: string, solver: OpenFOAMSolver): Promise<CFDResult>;

  // Post-processing
  extractResults(caseDir: string): Promise<CFDFieldData>;
}

type OpenFOAMSolver = 'buoyantSimpleFoam' | 'simpleFoam' | 'buoyantPimpleFoam';

interface CFDCaseConfig {
  solver: OpenFOAMSolver;
  geometry: {
    stlFile: string;           // enclosure geometry as STL
    inlets?: Surface[];
    outlets?: Surface[];
    walls?: Surface[];
  };
  thermalSources: ThermalSource[];
  fluidProperties: FluidProperties;
  meshSettings: {
    baseCellSize: number;      // mm
    refinementLevels: number;
    boundaryLayers: number;
  };
}

interface ThermalSource {
  name: string;
  type: 'fixedTemperature' | 'fixedHeatFlux';
  value: number;               // °C or W/m²
  surface: string;             // patch name
}

interface FluidProperties {
  fluid: 'air' | 'water' | 'custom';
  density?: number;            // kg/m³
  viscosity?: number;          // Pa·s
  thermalConductivity?: number; // W/(m·K)
  specificHeat?: number;       // J/(kg·K)
  beta?: number;               // 1/K, thermal expansion coefficient
}

interface CFDResult {
  converged: boolean;
  iterations: number;
  residuals: { [field: string]: number };
  maxVelocity: number;         // m/s
  maxTemperature: number;      // °C
  avgTemperature: number;      // °C
  heatTransferRate: number;    // W
  pressureDrop?: number;       // Pa
  results: CFDFieldData;
}

interface CFDFieldData {
  temperatureField: string;    // path to OpenFOAM field data
  velocityField: string;
  pressureField: string;
  hotspots: Hotspot[];
}

Implementation sketch:

class OpenFOAMAdapter implements ToolAdapter {
  name = 'openfoam';
  version = '1.0.0';

  async detect(): Promise<ToolInstall | null> {
    try {
      const result = await exec('simpleFoam -help');
      // OpenFOAM prints version in the banner
      const version = this.parseVersion(result.stdout);
      return { found: true, version, path: process.env.WM_PROJECT_DIR || '' };
    } catch {
      return null;
    }
  }

  async setupCase(config: CFDCaseConfig): Promise<string> {
    const caseDir = path.join(this.workDir, 'cfd_case');
    await mkdir(caseDir, { recursive: true });

    // Create OpenFOAM directory structure
    await mkdir(path.join(caseDir, '0'));        // initial conditions
    await mkdir(path.join(caseDir, 'constant')); // mesh + properties
    await mkdir(path.join(caseDir, 'system'));    // solver settings

    // Write dictionary files
    await this.writeTransportProperties(caseDir, config.fluidProperties);
    await this.writeFvSchemes(caseDir);
    await this.writeFvSolution(caseDir, config.solver);
    await this.writeControlDict(caseDir, config.solver);
    await this.writeInitialConditions(caseDir, config);
    await this.writeSnappyHexMeshDict(caseDir, config);

    return caseDir;
  }

  async generateMesh(caseDir: string, stlFile: string): Promise<OpenFOAMMeshResult> {
    // 1. Block mesh (background mesh)
    await exec('blockMesh', { cwd: caseDir, timeout: 60_000 });

    // 2. Copy STL to triSurface
    const triDir = path.join(caseDir, 'constant', 'triSurface');
    await mkdir(triDir, { recursive: true });
    await copyFile(stlFile, path.join(triDir, 'enclosure.stl'));

    // 3. snappyHexMesh
    await exec('snappyHexMesh -overwrite', { cwd: caseDir, timeout: 300_000 });

    // 4. Check mesh quality
    const checkResult = await exec('checkMesh', { cwd: caseDir });
    return this.parseMeshCheck(checkResult.stdout);
  }

  async runSolver(caseDir: string, solver: OpenFOAMSolver): Promise<CFDResult> {
    const result = await exec(solver, {
      cwd: caseDir,
      timeout: 600_000,  // 10 min timeout
    });

    return {
      converged: this.checkConvergence(result.stdout),
      iterations: this.extractIterations(result.stdout),
      residuals: this.extractResiduals(result.stdout),
      ...await this.extractResults(caseDir),
    };
  }
}

4. Data Model (Pydantic Schemas)

4.1 Input Schema

# domain_agents/mechanical/schema.py
from __future__ import annotations
from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field

# ─── Sub-schemas ───

class Dimensions(BaseModel):
    x: float = Field(gt=0, description="Width in mm")
    y: float = Field(gt=0, description="Depth in mm")
    z: float = Field(gt=0, description="Height in mm")
    unit: str = Field(default="mm")

class MountingHole(BaseModel):
    x: float = Field(description="X position in mm from origin")
    y: float = Field(description="Y position in mm from origin")
    diameter: float = Field(gt=0, description="Hole diameter in mm")
    type: str = Field(default="through", pattern="^(through|threaded|standoff)$")
    thread_spec: Optional[str] = Field(default=None, description='e.g. "M3x0.5"')

class ConnectorCutout(BaseModel):
    name: str = Field(description='e.g. "USB-C", "barrel jack"')
    width: float = Field(gt=0)
    height: float = Field(gt=0)
    face: str = Field(pattern="^(front|back|left|right|top|bottom)$")
    x: float
    y: float

class ComponentHeight(BaseModel):
    top: float = Field(gt=0, description="Max component height above PCB, mm")
    bottom: float = Field(ge=0, description="Max component height below PCB, mm")

class PCBConstraints(BaseModel):
    dimensions: Dimensions = Field(description="PCB outline dimensions")
    mounting_holes: list[MountingHole] = Field(min_length=1)
    max_component_height: ComponentHeight
    connector_cutouts: list[ConnectorCutout] = Field(default_factory=list)
    clearance: float = Field(default=2, gt=0, description="Min clearance around PCB, mm")

class HeatSource(BaseModel):
    name: str
    power: float = Field(gt=0, description="Watts")
    position: tuple[float, float]
    footprint: tuple[float, float] = Field(description="(width, depth)")

class CoolingMethod(str, Enum):
    natural_convection = "natural_convection"
    forced_convection = "forced_convection"
    heatsink = "heatsink"
    liquid = "liquid"

class ThermalConstraints(BaseModel):
    total_dissipation: float = Field(gt=0, description="Total power dissipation in Watts")
    heat_sources: list[HeatSource] = Field(min_length=1)
    max_junction_temp: float = Field(default=85, description="Max junction temperature degC")
    ambient_temp: float = Field(default=25, description="Ambient temperature degC")
    cooling_method: CoolingMethod = CoolingMethod.natural_convection

class FrequencyRange(BaseModel):
    min: float
    max: float

class VibrationSpec(BaseModel):
    g_rms: Optional[float] = Field(default=None, description="Random vibration level")
    frequency_range: Optional[FrequencyRange] = None

class ShockSpec(BaseModel):
    peak_g: Optional[float] = None
    duration: Optional[float] = Field(default=None, description="ms")

class IPRating(str, Enum):
    IP20 = "IP20"
    IP44 = "IP44"
    IP54 = "IP54"
    IP65 = "IP65"
    IP67 = "IP67"
    IP68 = "IP68"

class TemperatureRange(BaseModel):
    min: float = Field(default=-20, description="degC")
    max: float = Field(default=60, description="degC")

class EnvironmentalConstraints(BaseModel):
    ip_rating: IPRating = IPRating.IP20
    temperature_range: TemperatureRange = Field(default_factory=TemperatureRange)
    vibration: Optional[VibrationSpec] = None
    shock: Optional[ShockSpec] = None
    altitude: Optional[float] = Field(default=None, description="Max altitude in meters")

class ManufacturingMethod(str, Enum):
    fdm = "3d_print_fdm"
    sla = "3d_print_sla"
    sls = "3d_print_sls"
    injection_molding = "injection_molding"
    cnc_machining = "cnc_machining"
    sheet_metal = "sheet_metal"
    casting = "casting"

class JoinMethod(str, Enum):
    snap_fit = "snap_fit"
    screws = "screws"
    ultrasonic_weld = "ultrasonic_weld"
    adhesive = "adhesive"
    gasket = "gasket"

class SurfaceFinish(str, Enum):
    as_printed = "as_printed"
    sanded = "sanded"
    painted = "painted"
    textured = "textured"
    polished = "polished"

class MechanicalRequirements(BaseModel):
    material: Optional[str] = Field(default=None, description='Preferred material or "auto"')
    manufacturing_method: ManufacturingMethod = ManufacturingMethod.fdm
    wall_thickness: Optional[float] = Field(default=None, gt=0, description="Min wall thickness mm")
    join_method: JoinMethod = JoinMethod.screws
    surface_finish: Optional[SurfaceFinish] = None
    min_safety_factor: float = Field(default=2.0, gt=0)

class TaskType(str, Enum):
    enclosure_generation = "enclosure_generation"
    fea_stress = "fea_stress"
    fea_modal = "fea_modal"
    fea_thermal = "fea_thermal"
    cfd_thermal = "cfd_thermal"
    tolerance_analysis = "tolerance_analysis"
    material_selection = "material_selection"
    mechanical_bom = "mechanical_bom"
    dfa_analysis = "dfa_analysis"

class ProjectPhase(str, Enum):
    POC = "POC"
    EVT = "EVT"
    DVT = "DVT"
    PVT = "PVT"

# ─── Top-level Input ───

class MechanicalInput(BaseModel):
    task_type: TaskType
    pcb: Optional[PCBConstraints] = None
    thermal: Optional[ThermalConstraints] = None
    environmental: Optional[EnvironmentalConstraints] = None
    mechanical: Optional[MechanicalRequirements] = None
    existing_model: Optional[str] = Field(default=None, description="Path to .FCStd or .step file")
    project_phase: ProjectPhase = ProjectPhase.EVT

4.2 Output Schema

# ─── Output Sub-schemas ───

class CADArtifact(BaseModel):
    fcstd_path: str = Field(description="Path to FreeCAD native file")
    step_path: str = Field(description="Path to STEP file")
    stl_path: str = Field(description="Path to STL mesh file")
    volume: float = Field(gt=0, description="Part volume in mm3")
    surface_area: float = Field(gt=0, description="Surface area in mm2")
    bounding_box: Dimensions
    mass_grams: float = Field(gt=0, description="Estimated mass in grams")

class MeshInfo(BaseModel):
    node_count: int = Field(gt=0)
    element_count: int = Field(gt=0)
    element_type: str

class CriticalLocation(BaseModel):
    position: tuple[float, float, float]
    stress: float
    description: str

class StaticStressResults(BaseModel):
    analysis_type: str = Field(default="static_stress")
    max_von_mises: float = Field(description="MPa")
    max_displacement: float = Field(description="mm")
    safety_factor: float
    critical_locations: list[CriticalLocation]

class ModalResults(BaseModel):
    analysis_type: str = Field(default="modal")
    natural_frequencies: list[float] = Field(description="Hz")
    mode_descriptions: list[str]

class Hotspot(BaseModel):
    position: tuple[float, float, float]
    temperature: float
    nearest_component: Optional[str] = None

class ThermalResults(BaseModel):
    analysis_type: str = Field(default="thermal")
    max_temperature: float = Field(description="degC")
    min_temperature: float = Field(description="degC")
    thermal_resistance: float = Field(description="degC/W")
    hotspots: list[Hotspot]

class FEAResult(BaseModel):
    analysis_type: str = Field(pattern="^(static_stress|modal|thermal)$")
    solver: str = Field(default="calculix")
    mesh_info: MeshInfo
    results: StaticStressResults | ModalResults | ThermalResults
    passed: bool
    results_file_path: str = Field(description="Path to .frd results file")

class ToleranceDimension(BaseModel):
    name: str
    nominal: float = Field(description="mm")
    tolerance: float = Field(description="+/- mm")
    distribution: str = Field(default="normal", pattern="^(uniform|normal)$")

class WorstCaseResult(BaseModel):
    gap_min: float = Field(description="mm")
    gap_max: float = Field(description="mm")
    passed: bool

class RSSResult(BaseModel):
    gap_mean: float = Field(description="mm")
    gap_std_dev: float = Field(description="mm")
    cpk: float = Field(description="Process capability index")
    yield_percent: float = Field(description="Expected yield %")
    passed: bool

class ToleranceStack(BaseModel):
    name: str
    dimensions: list[ToleranceDimension]
    worst_case: WorstCaseResult
    rss: RSSResult

class MaterialProperties(BaseModel):
    density: float = Field(description="g/cm3")
    youngs_modulus: float = Field(description="GPa")
    yield_strength: float = Field(description="MPa")
    tensile_strength: float = Field(description="MPa")
    elongation: float = Field(description="% at break")
    thermal_conductivity: float = Field(description="W/(m*K)")
    specific_heat: float = Field(description="J/(kg*K)")
    cte: float = Field(description="um/(m*K), coeff. of thermal expansion")
    max_service_temp: float = Field(description="degC")
    flammability: Optional[str] = Field(default=None, description="UL94 rating")

class MaterialAlternative(BaseModel):
    name: str
    tradeoff: str

class SupplierInfo(BaseModel):
    trade_name: Optional[str] = None
    manufacturer: Optional[str] = None
    unit_cost: Optional[float] = Field(default=None, description="$/kg")

class MaterialSpec(BaseModel):
    name: str
    category: str = Field(pattern="^(polymer|metal|ceramic|composite)$")
    grade: Optional[str] = None
    properties: MaterialProperties
    rationale: str = Field(description="Why this material was selected")
    alternatives: list[MaterialAlternative] = Field(default_factory=list)
    supplier_info: Optional[SupplierInfo] = None

class MechanicalBOMEntry(BaseModel):
    item_number: int = Field(gt=0)
    part_name: str
    material: str
    quantity: int = Field(gt=0)
    manufacturing_method: str
    estimated_cost: float = Field(description="USD per unit")
    weight: float = Field(description="grams")
    drawing: Optional[str] = Field(default=None, description="Path to drawing file")

class DFAIssue(BaseModel):
    severity: str = Field(pattern="^(critical|warning|info)$")
    part: str
    issue: str
    recommendation: str

class DFAScore(BaseModel):
    total_parts: int = Field(gt=0)
    theoretical_min_parts: int = Field(gt=0)
    dfa_index: float = Field(ge=0, le=100, description="DFA efficiency score")
    assembly_time: float = Field(description="Estimated assembly time in seconds")
    issues: list[DFAIssue]

class DigitalThreadLinks(BaseModel):
    requirements_traced: list[str] = Field(description="Requirement IDs addressed")
    design_elements: list[str] = Field(description="Design element IDs created")
    validation_evidence: list[str] = Field(description="FEA/test result IDs")

# ─── Top-level Output ───

class MechanicalOutput(BaseModel):
    task_type: str
    cad_artifacts: Optional[CADArtifact] = None
    fea_results: Optional[FEAResult] = None
    tolerance_stacks: Optional[list[ToleranceStack]] = None
    material_spec: Optional[MaterialSpec] = None
    mechanical_bom: Optional[list[MechanicalBOMEntry]] = None
    dfa_score: Optional[DFAScore] = None
    digital_thread: DigitalThreadLinks
    warnings: list[str] = Field(default_factory=list)
    recommendations: list[str] = Field(default_factory=list)

5. Feature Prioritization (MoSCoW)

Must Have (P0) — Required for ME Agent MVP

Feature Description Dependency
Enclosure generation LLM generates FreeCAD script from PCB constraints → STEP/STL output FreeCADAdapter
FreeCAD adapter Headless script execution, STEP/STL export, mesh generation FreeCAD 0.21+
CalculiX adapter Static stress FEA via .inp files, result parsing CalculiX 2.21+
Material selection Select from built-in library (30+ materials) with documented rationale Material DB
Tolerance stack-up Worst-case + RSS analysis, Cpk calculation None (analytical)
Mechanical BOM Extract BOM from CAD model with cost estimates FreeCADAdapter
Multi-provider LLM Anthropic + OpenAI provider abstraction (LLM Provider) anthropic, openai
Pydantic validation Input/output schema validation for all task types pydantic

Should Have (P1) — Expected for Phase 2 release

Feature Description Dependency
OpenFOAM adapter CFD thermal simulation for airflow/cooling analysis OpenFOAM v11+
CFD thermal analysis Predict hotspots, airflow patterns in enclosures OpenFOAMAdapter
DFA analysis Design for Assembly scoring and improvement recommendations None (analytical)
STEP/STL export Multi-format export with configurable mesh quality FreeCADAdapter
FEA thermal Steady-state thermal analysis via CalculiX CalculiXAdapter

Could Have (P2) — If time permits

Feature Description Dependency
Modal/vibration analysis Natural frequency extraction, harmonic response CalculiXAdapter
Impact/drop simulation Transient FEA for drop test prediction CalculiXAdapter
2D tolerance stack visualization Generate tolerance stack-up diagram as SVG None
3D tolerance analysis GD&T-aware tolerance analysis FreeCADAdapter
Parametric sweep Run multiple design variants and compare FEA results All adapters

Won’t Have (Out of Scope)

Feature Rationale
Complex multi-body assemblies Requires assembly solver; defer to Phase 3
Injection mold tooling design Specialized DFM; separate agent scope
Sheet metal unfolding/DFM Requires dedicated FreeCAD Sheet Metal workbench integration
Fluid-structure interaction (FSI) Coupled FEA+CFD; too complex for initial release
Topology optimization Experimental in FreeCAD; defer to Phase 3

6. Integration Points

6.1 Upstream Dependencies

flowchart LR
    REQ[REQ Agent<br/>Constraints] --> ME
    EE[EE Agent<br/>PCB Outline, Thermal Map] --> ME
    ID[ID Agent<br/>Form Factor, Aesthetics] --> ME
    SYS[SYS Agent<br/>Interface Definitions] --> ME

    ME[ME Agent]

    style ME fill:#9b59b6,color:#fff
    style REQ fill:#3498db,color:#fff
    style EE fill:#3498db,color:#fff
    style ID fill:#3498db,color:#fff
    style SYS fill:#3498db,color:#fff
Source Agent Data Consumed Format
REQ Mechanical constraints (dimensions, IP rating, temp range, vibration) constraints.jsonMechanicalConstraints
EE PCB outline dimensions, mounting holes, component heights, thermal dissipation map pcb-constraints.jsonPCBConstraintsSchema
ID Form factor envelope, aesthetic constraints, material palette form-factor.jsonEnvironmentalConstraintsSchema
SYS Mechanical interface definitions (mounting, connectors, cable routing) interfaces.jsonConnectorCutoutSchema

6.2 Downstream Consumers

flowchart LR
    ME[ME Agent] --> MFG[MFG Agent<br/>STEP + BOM for DFM]
    ME --> SIM[SIM Agent<br/>Mesh for Multi-physics]
    ME --> TST[TST Agent<br/>Test Specifications]
    ME --> SC[SC Agent<br/>Mechanical BOM]
    ME --> COST[Cost Agent<br/>Material + Mfg Estimates]

    style ME fill:#9b59b6,color:#fff
    style MFG fill:#27ae60,color:#fff
    style SIM fill:#27ae60,color:#fff
    style TST fill:#27ae60,color:#fff
    style SC fill:#27ae60,color:#fff
    style COST fill:#27ae60,color:#fff
Target Agent Data Provided Format
MFG STEP file + mechanical BOM for DFM review .step, mechanical-bom.json
SIM FEA mesh for multi-physics coupled simulation .inp mesh, .frd results
TST Mechanical test specifications (drop, vibration, thermal cycling) mech-test-spec.json
SC Mechanical BOM (fasteners, gaskets, enclosure parts) mechanical-bom.csv
Cost Material costs, manufacturing method estimates cost-estimate.json

6.3 Digital Thread Integration

The ME Agent links all artifacts to the Neo4j digital thread:

// Requirement → Design Element → Material → FEA Validation
MATCH (req:Requirement {id: 'REQ-MECH-001'})
CREATE (design:DesignElement {
  id: 'DE-ENC-001',
  type: 'enclosure',
  cadFile: 'enclosure_v1.step',
  agent: 'mechanical-agent'
})
CREATE (mat:Material {
  name: 'ABS',
  grade: 'Ultramid A3WG6',
  yieldStrength: 85
})
CREATE (fea:ValidationEvidence {
  id: 'FEA-001',
  type: 'static_stress',
  safetyFactor: 2.8,
  passed: true,
  solver: 'calculix'
})

CREATE (req)-[:IMPLEMENTED_BY]->(design)
CREATE (design)-[:USES_MATERIAL]->(mat)
CREATE (design)-[:VALIDATED_BY]->(fea)

7. Implementation Phases (14 Weeks)

Phase A: Foundation (Weeks 1-3)

Goal: Multi-provider LLM layer + base agent infrastructure + FreeCAD adapter

Week Deliverables
Wk 1 LLMProvider interface, AnthropicProvider, OpenAIProvider, LLMProviderFactory, unit tests
Wk 2 FreeCADAdapter: detection, headless script execution, STEP/STL export, mesh generation
Wk 3 MechanicalAgent base class: task routing, input/output Pydantic schemas, agent lifecycle

Exit Criteria:

  • LLMProviderFactory.create('anthropic') returns working provider
  • FreeCADAdapter.detect() finds FreeCAD installation
  • FreeCADAdapter.executeScript() runs a hello-world script headlessly
  • All Pydantic schemas pass type checking

Phase B: Core ME Agent (Weeks 4-6)

Goal: Enclosure generation + material selection + mechanical BOM

Week Deliverables
Wk 4 Enclosure generation: LLM prompt engineering, FreeCAD script templates, parametric enclosure generation from PCB constraints
Wk 5 Material selection engine: material library (30+ entries), selection algorithm, rationale generation
Wk 6 Mechanical BOM generation: extract parts from FreeCAD model, cost estimation, BOM export (CSV/JSON)

Exit Criteria:

  • Given PCB dimensions + mounting holes → generates valid STEP enclosure in <15 min
  • Material selection returns documented rationale with alternatives
  • Mechanical BOM exports with correct item count and estimated costs

Phase C: FEA Integration (Weeks 7-9)

Goal: CalculiX adapter + tolerance analysis + FEA workflows

Week Deliverables
Wk 7 CalculiXAdapter: detection, .inp file generation, solver execution, .frd parsing
Wk 8 Static stress FEA workflow: mesh from FreeCAD → CalculiX → safety factor report
Wk 9 Tolerance stack-up analysis: worst-case + RSS calculation, Cpk, yield prediction

Exit Criteria:

  • CalculiX adapter runs static stress analysis on generated enclosure
  • Safety factor calculation matches manual verification within 5%
  • Tolerance analysis produces correct worst-case and RSS results

Phase D: CFD & DFA (Weeks 10-12)

Goal: OpenFOAM adapter + DFA analysis + integration testing

Week Deliverables
Wk 10 OpenFOAMAdapter: case setup, snappyHexMesh, buoyantSimpleFoam execution
Wk 11 CFD thermal workflow: enclosure STL → mesh → thermal simulation → hotspot report
Wk 12 DFA analysis engine + full integration testing (enclosure → FEA → CFD → BOM pipeline)

Exit Criteria:

  • OpenFOAM adapter runs thermal CFD on enclosure geometry
  • DFA analysis produces actionable recommendations
  • End-to-end pipeline: PCB constraints → enclosure → FEA → BOM completes successfully

Phase E: Hardening & Documentation (Weeks 13-14)

Goal: Error handling, CLI integration, documentation

Week Deliverables
Wk 13 Error handling: retry logic, timeout management, graceful degradation when tools unavailable. CLI commands: forge run mechanical-enclosure, forge run mechanical-fea
Wk 14 Documentation: user guide, API reference, example workflows. Performance optimization, edge case testing

Exit Criteria:

  • All error paths tested with mocked tool failures
  • CLI commands registered and documented
  • 80% code coverage on agent and adapter code

Timeline Visualization

gantt
    title ME Agent Implementation (14 Weeks)
    dateFormat YYYY-MM-DD
    axisFormat %b %d

    section Phase A: Foundation
    LLM Provider Layer       :a1, 2026-01-05, 7d
    FreeCAD Adapter          :a2, after a1, 7d
    ME Agent Base + Schemas  :a3, after a2, 7d

    section Phase B: Core
    Enclosure Generation     :b1, after a3, 7d
    Material Selection       :b2, after b1, 7d
    Mechanical BOM           :b3, after b2, 7d

    section Phase C: FEA
    CalculiX Adapter         :c1, after b3, 7d
    Stress FEA Workflow      :c2, after c1, 7d
    Tolerance Analysis       :c3, after c2, 7d

    section Phase D: CFD & DFA
    OpenFOAM Adapter         :d1, after c3, 7d
    CFD Thermal Workflow     :d2, after d1, 7d
    DFA + Integration Tests  :d3, after d2, 7d

    section Phase E: Hardening
    Error Handling + CLI     :e1, after d3, 7d
    Docs + Optimization      :e2, after e1, 7d

8. Success Metrics & KPIs

8.1 Performance Targets

Metric Target Measurement
Enclosure generation time < 15 minutes Time from input submission to STEP output
FEA analysis time < 10 minutes Mesh generation + CalculiX solve + result parsing
CFD analysis time < 20 minutes Mesh generation + OpenFOAM solve + post-processing
Tolerance analysis time < 30 seconds Analytical calculation
Material selection time < 10 seconds Database query + LLM rationale
First-pass success rate > 70% Enclosures that pass FEA without revision
FEA accuracy < 10% deviation Compared to commercial FEA on reference models

8.2 Quality Targets

Metric Target Measurement
STEP file validity 100% Imports successfully in any CAD tool
FEA convergence rate > 95% CalculiX solves without divergence
Schema validation pass rate 100% All outputs pass Pydantic validation
Digital thread completeness > 90% All artifacts linked in Neo4j
Test coverage > 80% Unit + integration tests

8.3 Business Impact Targets

Metric Target Measurement
Enclosure design cycle reduction > 90% Weeks → hours
Simulation cost reduction 100% $0 license cost vs. $30K+
Prototype iteration speed 2x improvement Fewer physical prototyping rounds

9. Risk Matrix

# Risk Likelihood Impact Mitigation
R1 LLM-generated FreeCAD scripts fail — syntax errors, invalid geometry, boolean operation failures High High Script template library, AST validation before execution, self-healing retry loop with error feedback to LLM, curated test cases
R2 FreeCAD headless instability — crashes, memory leaks, version incompatibilities Medium High Docker container isolation, version pinning (0.21.2), process watchdog with timeout, fallback to script templates
R3 CalculiX convergence failures — ill-conditioned mesh, inappropriate element types Medium Medium Mesh quality pre-checks, auto mesh refinement, convergence monitoring, timeout + fallback to coarser mesh
R4 OpenFOAM setup complexity — dictionary file errors, mesh quality issues Medium Medium Template case directories, validated dictionary generators, mesh quality checks via checkMesh
R5 LLM hallucinated material properties — incorrect values for strength, thermal conductivity Low High Verified material database (not LLM-generated), cross-reference with MatWeb/MATWEB data, unit sanity checks
R6 STEP export compatibility — FreeCAD STEP files not importing in downstream tools Low Medium STEP AP214 compliance testing, reference model validation against SOLIDWORKS/Fusion 360 import
R7 Performance degradation — FEA/CFD taking too long for large models Medium Medium Element count limits, adaptive mesh coarsening, timeout with partial results, background execution
R8 Security: arbitrary code execution — LLM generates malicious FreeCAD Python scripts Low Critical Script sandbox (no os, subprocess, socket imports), AST whitelist, filesystem isolation via Docker

Risk Mitigation Architecture

flowchart TD
    A[LLM generates script] --> B{AST Validation}
    B -->|Blocked imports| C[Reject + retry]
    B -->|Clean| D{Sandbox Execution}
    D -->|Timeout| E[Kill process + retry with simpler geometry]
    D -->|Crash| F[Log error + retry with template fallback]
    D -->|Success| G{Output Validation}
    G -->|Invalid geometry| H[Feed error back to LLM]
    G -->|Valid| I[Accept output]

    style B fill:#f39c12,color:#000
    style D fill:#e74c3c,color:#fff
    style G fill:#27ae60,color:#fff

10. Material Library (Reference)

The ME Agent includes a verified material database. Properties are sourced from manufacturer datasheets, not LLM-generated.

10.1 Polymers

Material Density (g/cm³) Yield (MPa) Tensile (MPa) E (GPa) Tmax (°C) k (W/m·K) CTE (µm/m·K) Use Case
ABS 1.04 42 44 2.3 80 0.17 90 General enclosures, prototyping
ABS+PC 1.10 50 55 2.5 100 0.20 75 Impact-resistant enclosures
PLA 1.24 60 65 3.5 55 0.13 68 Low-cost prototyping only
PETG 1.27 50 53 2.2 70 0.15 60 Chemical-resistant enclosures
Nylon PA6 1.14 70 85 2.8 120 0.25 80 High-temp, snap fits
Nylon PA6-GF30 1.36 120 160 9.5 140 0.35 30 Structural, high-temp
PC 1.20 62 66 2.4 130 0.20 65 Transparent enclosures, high impact
PP 0.91 35 38 1.5 100 0.12 100 Chemical resistance, living hinges
POM (Acetal) 1.41 65 70 3.0 90 0.31 110 Gears, precision mechanisms
PEEK 1.32 100 105 4.0 250 0.25 47 Extreme environments

10.2 Metals

Material Density (g/cm³) Yield (MPa) Tensile (MPa) E (GPa) Tmax (°C) k (W/m·K) CTE (µm/m·K) Use Case
Al 6061-T6 2.70 276 310 69 150 167 23.6 CNC enclosures, heatsinks
Al 5052-H32 2.68 193 228 70 150 138 23.8 Sheet metal enclosures
Al 7075-T6 2.81 503 572 72 150 130 23.4 High-strength structural
SS 304 8.00 215 505 193 800 16.2 17.3 Corrosion-resistant enclosures
SS 316L 8.00 170 485 193 800 16.3 15.9 Medical, marine
Carbon Steel 1018 7.87 370 440 205 400 51.9 11.7 General structural
Copper C110 8.94 69 221 117 200 391 17.0 Heatsinks, bus bars
Brass C360 8.50 310 385 97 200 115 20.5 Connectors, fittings
Ti-6Al-4V 4.43 880 950 114 350 6.7 8.6 Aerospace, medical implants

10.3 Composites & Specialty

Material Density (g/cm³) Yield (MPa) Tensile (MPa) E (GPa) Tmax (°C) k (W/m·K) Use Case
FR-4 (PCB substrate) 1.85 310 22 130 0.29 Reference for thermal analysis
CFRP (quasi-iso) 1.55 600 70 150 5.0 Lightweight structural
Silicone (60A) 1.15 8 0.005 200 0.20 Gaskets, seals
Thermal pad 2.0-3.0 200 1.0-6.0 Thermal interface materials

11. Reference: FreeCAD Enclosure Script

Example LLM-generated FreeCAD Python script for enclosure generation:

"""
MetaForge ME Agent — Enclosure Generation Script
Generated for: IoT Sensor Enclosure
PCB: 60mm x 40mm, 4x M3 mounting holes
IP Rating: IP54
Material: ABS
"""
import FreeCAD
import Part
import Mesh

# ─── Parameters (from MechanicalInput) ───
PCB_WIDTH = 60.0    # mm
PCB_DEPTH = 40.0    # mm
PCB_HEIGHT = 1.6    # mm
CLEARANCE = 2.0     # mm around PCB
WALL = 2.0          # mm wall thickness
COMP_HEIGHT_TOP = 12.0   # mm max component height above PCB
COMP_HEIGHT_BOT = 2.0    # mm max component height below PCB

# Derived dimensions
INNER_W = PCB_WIDTH + 2 * CLEARANCE
INNER_D = PCB_DEPTH + 2 * CLEARANCE
INNER_H_BOT = COMP_HEIGHT_BOT + PCB_HEIGHT + 2.0  # bottom half
INNER_H_TOP = COMP_HEIGHT_TOP + 2.0                # top half
OUTER_W = INNER_W + 2 * WALL
OUTER_D = INNER_D + 2 * WALL

# Mounting hole positions (relative to PCB origin)
MOUNT_HOLES = [
    (3.5, 3.5),
    (3.5, 36.5),
    (56.5, 3.5),
    (56.5, 36.5),
]
MOUNT_HOLE_DIA = 3.2  # M3 clearance

# ─── Create Document ───
doc = FreeCAD.newDocument("Enclosure")

# ─── Bottom Shell ───
outer_bot = Part.makeBox(OUTER_W, OUTER_D, INNER_H_BOT + WALL)
inner_bot = Part.makeBox(INNER_W, INNER_D, INNER_H_BOT)
inner_bot.translate(FreeCAD.Vector(WALL, WALL, WALL))
bottom_shell = outer_bot.cut(inner_bot)

# Add PCB standoffs
for (hx, hy) in MOUNT_HOLES:
    standoff_x = hx + CLEARANCE + WALL
    standoff_y = hy + CLEARANCE + WALL
    standoff = Part.makeCylinder(3.0, COMP_HEIGHT_BOT + WALL,
                                  FreeCAD.Vector(standoff_x, standoff_y, WALL))
    hole = Part.makeCylinder(MOUNT_HOLE_DIA / 2, COMP_HEIGHT_BOT + WALL + 1,
                              FreeCAD.Vector(standoff_x, standoff_y, WALL - 0.5))
    standoff = standoff.cut(hole)
    bottom_shell = bottom_shell.fuse(standoff)

# ─── Top Shell ───
outer_top = Part.makeBox(OUTER_W, OUTER_D, INNER_H_TOP + WALL)
inner_top = Part.makeBox(INNER_W, INNER_D, INNER_H_TOP)
inner_top.translate(FreeCAD.Vector(WALL, WALL, 0))
top_shell = outer_top.cut(inner_top)

# ─── Connector Cutout (USB-C on back face) ───
usbc_cutout = Part.makeBox(10.0, WALL + 2, 4.0)
usbc_cutout.translate(FreeCAD.Vector(
    OUTER_W / 2 - 5.0,  # centered
    OUTER_D - WALL - 1,  # back face
    WALL + 2.0            # height from bottom
))
bottom_shell = bottom_shell.cut(usbc_cutout)

# ─── Ventilation Slots (IP54: dust-protected) ───
for i in range(4):
    slot = Part.makeBox(15.0, 1.0, WALL + 2)
    slot.translate(FreeCAD.Vector(
        OUTER_W / 2 - 7.5,
        WALL - 1,
        INNER_H_TOP - 3.0 - i * 3.0
    ))
    top_shell = top_shell.cut(slot)

# ─── Add Parts to Document ───
bottom_part = doc.addObject("Part::Feature", "BottomShell")
bottom_part.Shape = bottom_shell

top_part = doc.addObject("Part::Feature", "TopShell")
top_part.Shape = top_shell

doc.recompute()

# ─── Export ───
doc.saveAs("/output/enclosure.FCStd")
Part.export([bottom_part, top_part], "/output/enclosure.step")
Mesh.export([bottom_part, top_part], "/output/enclosure.stl")

print(f"Volume (bottom): {bottom_shell.Volume:.1f} mm³")
print(f"Volume (top): {top_shell.Volume:.1f} mm³")
print(f"Bounding box: {OUTER_W} x {OUTER_D} x {INNER_H_BOT + INNER_H_TOP + 2*WALL} mm")
print("Export complete.")

12. Glossary

Term Definition
CAD Computer-Aided Design — 3D modeling software
CAE Computer-Aided Engineering — simulation and analysis
CFD Computational Fluid Dynamics — airflow/thermal fluid simulation
Cpk Process Capability Index — statistical measure of manufacturing precision
CTE Coefficient of Thermal Expansion
DFA Design for Assembly — methodology for reducing assembly complexity
DFM Design for Manufacturability
FEA Finite Element Analysis — structural/thermal simulation
FEM Finite Element Method — mathematical approach underlying FEA
GD&T Geometric Dimensioning and Tolerancing
IP rating Ingress Protection — dust/water resistance standard (IEC 60529)
RSS Root Sum of Squares — statistical tolerance analysis method
STEP Standard for the Exchange of Product model data (ISO 10303)
STL Standard Tessellation Language — mesh format for 3D printing
Von Mises Equivalent stress criterion for ductile material failure prediction

Document Description
Agent System Base agent interface, lifecycle, testing patterns
Tool Adapters Adapter interface, existing adapters (KiCad, NGSpice, supplier APIs)
Architecture System architecture, data flows, agent-tool access matrix
Framework Mapping 25-discipline framework, ME agent positioning
AI Memory & Knowledge Knowledge Layer: pgvector, RAG retrieval, material/failure mode knowledge
System Observability Agent metrics, LLM token/cost tracking, distributed tracing

Document Version: v0.1-draft Author: MetaForge Architecture Team Last Updated: 2026-02-15

← Agent System Tool Adapters →