Files
Atomizer/optimization_engine/plugins/post_solve/error_tracker.py
Anto01 773f8ff8af feat: Implement ACE Context Engineering framework (SYS_17)
Complete implementation of Agentic Context Engineering (ACE) framework:

Core modules (optimization_engine/context/):
- playbook.py: AtomizerPlaybook with helpful/harmful scoring
- reflector.py: AtomizerReflector for insight extraction
- session_state.py: Context isolation (exposed/isolated state)
- feedback_loop.py: Automated learning from trial results
- compaction.py: Long-session context management
- cache_monitor.py: KV-cache optimization tracking
- runner_integration.py: OptimizationRunner integration

Dashboard integration:
- context.py: 12 REST API endpoints for playbook management

Tests:
- test_context_engineering.py: 44 unit tests
- test_context_integration.py: 16 integration tests

Documentation:
- CONTEXT_ENGINEERING_REPORT.md: Comprehensive implementation report
- CONTEXT_ENGINEERING_API.md: Complete API reference
- SYS_17_CONTEXT_ENGINEERING.md: System protocol
- Updated cheatsheet with SYS_17 quick reference
- Enhanced bootstrap (00_BOOTSTRAP_V2.md)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 20:21:20 -05:00

269 lines
8.0 KiB
Python

"""
Error Tracker Hook - Context Engineering Integration
Preserves solver errors and failures in context for learning.
Based on Manus insight: "leave the wrong turns in the context"
This hook:
1. Captures solver errors and failures
2. Classifies error types for playbook categorization
3. Extracts relevant F06 content for analysis
4. Records errors to session state and LAC
Hook Point: post_solve
Priority: 100 (run early to capture before cleanup)
"""
from pathlib import Path
from datetime import datetime
from typing import Dict, Any, Optional
import json
import re
def classify_error(error_msg: str) -> str:
"""
Classify error type for playbook categorization.
Args:
error_msg: Error message text
Returns:
Error classification string
"""
error_lower = error_msg.lower()
# Check patterns in priority order
if any(x in error_lower for x in ['convergence', 'did not converge', 'diverge']):
return "convergence_failure"
elif any(x in error_lower for x in ['mesh', 'element', 'distorted', 'jacobian']):
return "mesh_error"
elif any(x in error_lower for x in ['singular', 'matrix', 'pivot', 'ill-conditioned']):
return "singularity"
elif any(x in error_lower for x in ['memory', 'allocation', 'out of memory']):
return "memory_error"
elif any(x in error_lower for x in ['license', 'checkout']):
return "license_error"
elif any(x in error_lower for x in ['boundary', 'constraint', 'spc', 'rigid body']):
return "boundary_condition_error"
elif any(x in error_lower for x in ['timeout', 'time limit']):
return "timeout_error"
elif any(x in error_lower for x in ['file', 'not found', 'missing']):
return "file_error"
else:
return "unknown_error"
def extract_f06_error(f06_path: Optional[str], max_chars: int = 500) -> str:
"""
Extract error section from F06 file.
Args:
f06_path: Path to F06 file
max_chars: Maximum characters to extract
Returns:
Error section content or empty string
"""
if not f06_path:
return ""
path = Path(f06_path)
if not path.exists():
return ""
try:
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# Look for error indicators
error_markers = [
"*** USER FATAL",
"*** SYSTEM FATAL",
"*** USER WARNING",
"*** SYSTEM WARNING",
"FATAL ERROR",
"ERROR MESSAGE"
]
for marker in error_markers:
if marker in content:
idx = content.index(marker)
# Extract surrounding context
start = max(0, idx - 100)
end = min(len(content), idx + max_chars)
return content[start:end].strip()
# If no explicit error marker, check for convergence messages
convergence_patterns = [
r"CONVERGENCE NOT ACHIEVED",
r"SOLUTION DID NOT CONVERGE",
r"DIVERGENCE DETECTED"
]
for pattern in convergence_patterns:
match = re.search(pattern, content, re.IGNORECASE)
if match:
idx = match.start()
start = max(0, idx - 50)
end = min(len(content), idx + max_chars)
return content[start:end].strip()
return ""
except Exception as e:
return f"Error reading F06: {str(e)}"
def find_f06_file(working_dir: str, sim_file: str = "") -> Optional[Path]:
"""
Find the F06 file in the working directory.
Args:
working_dir: Working directory path
sim_file: Simulation file name (for naming pattern)
Returns:
Path to F06 file or None
"""
work_path = Path(working_dir)
# Try common patterns
patterns = [
"*.f06",
"*-solution*.f06",
"*_sim*.f06"
]
for pattern in patterns:
matches = list(work_path.glob(pattern))
if matches:
# Return most recently modified
return max(matches, key=lambda p: p.stat().st_mtime)
return None
def track_error(context: Dict[str, Any]) -> Dict[str, Any]:
"""
Hook that preserves errors for context learning.
Called at post_solve after solver completes.
Captures error information regardless of success/failure
to enable learning from both outcomes.
Args:
context: Hook context with trial information
Returns:
Dictionary with error tracking results
"""
trial_number = context.get('trial_number', -1)
working_dir = context.get('working_dir', '.')
output_dir = context.get('output_dir', working_dir)
solver_returncode = context.get('solver_returncode', 0)
# Determine if this is an error case
# (solver returncode non-zero, or explicit error flag)
is_error = (
solver_returncode != 0 or
context.get('error', False) or
context.get('solver_failed', False)
)
if not is_error:
# No error to track, but still record success for learning
return {"error_tracked": False, "trial_success": True}
# Find and extract F06 error info
f06_path = context.get('f06_path')
if not f06_path:
f06_file = find_f06_file(working_dir, context.get('sim_file', ''))
if f06_file:
f06_path = str(f06_file)
f06_snippet = extract_f06_error(f06_path)
# Get error message from context or F06
error_message = context.get('error_message', '')
if not error_message and f06_snippet:
# Extract first line of F06 error as message
lines = f06_snippet.strip().split('\n')
error_message = lines[0][:200] if lines else "Unknown solver error"
# Classify error
error_type = classify_error(error_message or f06_snippet)
# Build error record
error_info = {
"trial": trial_number,
"timestamp": datetime.now().isoformat(),
"solver_returncode": solver_returncode,
"error_type": error_type,
"error_message": error_message,
"f06_snippet": f06_snippet[:1000] if f06_snippet else "",
"design_variables": context.get('design_variables', {}),
"working_dir": working_dir
}
# Save to error log (append mode - accumulate errors)
error_log_path = Path(output_dir) / "error_history.jsonl"
try:
error_log_path.parent.mkdir(parents=True, exist_ok=True)
with open(error_log_path, 'a', encoding='utf-8') as f:
f.write(json.dumps(error_info) + "\n")
except Exception as e:
print(f"Warning: Could not write error log: {e}")
# Try to update session state if context engineering is active
try:
from optimization_engine.context.session_state import get_session
session = get_session()
session.add_error(
f"Trial {trial_number}: {error_type} - {error_message[:100]}",
error_type=error_type
)
except ImportError:
pass # Context module not available
# Try to record to LAC if available
try:
from knowledge_base.lac import get_lac
lac = get_lac()
lac.record_insight(
category="failure",
context=f"Trial {trial_number} solver error",
insight=f"{error_type}: {error_message[:200]}",
confidence=0.7,
tags=["solver", error_type, "automatic"]
)
except ImportError:
pass # LAC not available
return {
"error_tracked": True,
"error_type": error_type,
"error_message": error_message[:200],
"f06_extracted": bool(f06_snippet)
}
# Hook registration metadata
HOOK_CONFIG = {
"name": "error_tracker",
"hook_point": "post_solve",
"priority": 100, # Run early to capture before cleanup
"enabled": True,
"description": "Preserves solver errors for context learning"
}
# Make the function discoverable by hook manager
def get_hook():
"""Return the hook function for registration."""
return track_error
# For direct plugin discovery
__all__ = ['track_error', 'HOOK_CONFIG', 'get_hook']