| """ |
| Experiential Memory β ReasoningBank implementation. |
| |
| Stores strategies as structured triples: |
| Title β strategy name |
| Description β contextual scenario |
| Content β step-by-step procedural rules and decision rationales |
| |
| Extracts strategies from both successful paths AND failed runs. |
| Failed runs generate evergreen negative guardrail rules via SAFLA. |
| """ |
| from datetime import datetime, timezone |
| from typing import Dict, List, Optional |
| from .models import ExperientialStrategy, MemoryOutcome, MemoryTier, RetrievalResult |
| from .retriever import retrieve |
| from .safla import update_confidence, should_retire, extract_guardrail |
| from src.config import settings |
|
|
|
|
| class ExperientialRepository: |
| def __init__(self): |
| self._strategies: Dict[str, ExperientialStrategy] = {} |
|
|
| |
|
|
| def add(self, strategy: ExperientialStrategy) -> ExperientialStrategy: |
| strategy.tier = MemoryTier.EXPERIENTIAL |
| if strategy.confidence == 0.5: |
| strategy.confidence = settings.safla_initial_confidence |
| self._strategies[strategy.item_id] = strategy |
| return strategy |
|
|
| def get(self, item_id: str) -> Optional[ExperientialStrategy]: |
| return self._strategies.get(item_id) |
|
|
| def all(self) -> List[ExperientialStrategy]: |
| return list(self._strategies.values()) |
|
|
| def guardrails(self) -> List[ExperientialStrategy]: |
| return [s for s in self._strategies.values() if s.is_guardrail] |
|
|
| def positive_strategies(self) -> List[ExperientialStrategy]: |
| return [s for s in self._strategies.values() if not s.is_guardrail] |
|
|
| |
|
|
| def record_outcome( |
| self, |
| strategy_ids: List[str], |
| outcome: MemoryOutcome, |
| ) -> List[str]: |
| """ |
| Apply SAFLA confidence update to each strategy used in a task. |
| On failure: extract a guardrail from each failing strategy. |
| Returns list of newly created guardrail IDs (if any). |
| """ |
| new_guardrail_ids: List[str] = [] |
| for sid in strategy_ids: |
| strategy = self._strategies.get(sid) |
| if not strategy: |
| continue |
| update_confidence(strategy, outcome) |
| if outcome == MemoryOutcome.FAILURE and not strategy.is_guardrail: |
| guardrail = extract_guardrail(strategy) |
| self._strategies[guardrail.item_id] = guardrail |
| new_guardrail_ids.append(guardrail.item_id) |
| if should_retire(strategy): |
| strategy.metadata["retired"] = True |
| return new_guardrail_ids |
|
|
| def consolidate(self) -> int: |
| """Remove strategies below the consolidation threshold. Returns count removed.""" |
| to_remove = [ |
| sid for sid, s in self._strategies.items() |
| if s.confidence < settings.memory_consolidation_threshold |
| ] |
| for sid in to_remove: |
| del self._strategies[sid] |
| return len(to_remove) |
|
|
| |
|
|
| def retrieve(self, query: str, top_k: int = 5) -> List[RetrievalResult]: |
| results = retrieve(query, self.all(), top_k=top_k) |
| for r in results: |
| r.item.usage_count += 1 |
| return results |
|
|
| def retrieve_guardrails(self, query: str, top_k: int = 3) -> List[RetrievalResult]: |
| results = retrieve(query, self.guardrails(), top_k=top_k) |
| return results |
|
|
| |
|
|
| def distill_from_outcome( |
| self, |
| title: str, |
| description: str, |
| content: str, |
| outcome: MemoryOutcome, |
| task_pattern: str = "", |
| tags: List[str] = [], |
| ) -> ExperientialStrategy: |
| """ |
| Create and store a new strategy distilled from a task outcome. |
| Success β positive procedural strategy. |
| Failure β negative guardrail rule. |
| """ |
| strategy = ExperientialStrategy( |
| title=title, |
| description=description, |
| content=content, |
| outcome=MemoryOutcome.GUARDRAIL if outcome == MemoryOutcome.FAILURE else outcome, |
| kind="guardrail" if outcome == MemoryOutcome.FAILURE else "strategy", |
| is_guardrail=(outcome == MemoryOutcome.FAILURE), |
| task_pattern=task_pattern, |
| tags=tags, |
| confidence=settings.safla_initial_confidence, |
| ) |
| return self.add(strategy) |
|
|