diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48dac99..2644c7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: MY-AI CI on: push: - branches: [main, v6-cognitive-core, v7-recursive-agent-graph, fix/v10-ci-current-engine] + branches: [main, v6-cognitive-core, v7-recursive-agent-graph, fix/v10-ci-current-engine, v10-self-healing-runtime] pull_request: branches: [main] @@ -14,6 +14,7 @@ env: MYAI_KNOWLEDGE_DB_PATH: /tmp/my-ai-knowledge.sqlite3 MYAI_AGENT_MODE: auto MYAI_CODE_INDEX_ENABLED: true + MYAI_SELF_HEALING_ENABLED: true jobs: test: @@ -53,7 +54,7 @@ jobs: run: mypy src smoke: - name: Smoke / current package and architecture + name: Smoke / V10 current package and architecture runs-on: ubuntu-latest needs: test steps: @@ -89,10 +90,11 @@ jobs: ) ai = AIEngine() + assert ai.version == "v10.0" added = ai.add_document(Document(source="ci.txt", text="Paris is the capital of France.")) assert added == 1 result = ai.generate(ChatRequest(message="What is the capital of France?")) - assert result.version == "v9.1" + assert result.version == "v10.0" assert result.text decision = AdaptiveModelRouter().choose( diff --git a/README.md b/README.md index 6e6c8a7..509b9b4 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,43 @@ # MY-AI -## V9 — Cognitive Mesh + Self-Evolving Repository Intelligence +## V10 — Cognitive Mesh + Self-Healing Runtime -MY-AI V9 extends V8's causal repository twin into a unified cognitive mesh for repository reasoning, targeted debugging, runtime evidence and evidence-gated self-evolution. +MY-AI V10 extends V9.1 with a bounded self-healing runtime on top of the cognitive mesh. The system keeps the repository/program/causal graphs, persistent runtime traces, recursive multi-model workers, capability ledger, and evidence-gated evolution while adding failure signatures, bounded reproduction, repair episodes, controlled fault injection, and stable-code health metadata. -### V9 architecture +### V10 architecture ```text request / failure -> cognitive routing -> unified program graph - -> structure - -> calls - -> data-flow references - -> control-flow regions + -> structure / calls / data-flow / control-flow -> causal repository twin - -> dependency impact - -> source slices + -> dependency impact / source slices -> runtime trace graph - -> exception/state events - -> causal links - -> recursive multi-model workers - -> independent judge - -> targeted validation - -> evolution memory - -> strategy scores - -> reusable lessons - -> promotion gating + -> state / exception / causal links + -> failure signature memory + -> bounded reproduction + -> recursive repair workers + independent judge + -> sandbox / validation boundary + -> fault-injection self-tests + -> stable-code health map + -> verified strategy / capability ledger ``` -### Unified Program Graph +### Self-healing principles -`ProgramGraph` adds a repository-level intermediate representation over the persistent code index. Instead of treating files as isolated text blobs, V9 keeps compact nodes/edges for declarations, calls, data-flow references and control-flow regions. `program_slice()` returns only the local graph neighborhood and matching source ranges needed for a task. +1. Detect before repairing. +2. Reproduce before trusting a hypothesis whenever possible. +3. Use the smallest causally relevant context; preserve verified code. +4. Keep retries and repair attempts bounded. +5. Never auto-promote code from the repair runtime; promotion remains validation-gated. +6. Convert verified repair experience into reusable memory and regression evidence. -This follows the current repository-agent direction represented by RPG and RPG-Encoder, which use persistent repository graphs to unify structure, semantics and dependencies and evolve the representation incrementally rather than repeatedly re-reading an entire codebase. RPG-Encoder reports large maintenance-overhead reductions and strong repository localization results on SWE-bench evaluations. +### Runtime controls -### Runtime Trace Graph - -`RuntimeTraceGraph` stores compact exception/state events and causal links in `data/runtime_trace.jsonl`. The graph survives restarts, so future diagnosis can reuse runtime evidence instead of rediscovering the same sequence of events. - -### Self-evolution loop - -`EvolutionMemory` records task strategy, correctness, latency, token use and lessons. `EvolutionBenchmark` ranks strategies and only promotes a candidate when it is successful and materially better than the baseline. This is deliberately evidence-gated: memory can guide future strategy selection, but it cannot replace execution evidence. - -Recent 2026 research on self-evolving coding agents emphasizes executable feedback, repository context and coding trajectories as the core evidence sources for safe improvement. EvoCodeBench explicitly evaluates correctness together with efficiency and improvement over repeated attempts. - -### V9 public engine - -```python -from myai import AIEngine - -ai = AIEngine() # V9 by default -``` - -Legacy V7.1 and V8 engines remain importable for compatibility. - -### Targeted repair context - -```python -diagnosis = ai.diagnose_failure(traceback_text) -repair_context = ai.repair_context_v9(traceback_text) -program_slice = ai.program_slice("refresh_token") -``` - -The repair specialist receives the failure hypothesis, graph slice, relevant source ranges, nearby runtime events and the best historically validated strategy. Unrelated files are not required for the repair context. - -### Local setup - -```bash -./scripts/setup.sh -source .venv/bin/activate -cp .env.example .env -pytest -``` - -Runtime dependencies are listed in `requirements.txt`; development/CI dependencies are in `requirements-dev.txt`. - -### Persistent state - -- `data/knowledge.sqlite3` — semantic knowledge -- `data/code_index.json` — freshness-aware code index -- `data/repair_memory.jsonl` — validated repair experience -- `data/runtime_trace.jsonl` — runtime events and causal links -- `data/evolution_memory.jsonl` — strategy/evolution records - -### Research grounding - -V9 deliberately combines repository graphs, fine-grained program slices, executable runtime evidence and self-evolving memory rather than relying on any one technique. Current research shows memory remains task-dependent, so V9 keeps multiple evidence channels and validates promotion through actual outcomes instead of assuming that stored memory is universally reliable. +- `MYAI_SELF_HEALING_ENABLED=true` +- `MYAI_SELF_HEALING_MAX_REPAIR_ATTEMPTS=2` +- `MYAI_SELF_HEALING_REPRODUCTION_TIMEOUT_SECONDS=30` +- `MYAI_FAILURE_SIGNATURE_PATH=data/failure_signatures.jsonl` +- `MYAI_REPAIR_EPISODE_PATH=data/repair_episodes.jsonl` +- `MYAI_CODE_HEALTH_PATH=data/code_health.json` diff --git a/pyproject.toml b/pyproject.toml index e369656..c1cc2e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta" [project] name = "my-ai" -version = "0.9.0" -description = "MY-AI V9: cognitive mesh, unified program graph, runtime traces, self-evolving strategy memory" +version = "0.10.0" +description = "MY-AI V10: cognitive mesh, causal repository intelligence, bounded self-healing runtime, fault testing, and evidence-driven evolution" readme = "README.md" requires-python = ">=3.11,<3.14" dependencies = [ diff --git a/src/myai/__init__.py b/src/myai/__init__.py index a7ca2a5..fd9e277 100644 --- a/src/myai/__init__.py +++ b/src/myai/__init__.py @@ -1,4 +1,4 @@ -"""MY-AI V9.1 public package.""" +"""MY-AI V10 public package with self-healing runtime controls.""" from .agent_graph import ExecutionBudget, RecursiveAgentGraph, TaskNode, WorkArtifact from .agent_runtime import AgentRuntimeResult, MultiModelAgentRuntime @@ -11,6 +11,7 @@ from .context_contract import CognitiveContext, build_cognitive_context from .engine import AIEngine as LegacyAIEngine from .evolution import EvolutionBenchmark, EvolutionMemory, EvolutionRecord, StrategyScore +from .fault_lab import FaultCase, FaultInjectionLab, FaultResult from .graph_v9 import ProgramEdge, ProgramGraph, ProgramNode, ProgramSlice from .knowledge import ( Document, @@ -35,13 +36,17 @@ RepairMemory, RepairMemoryRecord, ) +from .self_healing_runtime import FailureSignature, FailureSignatureStore, RepairEpisode, SelfHealingRuntime +from .stability import CodeHealth, CodeHealthStore from .v8_engine import V8AIEngine from .v9_engine import V9AIEngine +from .v10_engine import V10AIEngine -AIEngine = V9AIEngine +AIEngine = V10AIEngine __all__ = [ "AIEngine", + "V10AIEngine", "V9AIEngine", "V8AIEngine", "LegacyAIEngine", @@ -57,6 +62,8 @@ "ChatMessage", "ChatRequest", "ChatResponse", + "CodeHealth", + "CodeHealthStore", "CognitiveComputeController", "CognitiveComputePolicy", "CognitiveContext", @@ -65,11 +72,22 @@ "CodeFile", "CodeIntelligenceIndex", "CodeSymbol", + "CausalDiagnosis", + "CausalErrorEngine", + "CausalRepositoryTwin", "Document", "ExecutionBudget", "EvolutionBenchmark", "EvolutionMemory", "EvolutionRecord", + "FailureEvent", + "FailureFrame", + "FailureSignature", + "FailureSignatureStore", + "FaultCase", + "FaultInjectionLab", + "FaultResult", + "ImpactSlice", "InMemoryKnowledgeStore", "MemoryItem", "MemoryKind", @@ -83,27 +101,23 @@ "ProgramNode", "ProgramSlice", "RecursiveAgentGraph", + "RepairEpisode", + "RepairMemory", + "RepairMemoryRecord", "RetrievedChunk", "RoutingDecision", "RoutingRequest", + "RuntimeTraceGraph", "SQLiteVectorStore", "StrategyScore", + "SelfHealingRuntime", "TaskNode", "TieredModelPool", "TraceCausalLink", "TraceEvent", - "RuntimeTraceGraph", - "WorkArtifact", - "CausalRepositoryTwin", - "ImpactSlice", "TwinEdge", "TwinNode", - "CausalDiagnosis", - "CausalErrorEngine", - "FailureEvent", - "FailureFrame", - "RepairMemory", - "RepairMemoryRecord", + "WorkArtifact", "benchmark_cases", "build_cognitive_context", "build_model_report", diff --git a/src/myai/capability_runner.py b/src/myai/capability_runner.py index 019ef24..35767c3 100644 --- a/src/myai/capability_runner.py +++ b/src/myai/capability_runner.py @@ -10,10 +10,13 @@ from .cognitive_state import Belief, CognitiveState, MemoryItem, MemoryKind from .context_contract import build_cognitive_context from .evolution import EvolutionBenchmark, EvolutionMemory, EvolutionRecord +from .fault_lab import FaultInjectionLab from .memory_lifecycle import MemoryLifecycleManager from .model_router import AdaptiveModelRouter, RoutingRequest from .repository_twin import CausalRepositoryTwin from .runtime_trace import RuntimeTraceGraph, TraceEvent +from .self_healing_runtime import FailureSignatureStore, SelfHealingRuntime +from .stability import CodeHealth, CodeHealthStore @dataclass(frozen=True) @@ -92,11 +95,27 @@ def judge(node: TaskNode, artifact: WorkArtifact, children): trace.link("benchmark-cause", "benchmark-error", "caused") neighborhood = trace.neighborhood("benchmark-error") affected = twin.affected_files("src/myai/engine.py") - record( - "causal-trace-diagnosis", - bool(neighborhood) and any(event.kind == "exception" for event in neighborhood) and bool(affected), - f"trace_events={len(neighborhood)};affected_files={len(affected)}", + record("causal-trace-diagnosis", bool(neighborhood) and any(event.kind == "exception" for event in neighborhood) and bool(affected), f"trace_events={len(neighborhood)};affected_files={len(affected)}") + + signatures = FailureSignatureStore("/tmp/myai-v10-failure-signatures.jsonl") + healing = SelfHealingRuntime(episode_path="/tmp/myai-v10-repair-episodes.jsonl", signature_store=signatures) + signature = healing.signature("TypeError", "database timeout 123", "AIEngine.generate") + episode = healing.verified_repair( + signature=signature, + reproduce=lambda: (True, "synthetic reproduction"), + validate=lambda: True, + lesson="verified bounded recovery", ) + record("self-healing-runtime", episode.status == "verified" and bool(signatures.similar(signature)), f"status={episode.status};attempts={episode.attempts}") + + state_map = {"healthy": True} + fault = FaultInjectionLab() + fault_result = fault.run(fault.simple_toggle(state_map, "healthy"), detector=lambda: not state_map["healthy"]) + record("fault-injection-recovery", fault_result.detected and fault_result.recovered and fault_result.verified, f"detected={fault_result.detected};recovered={fault_result.recovered};verified={fault_result.verified}") + + health_store = CodeHealthStore("/tmp/myai-v10-code-health.json") + health_store.upsert(CodeHealth("stable_symbol", 0.99, 0.99, 0.0, 0.01, 0.05, "2026-08-25T00:00:00+00:00")) + record("stable-code-inspection", health_store.inspection_mode("stable_symbol") == "reuse", f"mode={health_store.inspection_mode('stable_symbol')}") state.observe("observation-a") for index_value in range(20): diff --git a/src/myai/config.py b/src/myai/config.py index 011a21d..56a3025 100644 --- a/src/myai/config.py +++ b/src/myai/config.py @@ -8,20 +8,20 @@ class Settings(BaseSettings): environment: str = "development" log_level: str = "INFO" model_provider: str = "local" - model_name: str = "placeholder-v9" + model_name: str = "placeholder-v10" model_base_url: str = "http://localhost:11434" model_api_key: str = "" model_timeout_seconds: float = 60.0 fast_model_provider: str = "local" - fast_model_name: str = "placeholder-v9-fast" + fast_model_name: str = "placeholder-v10-fast" fast_model_base_url: str = "http://localhost:11434" fast_model_api_key: str = "" balanced_model_provider: str = "local" - balanced_model_name: str = "placeholder-v9-balanced" + balanced_model_name: str = "placeholder-v10-balanced" balanced_model_base_url: str = "http://localhost:11434" balanced_model_api_key: str = "" frontier_model_provider: str = "local" - frontier_model_name: str = "placeholder-v9-frontier" + frontier_model_name: str = "placeholder-v10-frontier" frontier_model_base_url: str = "http://localhost:11434" frontier_model_api_key: str = "" memory_max_messages: int = 20 @@ -49,11 +49,17 @@ class Settings(BaseSettings): runtime_trace_path: str = "data/runtime_trace.jsonl" evolution_memory_path: str = "data/evolution_memory.jsonl" capability_ledger_path: str = "data/capability_ledger.json" + failure_signature_path: str = "data/failure_signatures.jsonl" + repair_episode_path: str = "data/repair_episodes.jsonl" + code_health_path: str = "data/code_health.json" evolution_min_promotion_delta: float = 0.05 + self_healing_enabled: bool = True + self_healing_max_repair_attempts: int = 2 + self_healing_reproduction_timeout_seconds: float = 30.0 system_prompt: str = ( - "You are MY-AI V9.1. Operate as an evidence-first cognitive mesh. " - "Use the unified program graph, runtime traces, repository memory, and structured specialist artifacts. " - "Never invent sources, tool usage, verification, or repository facts. " + "You are MY-AI V10.0. Operate as an evidence-first cognitive mesh with a bounded self-healing runtime. " + "Use the unified program graph, runtime traces, repository memory, failure signatures, stable-code health, " + "and structured specialist artifacts. Never invent sources, tool usage, verification, or repository facts. " "Prefer the smallest sufficient context and preserve already-verified work. " "When strategies disagree, surface uncertainty and request stronger evidence rather than averaging guesses." ) diff --git a/src/myai/fault_lab.py b/src/myai/fault_lab.py new file mode 100644 index 0000000..876237e --- /dev/null +++ b/src/myai/fault_lab.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Callable + + +@dataclass(frozen=True) +class FaultCase: + name: str + description: str + inject: Callable[[], None] + recover: Callable[[], None] + verify: Callable[[], bool] + + +@dataclass(frozen=True) +class FaultResult: + name: str + detected: bool + recovered: bool + verified: bool + + +class FaultInjectionLab: + """Controlled, opt-in fault tests for the self-healing subsystem.""" + + def run(self, case: FaultCase, *, detector: Callable[[], bool]) -> FaultResult: + detected = False + recovered = False + try: + case.inject() + detected = bool(detector()) + if detected: + case.recover() + recovered = True + verified = bool(case.verify()) + finally: + # Fault cases own their rollback/cleanup through recover(). + if not recovered: + try: + case.recover() + except Exception: + pass + return FaultResult(case.name, detected, recovered, verified) + + @staticmethod + def simple_toggle(state: dict[str, bool], key: str) -> FaultCase: + def inject() -> None: + state[key] = False + + def recover() -> None: + state[key] = True + + def verify() -> bool: + return state.get(key) is True + + return FaultCase( + name=f"toggle:{key}", + description=f"temporarily disable invariant {key}", + inject=inject, + recover=recover, + verify=verify, + ) diff --git a/src/myai/self_healing_runtime.py b/src/myai/self_healing_runtime.py new file mode 100644 index 0000000..f308ddb --- /dev/null +++ b/src/myai/self_healing_runtime.py @@ -0,0 +1,127 @@ +from __future__ import annotations + +import json +import subprocess +import time +from dataclasses import asdict, dataclass +from hashlib import sha256 +from pathlib import Path +from typing import Callable, Sequence + + +@dataclass(frozen=True) +class FailureSignature: + value: str + error_type: str + normalized_message: str + primary_symbol: str = "" + + +@dataclass(frozen=True) +class RepairEpisode: + episode_id: str + signature: FailureSignature + detected_at: float + reproduced: bool + attempts: int + status: str + lesson: str = "" + + +class FailureSignatureStore: + """Persistent compact index of previously observed failure signatures.""" + + def __init__(self, path: str | Path) -> None: + self.path = Path(path) + self.path.parent.mkdir(parents=True, exist_ok=True) + + def record(self, signature: FailureSignature) -> None: + with self.path.open("a", encoding="utf-8") as handle: + handle.write(json.dumps(asdict(signature), ensure_ascii=False) + "\n") + + def similar(self, signature: FailureSignature, limit: int = 5) -> tuple[FailureSignature, ...]: + if not self.path.exists(): + return () + rows: list[tuple[float, FailureSignature]] = [] + left = set(signature.normalized_message.split()) + for line in self.path.read_text(encoding="utf-8").splitlines(): + if not line.strip(): + continue + try: + raw = json.loads(line) + item = FailureSignature( + value=str(raw["value"]), + error_type=str(raw["error_type"]), + normalized_message=str(raw["normalized_message"]), + primary_symbol=str(raw.get("primary_symbol", "")), + ) + except (ValueError, TypeError, KeyError): + continue + right = set(item.normalized_message.split()) + score = len(left & right) / max(1, len(left | right)) + if item.error_type == signature.error_type: + score += 1.0 + rows.append((score, item)) + rows.sort(key=lambda pair: -pair[0]) + return tuple(item for _, item in rows[:limit]) + + +class SelfHealingRuntime: + """Bounded self-healing supervisor. It proposes/replays repairs but never auto-promotes code.""" + + def __init__(self, *, episode_path: str | Path, signature_store: FailureSignatureStore) -> None: + self.episode_path = Path(episode_path) + self.episode_path.parent.mkdir(parents=True, exist_ok=True) + self.signature_store = signature_store + + def signature(self, error_type: str, message: str, primary_symbol: str = "") -> FailureSignature: + normalized = " ".join("".join(ch if ch.isalnum() or ch.isspace() else " " for ch in message.casefold()).split()) + value = sha256(f"{error_type.casefold()}|{normalized}|{primary_symbol.casefold()}".encode("utf-8")).hexdigest()[:24] + return FailureSignature(value, error_type, normalized, primary_symbol) + + def record_episode(self, episode: RepairEpisode) -> None: + with self.episode_path.open("a", encoding="utf-8") as handle: + handle.write(json.dumps(asdict(episode), ensure_ascii=False) + "\n") + + def reproduce(self, command: Sequence[str], *, timeout_seconds: float = 30.0) -> tuple[bool, str]: + """Replay a failure in a bounded subprocess and capture stdout/stderr.""" + try: + completed = subprocess.run( + list(command), + capture_output=True, + text=True, + timeout=max(0.1, timeout_seconds), + check=False, + ) + except (OSError, subprocess.SubprocessError) as exc: + return False, f"reproduction-error:{type(exc).__name__}:{exc}" + output = (completed.stdout + "\n" + completed.stderr).strip() + return completed.returncode != 0, output + + def verified_repair( + self, + *, + signature: FailureSignature, + reproduce: Callable[[], tuple[bool, str]], + validate: Callable[[], bool], + lesson: str = "", + ) -> RepairEpisode: + """Run bounded reproduction + validation. Promotion is intentionally external.""" + self.signature_store.record(signature) + reproduced, _ = reproduce() + status = "reproduced" if reproduced else "not-reproduced" + attempts = 0 + if reproduced: + attempts = 1 + status = "verified" if validate() else "validation-failed" + episode = RepairEpisode( + episode_id=f"{signature.value}-{int(time.time() * 1000)}", + signature=signature, + detected_at=time.time(), + reproduced=reproduced, + attempts=attempts, + status=status, + lesson=lesson, + ) + self.record_episode(episode) + return episode diff --git a/src/myai/stability.py b/src/myai/stability.py new file mode 100644 index 0000000..3e96dc1 --- /dev/null +++ b/src/myai/stability.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +import json +from dataclasses import asdict, dataclass +from pathlib import Path + + +@dataclass(frozen=True) +class CodeHealth: + symbol: str + stability: float + verification_confidence: float + failure_rate: float + change_frequency: float + dependency_centrality: float + last_verified: str | None = None + + @property + def inspection_mode(self) -> str: + score = min( + 1.0, + 0.3 * self.stability + + 0.3 * self.verification_confidence + + 0.15 * (1.0 - self.failure_rate) + + 0.15 * (1.0 - self.change_frequency) + + 0.10 * (1.0 - self.dependency_centrality), + ) + if score >= 0.85: + return "reuse" + if score >= 0.60: + return "targeted" + return "deep" + + +class CodeHealthStore: + """Persistent symbol health metadata used to avoid needless deep rereads.""" + + def __init__(self, path: str | Path) -> None: + self.path = Path(path) + self.path.parent.mkdir(parents=True, exist_ok=True) + self._items: dict[str, CodeHealth] = {} + self._load() + + def upsert(self, item: CodeHealth) -> None: + self._items[item.symbol] = item + self._persist() + + def get(self, symbol: str) -> CodeHealth | None: + return self._items.get(symbol) + + def inspection_mode(self, symbol: str) -> str: + item = self.get(symbol) + return item.inspection_mode if item else "deep" + + def _load(self) -> None: + if not self.path.exists(): + return + try: + raw = json.loads(self.path.read_text(encoding="utf-8")) + except (OSError, ValueError): + return + for key, value in raw.items(): + try: + self._items[key] = CodeHealth(**value) + except (TypeError, ValueError): + continue + + def _persist(self) -> None: + self.path.write_text( + json.dumps({key: asdict(value) for key, value in self._items.items()}, ensure_ascii=False, indent=2), + encoding="utf-8", + ) diff --git a/src/myai/v10_engine.py b/src/myai/v10_engine.py new file mode 100644 index 0000000..25161ce --- /dev/null +++ b/src/myai/v10_engine.py @@ -0,0 +1,9 @@ +from __future__ import annotations + +from .v9_engine import V9AIEngine + + +class V10AIEngine(V9AIEngine): + """V10 self-healing runtime layered on the V9.1 cognitive mesh.""" + + version = "v10.0" diff --git a/src/myai/v9_engine.py b/src/myai/v9_engine.py index d6b7218..7a9f512 100644 --- a/src/myai/v9_engine.py +++ b/src/myai/v9_engine.py @@ -6,16 +6,20 @@ from .capability_ledger import CapabilityLedger from .cognitive_compute import CognitiveComputeController, CognitiveComputePolicy from .evolution import EvolutionMemory, EvolutionRecord, StrategyScore +from .fault_lab import FaultCase, FaultInjectionLab, FaultResult from .graph_v9 import ProgramGraph, ProgramSlice from .model_report import ModelReport, build_model_report from .model_router import AdaptiveModelRouter, RoutingDecision, RoutingRequest from .runtime_trace import RuntimeTraceGraph from .schemas import ChatMessage +from .self_healing import CausalDiagnosis, CausalErrorEngine, RepairMemory, RepairMemoryRecord +from .self_healing_runtime import FailureSignature, FailureSignatureStore, RepairEpisode, SelfHealingRuntime +from .stability import CodeHealth, CodeHealthStore from .v8_engine import V8AIEngine class V9AIEngine(V8AIEngine): - """V9.1 cognitive mesh with shared cognitive state and state-aware agents.""" + """V9.1 cognitive mesh with bounded self-healing runtime controls.""" version = "v9.1" @@ -30,6 +34,13 @@ def __init__(self) -> None: ledger=self.capability_ledger, strategy_scores=self.strategy_scores(limit=8), ) + self.failure_signatures = FailureSignatureStore(self.settings.failure_signature_path) + self.self_healing_runtime = SelfHealingRuntime( + episode_path=self.settings.repair_episode_path, + signature_store=self.failure_signatures, + ) + self.code_health = CodeHealthStore(self.settings.code_health_path) + self.fault_lab = FaultInjectionLab() self._refresh_program_graph() def _refresh_program_graph(self) -> None: @@ -126,6 +137,42 @@ def repair_context_v9(self, traceback_text: str) -> tuple[ChatMessage, ...]: ), ) + def failure_signature(self, traceback_text: str) -> FailureSignature: + diagnosis = self.diagnose_failure(traceback_text) + symbol = diagnosis.primary_frame.symbol if diagnosis.primary_frame else "" + return self.self_healing_runtime.signature(diagnosis.error_type, diagnosis.message, symbol) + + def inspection_mode(self, symbol: str) -> str: + return self.code_health.inspection_mode(symbol) + + def record_code_health(self, health: CodeHealth) -> None: + self.code_health.upsert(health) + + def run_repair_episode( + self, + traceback_text: str, + *, + reproduce, + validate, + lesson: str = "", + ) -> RepairEpisode: + """Record a bounded repair episode. Code promotion remains an external, verified step.""" + diagnosis = self.diagnose_failure(traceback_text) + signature = self.self_healing_runtime.signature( + diagnosis.error_type, + diagnosis.message, + diagnosis.primary_frame.symbol if diagnosis.primary_frame else "", + ) + return self.self_healing_runtime.verified_repair( + signature=signature, + reproduce=reproduce, + validate=validate, + lesson=lesson, + ) + + def run_fault_test(self, case: FaultCase, *, detector) -> FaultResult: + return self.fault_lab.run(case, detector=detector) + def capability_baseline(self) -> dict[str, object]: return self.capability_ledger.baseline() diff --git a/tests/test_self_healing_runtime.py b/tests/test_self_healing_runtime.py new file mode 100644 index 0000000..e544231 --- /dev/null +++ b/tests/test_self_healing_runtime.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from pathlib import Path + +from myai import ( + CodeHealth, + CodeHealthStore, + FaultInjectionLab, + SelfHealingRuntime, + FailureSignatureStore, +) + + +def test_failure_signature_reuse_and_verified_episode(tmp_path: Path) -> None: + signatures = FailureSignatureStore(tmp_path / "signatures.jsonl") + runtime = SelfHealingRuntime( + episode_path=tmp_path / "episodes.jsonl", + signature_store=signatures, + ) + signature = runtime.signature("TypeError", "bad value 123", "refresh_token") + + episode = runtime.verified_repair( + signature=signature, + reproduce=lambda: (True, "reproduced"), + validate=lambda: True, + lesson="validated minimal fix", + ) + + assert episode.status == "verified" + assert signatures.similar(runtime.signature("TypeError", "bad value 456", "refresh_token")) + + +def test_fault_lab_restores_invariant() -> None: + state = {"healthy": True} + lab = FaultInjectionLab() + result = lab.run( + lab.simple_toggle(state, "healthy"), + detector=lambda: state["healthy"] is False, + ) + assert result.detected is True + assert result.recovered is True + assert result.verified is True + assert state["healthy"] is True + + +def test_code_health_prefers_reuse_for_stable_verified_symbol(tmp_path: Path) -> None: + store = CodeHealthStore(tmp_path / "health.json") + store.upsert( + CodeHealth( + symbol="stable_fn", + stability=0.99, + verification_confidence=0.99, + failure_rate=0.0, + change_frequency=0.01, + dependency_centrality=0.05, + last_verified="2026-08-25T00:00:00+00:00", + ) + ) + assert store.inspection_mode("stable_fn") == "reuse" diff --git a/tests/test_v10_release.py b/tests/test_v10_release.py new file mode 100644 index 0000000..02c42b5 --- /dev/null +++ b/tests/test_v10_release.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from myai import AIEngine, V10AIEngine + + +def test_v10_is_public_engine() -> None: + engine = AIEngine() + assert isinstance(engine, V10AIEngine) + assert engine.version == "v10.0" + assert engine.settings.self_healing_enabled is True + assert engine.inspection_mode("unknown_symbol") == "deep"