Cesium2 / src /kvstore.py
MORPH-AI
feat: dynamic MoE expansion, multi-head CoT, plugin architecture, improved MoD
82f262a
Raw
History Blame Contribute Delete
2.9 kB
"""
KVStore - persistent key-value cache (disk-backed JSON, TTL + LRU eviction).
Wraps cross-turn conversation context so it survives process restarts.
"""
import json
import threading
import time
from pathlib import Path
from typing import Any, Dict, List, Optional
class KVStore:
def __init__(self, path: str, max_entries: int = 1000, default_ttl: float = 3600.0):
self.path = Path(path)
self.max_entries = max_entries
self.default_ttl = default_ttl
self._lock = threading.Lock()
self._data: Dict[str, Any] = {}
self._meta: Dict[str, Dict] = {} # key -> {exp, last, size}
if self.path.exists():
self._load()
def _load(self):
try:
raw = json.loads(self.path.read_text(encoding="utf-8"))
self._data = raw.get("data", {})
self._meta = raw.get("meta", {})
except (json.JSONDecodeError, OSError):
self._data, self._meta = {}, {}
def _persist(self):
self.path.parent.mkdir(parents=True, exist_ok=True)
tmp = self.path.with_suffix(".tmp")
tmp.write_text(
json.dumps({"data": self._data, "meta": self._meta}),
encoding="utf-8",
)
tmp.replace(self.path)
def _evict(self):
now = time.time()
expired = [k for k, m in self._meta.items() if m.get("exp", 0) < now]
for k in expired:
self._data.pop(k, None)
self._meta.pop(k, None)
while len(self._data) > self.max_entries:
oldest = min(self._meta, key=lambda k: self._meta[k].get("last", 0))
self._data.pop(oldest, None)
self._meta.pop(oldest, None)
def get(self, key: str, default: Any = None) -> Any:
with self._lock:
m = self._meta.get(key)
if m is None:
return default
if m.get("exp", 0) < time.time():
self._data.pop(key, None)
self._meta.pop(key, None)
return default
m["last"] = time.time()
return self._data.get(key, default)
def set(self, key: str, value: Any, ttl: Optional[float] = None):
with self._lock:
exp = time.time() + (ttl if ttl is not None else self.default_ttl)
self._data[key] = value
self._meta[key] = {
"exp": exp,
"last": time.time(),
"size": len(json.dumps(value, default=str)),
}
self._evict()
self._persist()
def delete(self, key: str):
with self._lock:
self._data.pop(key, None)
self._meta.pop(key, None)
self._persist()
def keys(self) -> List[str]:
with self._lock:
return list(self._data.keys())
def flush(self):
with self._lock:
self._persist()