MnemoCore / src /mnemocore /core /confidence.py
Granis87's picture
Upload folder using huggingface_hub
c3a3710 verified
"""
Confidence Calibration Module (Phase 5.0)
==========================================
Generates structured confidence envelopes for retrieved memories,
combining all available trust signals into a single queryable object.
Signals used:
- BayesianLTP reliability (mean of Beta posterior)
- access_count (low count → less evidence)
- staleness (days since last verification)
- source type (external ≤ user_correction vs observation)
- contradiction flag (from ProvenanceRecord)
Output: a ConfidenceEnvelope dict appended to every query response,
enabling consuming agents to make trust-aware decisions.
Public API:
env = ConfidenceEnvelopeGenerator.build(node, provenance)
level = env["level"] # "high" | "medium" | "low" | "contradicted" | "stale"
"""
from __future__ import annotations
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any, Dict, Optional
if TYPE_CHECKING:
from .node import MemoryNode
from .provenance import ProvenanceRecord
# ------------------------------------------------------------------ #
# Confidence levels (ordered by trust) #
# ------------------------------------------------------------------ #
LEVEL_HIGH = "high"
LEVEL_MEDIUM = "medium"
LEVEL_LOW = "low"
LEVEL_CONTRADICTED = "contradicted"
LEVEL_STALE = "stale"
# Thresholds
RELIABILITY_HIGH_THRESHOLD = 0.80
RELIABILITY_MEDIUM_THRESHOLD = 0.50
ACCESS_COUNT_MIN_EVIDENCE = 2 # Less than this → low confidence
ACCESS_COUNT_HIGH_EVIDENCE = 5 # At least this → supports high confidence
STALENESS_STALE_DAYS = 30 # Days without verification → stale
# ------------------------------------------------------------------ #
# Source-type trust weights #
# ------------------------------------------------------------------ #
SOURCE_TRUST: Dict[str, float] = {
"observation": 1.0,
"inference": 0.8,
"external_sync": 0.75,
"dream": 0.6,
"consolidation": 0.85,
"prediction": 0.5,
"user_correction": 1.0,
"unknown": 0.5,
}
# ------------------------------------------------------------------ #
# Confidence Envelope Generator #
# ------------------------------------------------------------------ #
class ConfidenceEnvelopeGenerator:
"""
Builds a confidence_envelope dict for a MemoryNode.
Does NOT mutate the node — only reads fields.
Thread-safe; no shared state.
"""
@staticmethod
def _reliability(node: "MemoryNode") -> float:
"""
Extract reliability float from the node.
Falls back to ltp_strength if no Bayesian state is attached.
"""
bayes = getattr(node, "_bayes", None)
if bayes is not None:
return float(bayes.mean)
return float(getattr(node, "ltp_strength", 0.5))
@staticmethod
def _staleness_days(node: "MemoryNode", provenance: Optional["ProvenanceRecord"]) -> float:
"""Days since last verification, or days since last access."""
if provenance:
# Find the most recent 'verified' event
for evt in reversed(provenance.lineage):
if evt.event == "verified" and evt.outcome is True:
try:
ts = datetime.fromisoformat(evt.timestamp)
if ts.tzinfo is None:
ts = ts.replace(tzinfo=timezone.utc)
delta = datetime.now(timezone.utc) - ts
return delta.total_seconds() / 86400.0
except (ValueError, TypeError):
pass
# Fall back to last_accessed on the node
last = getattr(node, "last_accessed", None)
if last is not None:
if getattr(last, "tzinfo", None) is None:
last = last.replace(tzinfo=timezone.utc)
delta = datetime.now(timezone.utc) - last
return delta.total_seconds() / 86400.0
return 0.0
@classmethod
def build(
cls,
node: "MemoryNode",
provenance: Optional["ProvenanceRecord"] = None,
) -> Dict[str, Any]:
"""
Build a full confidence_envelope dict for the given node.
Returns a dict suitable for direct JSON serialization.
"""
reliability = cls._reliability(node)
access_count: int = getattr(node, "access_count", 1)
staleness: float = cls._staleness_days(node, provenance)
# Determine source type for trust weighting
source_type = "unknown"
if provenance:
source_type = provenance.origin.type
source_trust = SOURCE_TRUST.get(source_type, 0.5)
# Contradiction check
is_contradicted = provenance.is_contradicted() if provenance else False
# Last verified date (human-readable)
last_verified: Optional[str] = None
if provenance:
for evt in reversed(provenance.lineage):
if evt.event == "verified" and evt.outcome is True:
last_verified = evt.timestamp
break
# ---- Determine level ------------------------------------ #
if is_contradicted:
level = LEVEL_CONTRADICTED
elif staleness > STALENESS_STALE_DAYS:
level = LEVEL_STALE
elif (
reliability >= RELIABILITY_HIGH_THRESHOLD
and access_count >= ACCESS_COUNT_HIGH_EVIDENCE
and source_trust >= 0.75
):
level = LEVEL_HIGH
elif reliability >= RELIABILITY_MEDIUM_THRESHOLD and access_count >= ACCESS_COUNT_MIN_EVIDENCE:
level = LEVEL_MEDIUM
else:
level = LEVEL_LOW
envelope: Dict[str, Any] = {
"level": level,
"reliability": round(reliability, 4),
"access_count": access_count,
"staleness_days": round(staleness, 1),
"source_type": source_type,
"source_trust": round(source_trust, 2),
"is_contradicted": is_contradicted,
}
if last_verified:
envelope["last_verified"] = last_verified
return envelope
# ------------------------------------------------------------------ #
# Convenience function #
# ------------------------------------------------------------------ #
def build_confidence_envelope(
node: "MemoryNode",
provenance: Optional["ProvenanceRecord"] = None,
) -> Dict[str, Any]:
"""
Module-level shortcut for ConfidenceEnvelopeGenerator.build().
Args:
node: MemoryNode to evaluate.
provenance: Optional ProvenanceRecord for the node.
Returns:
confidence_envelope dict with level, reliability, staleness, etc.
"""
return ConfidenceEnvelopeGenerator.build(node, provenance)