| from fastapi import APIRouter, HTTPException |
| from pydantic import BaseModel |
| from typing import Any, Dict, List, Optional |
|
|
| from src.memory.models import MemoryTier, MemoryOutcome |
| from src.memory.models import MemoryItem, ExperientialStrategy |
| from src.memory.safla import update_confidence, should_retire |
| from src.memory.store import ( |
| associative_store as _associative, |
| temporal_store as _temporal, |
| experiential_store as _experiential, |
| working_store as _working, |
| ) |
|
|
| router = APIRouter(prefix="/memory", tags=["memory"]) |
|
|
|
|
| class MemoryAddRequest(BaseModel): |
| tier: str |
| title: str |
| content: str |
| tags: List[str] = [] |
| metadata: Dict[str, Any] = {} |
|
|
|
|
| class StrategyRequest(BaseModel): |
| title: str |
| description: str |
| content: str |
| outcome: str = "success" |
| task_pattern: str = "" |
| tags: List[str] = [] |
|
|
|
|
| class SAFLAFeedbackRequest(BaseModel): |
| item_id: str |
| outcome: str |
|
|
|
|
| |
|
|
| @router.get("") |
| async def memory_overview(): |
| return { |
| "associative": {"count": len(_associative.all())}, |
| "temporal": {"count": len(_temporal.all())}, |
| "experiential": { |
| "count": len(_experiential.all()), |
| "guardrails": len(_experiential.guardrails()), |
| "strategies": len(_experiential.positive_strategies()), |
| }, |
| "working": { |
| "count": len(_working.all()), |
| "utilization": _working.utilization, |
| "subgoals": len(_working.subgoal_tree()), |
| }, |
| } |
|
|
|
|
| |
|
|
| @router.get("/associative") |
| async def list_associative(query: str = "", top_k: int = 20): |
| if query: |
| results = _associative.retrieve(query, top_k=top_k) |
| return [{"item": _fmt(r.item), "score": r.relevance_score} for r in results] |
| return [_fmt(i) for i in _associative.all()] |
|
|
|
|
| @router.post("/associative") |
| async def add_associative(req: MemoryAddRequest): |
| item = MemoryItem(title=req.title, content=req.content, tags=req.tags, metadata=req.metadata) |
| _associative.add(item) |
| return _fmt(item) |
|
|
|
|
| |
|
|
| @router.get("/temporal") |
| async def list_temporal(): |
| return [_fmt(i) for i in _temporal.timeline()] |
|
|
|
|
| @router.post("/temporal") |
| async def add_temporal(req: MemoryAddRequest, follows_id: Optional[str] = None): |
| item = MemoryItem(title=req.title, content=req.content, tags=req.tags) |
| _temporal.add_event(item, follows_id=follows_id) |
| return _fmt(item) |
|
|
|
|
| |
|
|
| @router.get("/experiential") |
| async def list_experiential(guardrails_only: bool = False, query: str = "", top_k: int = 20): |
| if query: |
| fn = _experiential.retrieve_guardrails if guardrails_only else _experiential.retrieve |
| results = fn(query, top_k=top_k) |
| return [{"item": _fmt(r.item), "score": r.relevance_score} for r in results] |
| items = _experiential.guardrails() if guardrails_only else _experiential.all() |
| return [_fmt(i) for i in items] |
|
|
|
|
| @router.post("/experiential") |
| async def add_strategy(req: StrategyRequest): |
| strategy = _experiential.distill_from_outcome( |
| title=req.title, |
| description=req.description, |
| content=req.content, |
| outcome=MemoryOutcome(req.outcome), |
| task_pattern=req.task_pattern, |
| tags=req.tags, |
| ) |
| return _fmt(strategy) |
|
|
|
|
| @router.post("/experiential/safla") |
| async def safla_feedback(req: SAFLAFeedbackRequest): |
| strategy = _experiential.get(req.item_id) |
| if not strategy: |
| raise HTTPException(status_code=404, detail="Strategy not found") |
| update_confidence(strategy, MemoryOutcome(req.outcome)) |
| retired = should_retire(strategy) |
| return {"item_id": req.item_id, "confidence": strategy.confidence, "retired": retired} |
|
|
|
|
| @router.post("/experiential/consolidate") |
| async def consolidate(): |
| removed = _experiential.consolidate() |
| return {"removed": removed, "remaining": len(_experiential.all())} |
|
|
|
|
| |
|
|
| @router.get("/working") |
| async def list_working(): |
| return { |
| "items": [_fmt(i) for i in _working.all()], |
| "subgoals": _working.subgoal_tree(), |
| "utilization": _working.utilization, |
| } |
|
|
|
|
| @router.delete("/working") |
| async def clear_working(): |
| _working.clear() |
| return {"cleared": True} |
|
|
|
|
| |
|
|
| def _fmt(item: MemoryItem) -> Dict[str, Any]: |
| d: Dict[str, Any] = { |
| "item_id": item.item_id, |
| "tier": item.tier.value, |
| "title": item.title, |
| "content": item.content, |
| "tags": item.tags, |
| "confidence": item.confidence, |
| "usage_count": item.usage_count, |
| "created_at": item.created_at.isoformat(), |
| "updated_at": item.updated_at.isoformat(), |
| } |
| if isinstance(item, ExperientialStrategy): |
| d.update({ |
| "description": item.description, |
| "outcome": item.outcome.value, |
| "kind": item.kind, |
| "is_guardrail": item.is_guardrail, |
| "task_pattern": item.task_pattern, |
| }) |
| return d |
|
|