| """ |
| Base Learning Engine |
| |
| Abstract interface for self-improving agent modules. Both MementoEngine |
| (skill generation) and AlphaEvolverEngine (skill optimization) implement |
| this interface, enabling a unified lifecycle: |
| |
| analyze_episode → propose_code_change → validate_change |
| """ |
|
|
| import logging |
| from abc import ABC, abstractmethod |
| from typing import Any, Protocol, runtime_checkable |
|
|
| from sqlalchemy.orm import Session |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| @runtime_checkable |
| class SandboxProtocol(Protocol): |
| """ |
| Abstract sandbox interface for executing untrusted code. |
| |
| Upstream uses ContainerSandbox (Docker). |
| SaaS implementations can inject SandboxExecutionService (Fly.io). |
| """ |
|
|
| async def execute_raw_python( |
| self, |
| tenant_id: str, |
| code: str, |
| input_params: dict[str, Any], |
| timeout: int = 60, |
| safety_level: str = "MEDIUM_RISK", |
| **kwargs, |
| ) -> dict[str, Any]: |
| """ |
| Execute raw Python code in an isolated sandbox. |
| |
| Returns: |
| { |
| "status": "success" | "failed", |
| "output": str, |
| "execution_seconds": float, |
| "execution_id": str, |
| } |
| """ |
| ... |
|
|
|
|
| class BaseLearningEngine(ABC): |
| """ |
| Unified interface for self-improving agent modules. |
| |
| Subclasses must implement three core lifecycle methods: |
| 1. analyze_episode — read and interpret execution data |
| 2. propose_code_change — generate a code modification |
| 3. validate_change — execute in sandbox and assess fitness |
| """ |
|
|
| def __init__( |
| self, |
| db: Session, |
| llm_service: Any | None = None, |
| sandbox: SandboxProtocol | None = None, |
| ): |
| self.db = db |
| self.llm = llm_service |
| self.sandbox = sandbox |
|
|
| @abstractmethod |
| async def analyze_episode(self, episode_id: str, **kwargs) -> dict[str, Any]: |
| """ |
| Read and interpret an episode's execution data. |
| |
| Returns a structured analysis dict containing: |
| - task_description, error_trace, tool_calls (for failures) |
| - latency, token_usage, edge_case_signals (for successes) |
| """ |
|
|
| @abstractmethod |
| async def propose_code_change( |
| self, context: dict[str, Any], **kwargs |
| ) -> str: |
| """ |
| Generate a code modification proposal via LLM. |
| |
| Args: |
| context: Analysis output from analyze_episode() |
| |
| Returns: |
| Generated Python code string |
| """ |
|
|
| @abstractmethod |
| async def validate_change( |
| self, code: str, test_inputs: list[dict[str, Any]], tenant_id: str, **kwargs |
| ) -> dict[str, Any]: |
| """ |
| Execute proposed code in sandbox and assess fitness. |
| |
| Returns: |
| { |
| "passed": bool, |
| "proxy_signals": dict, |
| "execution_result": dict, |
| } |
| """ |
|
|
| def _get_llm_service(self): |
| """Get LLM service with graceful fallback.""" |
| if self.llm is not None: |
| return self.llm |
|
|
| try: |
| from core.llm_service import get_llm_service |
|
|
| self.llm = get_llm_service() |
| return self.llm |
| except Exception as e: |
| logger.warning( |
| f"LLM service unavailable — Auto-Dev features requiring LLM will be skipped: {e}" |
| ) |
| return None |
|
|
| def _get_sandbox(self): |
| """Get sandbox with graceful fallback to ContainerSandbox.""" |
| if self.sandbox is not None: |
| return self.sandbox |
|
|
| try: |
| from core.auto_dev.container_sandbox import ContainerSandbox |
|
|
| self.sandbox = ContainerSandbox() |
| return self.sandbox |
| except Exception as e: |
| logger.warning(f"Sandbox unavailable — validation will be skipped: {e}") |
| return None |
|
|
| def _strip_markdown_fences(self, code: str) -> str: |
| """Strip markdown code fences from LLM output.""" |
| code = code.strip() |
| if code.startswith("```python"): |
| code = code[len("```python") :] |
| elif code.startswith("```"): |
| code = code[3:] |
| if code.endswith("```"): |
| code = code[:-3] |
| return code.strip() |
|
|