study-buddy / app /services /memory_service.py
GitHub Actions
deploy d092bea3608b7a29952f16357fda39b7a29e399b
2e818da
Raw
History Blame Contribute Delete
3 kB
"""Lightweight local memory (the 'cognee' role) -> disk-backed, no external deps.
Two roles, both under ~/.studybuddy/cognee/ (per CLAUDE.md, local only):
- Ephemeral per-PDF report clusters: scratch memory holding each processed note-insight
while a report is being compiled, FLUSHED when the report session ends.
- Persistent learning trajectory: append-only per-PDF history of evaluation snapshots
(classification + reasoning + scores over time). Never flushed.
Swappable for a real LanceDB/Cognee backend later without changing callers.
"""
from __future__ import annotations
import json
import os
import time
from typing import Any, Dict, List
_BASE = os.path.expanduser("~/.studybuddy/cognee")
_CLUSTERS = os.path.join(_BASE, "clusters")
_TRAJ = os.path.join(_BASE, "trajectory")
class MemoryService:
def __init__(self) -> None:
os.makedirs(_CLUSTERS, exist_ok=True)
os.makedirs(_TRAJ, exist_ok=True)
# -- Ephemeral report cluster ------------------------------------- #
def _cluster_path(self, document_id: str) -> str:
return os.path.join(_CLUSTERS, f"{document_id}.json")
def push_insights(self, document_id: str, insights: List[Dict[str, Any]]) -> None:
if not document_id:
return
existing = self.read_cluster(document_id)
existing.extend(insights)
with open(self._cluster_path(document_id), "w", encoding="utf-8") as f:
json.dump(existing, f)
def read_cluster(self, document_id: str) -> List[Dict[str, Any]]:
if not document_id:
return []
p = self._cluster_path(document_id)
if not os.path.exists(p):
return []
try:
with open(p, encoding="utf-8") as f:
return json.load(f)
except Exception:
return []
def flush_cluster(self, document_id: str) -> None:
if not document_id:
return
p = self._cluster_path(document_id)
if os.path.exists(p):
try:
os.remove(p)
except Exception:
pass
# -- Persistent trajectory ---------------------------------------- #
def _traj_path(self, document_id: str) -> str:
return os.path.join(_TRAJ, f"{document_id}.json")
def append_trajectory(self, document_id: str, snapshot: Dict[str, Any]) -> None:
if not document_id:
return
items = self.read_trajectory(document_id)
items.append({**snapshot, "ts": time.time()})
with open(self._traj_path(document_id), "w", encoding="utf-8") as f:
json.dump(items, f)
def read_trajectory(self, document_id: str) -> List[Dict[str, Any]]:
if not document_id:
return []
p = self._traj_path(document_id)
if not os.path.exists(p):
return []
try:
with open(p, encoding="utf-8") as f:
return json.load(f)
except Exception:
return []