verdict / memory.py
AnnaRay9999's picture
Upload 7 files
04524a7 verified
Raw History Blame Contribute Delete
4.57 kB
"""Verdict memory — turns one-shot checks into continuous monitoring.
This is the leap from "check once" to "always-on control": Verdict remembers
every verdict it issues, and on a re-check it reports WHAT CHANGED — e.g.
"was APPROVE, now ESCALATE: a new lawsuit appeared." That is the difference
between a demo and a product a compliance team actually subscribes to.
Memory backend:
- Default: a fast local JSON store, so the app and demo always work with zero
extra setup or dependencies.
- Optional: Cognee (https://cognee.ai) knowledge-graph memory, enabled by
setting COGNEE_API_KEY. Cognee gives agents structured, persistent memory;
here it stores each counterparty's verdict history so risk can be reasoned
over time, not just retrieved.
Set MEMORY_BACKEND=cognee (plus COGNEE_API_KEY / COGNEE_SERVICE_URL) to use
Cognee; otherwise the local store is used.
"""
import json
import os
import time
_BACKEND = os.getenv("MEMORY_BACKEND", "local").lower()
_LOCAL_PATH = os.getenv("MEMORY_PATH", "verdict_memory.json")
_COGNEE_KEY = os.getenv("COGNEE_API_KEY", "")
_COGNEE_URL = os.getenv("COGNEE_SERVICE_URL", "")
def _norm(name: str) -> str:
return " ".join((name or "").lower().split())
# --- Local JSON store -------------------------------------------------------
def _load_local() -> dict:
try:
with open(_LOCAL_PATH, "r", encoding="utf-8") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return {}
def _save_local(store: dict) -> None:
try:
with open(_LOCAL_PATH, "w", encoding="utf-8") as f:
json.dump(store, f)
except OSError:
pass # never let a memory write break a verdict
def _local_get(name: str):
return _load_local().get(_norm(name))
def _local_put(name: str, record: dict) -> None:
store = _load_local()
store[_norm(name)] = record
_save_local(store)
# --- Cognee store (optional) ------------------------------------------------
def _cognee_available() -> bool:
return _BACKEND == "cognee" and bool(_COGNEE_KEY)
def _cognee_put(name: str, record: dict) -> None:
"""Best-effort store into Cognee; falls back to local on any failure."""
try:
import asyncio
import cognee
async def _go():
if _COGNEE_URL:
await cognee.serve(url=_COGNEE_URL, api_key=_COGNEE_KEY)
text = (f"Counterparty due-diligence record for {name}: "
f"verdict {record['verdict']}, risk {record['risk_score']}, "
f"summary: {record['summary']}")
await cognee.remember(text, session_id=_norm(name))
asyncio.run(_go())
except Exception: # noqa: BLE001
pass
# Always mirror to local so change-detection is reliable and fast.
_local_put(name, record)
def _cognee_get(name: str):
# For reliable structured diffing we read the mirrored local record.
return _local_get(name)
# --- Public API -------------------------------------------------------------
def recall(name: str):
"""Return the last stored record for a counterparty, or None."""
if _cognee_available():
return _cognee_get(name)
return _local_get(name)
def remember(name: str, decision: dict) -> None:
"""Store the latest verdict for a counterparty."""
record = {
"verdict": decision.get("verdict"),
"risk_score": decision.get("risk_score"),
"summary": decision.get("summary", ""),
"ts": time.time(),
}
if _cognee_available():
_cognee_put(name, record)
else:
_local_put(name, record)
def diff(name: str, decision: dict):
"""Compare the new decision to the last stored one.
Returns None if no prior record, else a change summary dict."""
prev = recall(name)
if not prev:
return None
old_v, new_v = prev.get("verdict"), decision.get("verdict")
old_r, new_r = prev.get("risk_score", 0), decision.get("risk_score", 0)
changed = (old_v != new_v) or (abs((new_r or 0) - (old_r or 0)) >= 10)
direction = "unchanged"
if (new_r or 0) > (old_r or 0):
direction = "worsened"
elif (new_r or 0) < (old_r or 0):
direction = "improved"
return {
"changed": changed,
"direction": direction,
"old_verdict": old_v, "new_verdict": new_v,
"old_risk": old_r, "new_risk": new_r,
"backend": "cognee" if _cognee_available() else "local",
}
def backend_name() -> str:
return "Cognee" if _cognee_available() else "local store"