"""Consent-based memory with local-first persistence.""" from __future__ import annotations import json from dataclasses import dataclass, asdict from datetime import datetime, timezone from pathlib import Path from typing import Dict, List, Optional from .perception import PerceptionInput @dataclass class MemoryEntry: fingerprint: str consent: bool summary: str tags: List[str] timestamp: str metadata: Dict[str, str] @classmethod def from_dict(cls, payload: Dict[str, str]) -> "MemoryEntry": return cls( fingerprint=payload["fingerprint"], consent=payload["consent"], summary=payload["summary"], tags=list(payload.get("tags", [])), timestamp=payload["timestamp"], metadata=dict(payload.get("metadata", {})), ) class ConsentMemory: """Stores entries locally only when consent is provided.""" def __init__(self, path: Path | None = None) -> None: self._path = path or Path.home() / ".config" / "blux-ca" / "memory" / "consent.jsonl" self._path.parent.mkdir(parents=True, exist_ok=True) self._session: Dict[str, MemoryEntry] = {} def store( self, perception: PerceptionInput, *, consent: bool, summary: str, metadata: Optional[Dict[str, str]] = None, ) -> MemoryEntry: entry = MemoryEntry( fingerprint=perception.fingerprint, consent=consent, summary=summary, tags=list(perception.tags), timestamp=datetime.now(timezone.utc).isoformat(), metadata=dict(metadata or {}), ) self._session[entry.fingerprint] = entry if consent: self._append(entry) return entry def _append(self, entry: MemoryEntry) -> None: with self._path.open("a", encoding="utf-8") as handle: handle.write(json.dumps(asdict(entry), ensure_ascii=False) + "\n") def session_entries(self) -> List[MemoryEntry]: return list(self._session.values()) def persistent_entries(self, *, limit: int | None = None) -> List[MemoryEntry]: if not self._path.exists(): return [] entries: List[MemoryEntry] = [] with self._path.open("r", encoding="utf-8") as handle: for line in handle: if not line.strip(): continue entries.append(MemoryEntry.from_dict(json.loads(line))) if limit and len(entries) >= limit: break return entries __all__ = ["ConsentMemory", "MemoryEntry"]