FlyBrain-Lab / src /llm /research_memory.py
timfromhcs's picture
FlyBrain v4.1.0 Space build (REAL_SUBGRAPH, CPU-only, honest backend)
3d46076 verified
Raw History Blame Contribute Delete
3.67 kB
"""Research memory (STAGE K): persistent, append-only, hash-chained records
of the scientist's experiments, hypotheses, outcomes and comparisons.
The scientist learns from the history of its own research."""
import hashlib
import json
import os
import time
from typing import Any, Dict, List, Optional
RESEARCH_MEMORY_VERSION = "research_memory_v1"
def _record_hash(rec: Dict[str, Any], prev_hash: str) -> str:
payload = json.dumps(rec, sort_keys=True) + "|" + prev_hash
return hashlib.sha256(payload.encode()).hexdigest()
class ResearchMemory:
"""Append-only JSONL store with a hash chain (tamper-evident)."""
def __init__(self, path: str = "diagnostics/research_memory.jsonl"):
self.path = path
self.records: List[Dict[str, Any]] = []
self._prev_hash = "0" * 64
if os.path.exists(path):
self._load()
def _load(self) -> None:
with open(self.path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
rec = json.loads(line)
self.records.append(rec)
self._prev_hash = rec.get("record_hash", self._prev_hash)
def append(self, kind: str, payload: Dict[str, Any]) -> Dict[str, Any]:
rec = {
"seq": len(self.records),
"ts": time.time(),
"kind": kind,
"payload": payload,
"prev_hash": self._prev_hash,
}
rec["record_hash"] = _record_hash({k: v for k, v in rec.items()
if k != "record_hash"}, self._prev_hash)
self.records.append(rec)
self._prev_hash = rec["record_hash"]
os.makedirs(os.path.dirname(self.path) or ".", exist_ok=True)
with open(self.path, "a", encoding="utf-8") as f:
f.write(json.dumps(rec, sort_keys=True) + "\n")
return rec
def verify_chain(self) -> bool:
prev = "0" * 64
for rec in self.records:
if rec.get("prev_hash") != prev:
return False
expected = _record_hash({k: v for k, v in rec.items()
if k != "record_hash"}, prev)
if rec.get("record_hash") != expected:
return False
prev = rec["record_hash"]
return True
# ---- scientist-facing queries ----
def experiments(self) -> List[Dict[str, Any]]:
return [r for r in self.records if r["kind"] == "experiment"]
def hypotheses(self, status: Optional[str] = None) -> List[Dict[str, Any]]:
out = [r for r in self.records if r["kind"] == "hypothesis"]
if status is not None:
out = [r for r in out if r["payload"].get("status") == status]
return out
def failed_experiments(self) -> List[Dict[str, Any]]:
return [r for r in self.records
if r["kind"] == "experiment" and r["payload"].get("status") == "FAILED"]
def successful_protocols(self) -> List[Dict[str, Any]]:
"""Protocol shapes that produced measurable results before."""
return [r["payload"] for r in self.records
if r["kind"] == "experiment" and r["payload"].get("measured", True)
and r["payload"].get("status") == "EXECUTED"]
def summary(self) -> Dict[str, Any]:
kinds: Dict[str, int] = {}
for r in self.records:
kinds[r["kind"]] = kinds.get(r["kind"], 0) + 1
return {"records": len(self.records), "kinds": kinds,
"chain_valid": self.verify_chain(),
"version": RESEARCH_MEMORY_VERSION}