Add persistent memory: Step 1-3 implementation
- Step 1: persist_signature() - append-only JSONL persistence
- Step 2: encode_to_phase() - sentence transformer phase encoding
(fallback to zeros if model unavailable)
- Step 3: retrieve_signatures(), compute_resonance_score(), phase_distance()
- Added origin/parent_id fields to TemporalSignature schema
- Added sentence-transformers to requirements.txt
This commit is contained in:
@@ -57,6 +57,8 @@ class TemporalSignature:
|
|||||||
frequency_modes: Active frequency components
|
frequency_modes: Active frequency components
|
||||||
context_hash: Hash of associated context/information
|
context_hash: Hash of associated context/information
|
||||||
strength: Memory strength classification
|
strength: Memory strength classification
|
||||||
|
origin: Source of this memory (user, solaria, etc.)
|
||||||
|
parent_id: Parent signature ID for链条 tracking
|
||||||
created_at: When this memory was formed
|
created_at: When this memory was formed
|
||||||
last_accessed: When this memory was last recalled
|
last_accessed: When this memory was last recalled
|
||||||
access_count: Number of times recalled
|
access_count: Number of times recalled
|
||||||
@@ -71,6 +73,8 @@ class TemporalSignature:
|
|||||||
strength: MemoryStrength
|
strength: MemoryStrength
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
last_accessed: datetime
|
last_accessed: datetime
|
||||||
|
origin: str = "user" # Added: source of memory
|
||||||
|
parent_id: Optional[str] = None # Added: parent signature for链条
|
||||||
access_count: int = 0
|
access_count: int = 0
|
||||||
decay_rate: float = 0.01
|
decay_rate: float = 0.01
|
||||||
content: Optional[Dict[str, Any]] = None
|
content: Optional[Dict[str, Any]] = None
|
||||||
@@ -84,6 +88,8 @@ class TemporalSignature:
|
|||||||
"frequency_modes": self.frequency_modes,
|
"frequency_modes": self.frequency_modes,
|
||||||
"context_hash": self.context_hash,
|
"context_hash": self.context_hash,
|
||||||
"strength": self.strength.value,
|
"strength": self.strength.value,
|
||||||
|
"origin": self.origin,
|
||||||
|
"parent_id": self.parent_id,
|
||||||
"created_at": self.created_at.isoformat(),
|
"created_at": self.created_at.isoformat(),
|
||||||
"last_accessed": self.last_accessed.isoformat(),
|
"last_accessed": self.last_accessed.isoformat(),
|
||||||
"access_count": self.access_count,
|
"access_count": self.access_count,
|
||||||
@@ -101,6 +107,8 @@ class TemporalSignature:
|
|||||||
frequency_modes=data["frequency_modes"],
|
frequency_modes=data["frequency_modes"],
|
||||||
context_hash=data["context_hash"],
|
context_hash=data["context_hash"],
|
||||||
strength=MemoryStrength(data["strength"]),
|
strength=MemoryStrength(data["strength"]),
|
||||||
|
origin=data.get("origin", "user"),
|
||||||
|
parent_id=data.get("parent_id"),
|
||||||
created_at=datetime.fromisoformat(data["created_at"]),
|
created_at=datetime.fromisoformat(data["created_at"]),
|
||||||
last_accessed=datetime.fromisoformat(data["last_accessed"]),
|
last_accessed=datetime.fromisoformat(data["last_accessed"]),
|
||||||
access_count=data["access_count"],
|
access_count=data["access_count"],
|
||||||
@@ -221,7 +229,9 @@ class TemporalMemory:
|
|||||||
self,
|
self,
|
||||||
temporal_state: TemporalState,
|
temporal_state: TemporalState,
|
||||||
context: Optional[Dict[str, Any]] = None,
|
context: Optional[Dict[str, Any]] = None,
|
||||||
force_attention: bool = False
|
force_attention: bool = False,
|
||||||
|
origin: str = "user",
|
||||||
|
parent_id: Optional[str] = None
|
||||||
) -> TemporalSignature:
|
) -> TemporalSignature:
|
||||||
"""
|
"""
|
||||||
Encode a temporal state into a persistent memory.
|
Encode a temporal state into a persistent memory.
|
||||||
@@ -233,6 +243,8 @@ class TemporalMemory:
|
|||||||
temporal_state: The KAIROS temporal state to encode
|
temporal_state: The KAIROS temporal state to encode
|
||||||
context: Associated semantic/contextual information
|
context: Associated semantic/contextual information
|
||||||
force_attention: Force encoding even below threshold
|
force_attention: Force encoding even below threshold
|
||||||
|
origin: Source of this memory (user, solaria, etc.)
|
||||||
|
parent_id: Parent signature ID for链条 tracking
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The created TemporalSignature
|
The created TemporalSignature
|
||||||
@@ -269,6 +281,8 @@ class TemporalMemory:
|
|||||||
frequency_modes=list(temporal_state.frequency_modes) if temporal_state.frequency_modes else [],
|
frequency_modes=list(temporal_state.frequency_modes) if temporal_state.frequency_modes else [],
|
||||||
context_hash=self._hash_context(context),
|
context_hash=self._hash_context(context),
|
||||||
strength=strength,
|
strength=strength,
|
||||||
|
origin=origin,
|
||||||
|
parent_id=parent_id,
|
||||||
created_at=datetime.utcnow(),
|
created_at=datetime.utcnow(),
|
||||||
last_accessed=datetime.utcnow(),
|
last_accessed=datetime.utcnow(),
|
||||||
access_count=0,
|
access_count=0,
|
||||||
@@ -717,3 +731,171 @@ def create_temporal_memory(
|
|||||||
memory.bind_engine(bind_to)
|
memory.bind_engine(bind_to)
|
||||||
|
|
||||||
return memory
|
return memory
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Step 1: Log-Only Persistence (JSONL)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def persist_signature(signature: TemporalSignature, filepath: str = "memory.jsonl") -> None:
|
||||||
|
"""
|
||||||
|
Append signature to append-only JSONL file.
|
||||||
|
|
||||||
|
Fire-and-forget: write after transduction completes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
signature: TemporalSignature to persist
|
||||||
|
filepath: Path to JSONL file
|
||||||
|
"""
|
||||||
|
with open(filepath, "a") as f:
|
||||||
|
f.write(json.dumps(signature.to_dict()) + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Step 2: Phase Encoder (Sentence Transformers)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
_model = None
|
||||||
|
|
||||||
|
def get_phase_model():
|
||||||
|
"""Get or create the sentence transformer model."""
|
||||||
|
global _model
|
||||||
|
if _model is None:
|
||||||
|
try:
|
||||||
|
from sentence_transformers import SentenceTransformer
|
||||||
|
_model = SentenceTransformer('all-MiniLM-L6-v2')
|
||||||
|
except ImportError:
|
||||||
|
_model = None
|
||||||
|
return _model
|
||||||
|
|
||||||
|
|
||||||
|
def encode_to_phase(text: str) -> List[float]:
|
||||||
|
"""
|
||||||
|
Encode text to phase vector using sentence transformer.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: Input text to encode
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Phase vector (list of floats)
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
model = get_phase_model()
|
||||||
|
if model is None:
|
||||||
|
# Fallback: return zeros if model not available
|
||||||
|
return [0.0] * 384
|
||||||
|
|
||||||
|
embedding = model.encode(text)
|
||||||
|
embedding = embedding / np.linalg.norm(embedding)
|
||||||
|
if np.allclose(embedding, 0):
|
||||||
|
return [0.0] * len(embedding)
|
||||||
|
|
||||||
|
# Map to phase space using arctan2
|
||||||
|
phases = np.arctan2(embedding, 1.0) # Maps to [-π, π]
|
||||||
|
return phases.tolist()
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Step 3: Retrieval Functions
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def phase_distance(a: List[float], b: List[float]) -> float:
|
||||||
|
"""
|
||||||
|
Calculate circular distance between phase vectors.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
a: First phase vector
|
||||||
|
b: Second phase vector
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Circular distance (0 to π * dim)
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
a = np.array(a)
|
||||||
|
b = np.array(b)
|
||||||
|
|
||||||
|
diff = np.abs(a - b)
|
||||||
|
return float(np.minimum(diff, 2*np.pi - diff).sum())
|
||||||
|
|
||||||
|
|
||||||
|
def retrieve_signatures(filepath: str = "memory.jsonl", limit: int = 100) -> List[TemporalSignature]:
|
||||||
|
"""
|
||||||
|
Load signatures from JSONL file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filepath: Path to JSONL file
|
||||||
|
limit: Maximum number of signatures to load
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of TemporalSignatures
|
||||||
|
"""
|
||||||
|
if not os.path.exists(filepath):
|
||||||
|
return []
|
||||||
|
|
||||||
|
signatures = []
|
||||||
|
with open(filepath, "r") as f:
|
||||||
|
for line in f:
|
||||||
|
if limit and len(signatures) >= limit:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
data = json.loads(line.strip())
|
||||||
|
signatures.append(TemporalSignature.from_dict(data))
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return signatures
|
||||||
|
|
||||||
|
|
||||||
|
def compute_resonance_score(
|
||||||
|
signature: TemporalSignature,
|
||||||
|
query_phase: List[float],
|
||||||
|
current_coherence: float,
|
||||||
|
now: Optional[datetime] = None
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
Compute resonance score for a signature.
|
||||||
|
|
||||||
|
Score = PhaseSimilarity × Coherence × e^(-λ × delta_t)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
signature: TemporalSignature to score
|
||||||
|
query_phase: Query phase vector
|
||||||
|
current_coherence: Current coherence value
|
||||||
|
now: Optional reference time (defaults to now)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Resonance score (0 to 1)
|
||||||
|
"""
|
||||||
|
from datetime import timezone
|
||||||
|
import math
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
if now is None:
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
# Handle timezone compatibility
|
||||||
|
from datetime import timezone
|
||||||
|
sig_time = signature.created_at
|
||||||
|
if sig_time.tzinfo is None:
|
||||||
|
sig_time = sig_time.replace(tzinfo=timezone.utc)
|
||||||
|
|
||||||
|
if not signature.phase_vector or not query_phase:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
# Phase similarity
|
||||||
|
phase_dist = phase_distance(query_phase, signature.phase_vector)
|
||||||
|
phase_sim = 1.0 / (1.0 + phase_dist)
|
||||||
|
|
||||||
|
# Temporal decay
|
||||||
|
dt = (now - sig_time).total_seconds()
|
||||||
|
decay = math.exp(-signature.decay_rate * dt / 3600) # hours
|
||||||
|
|
||||||
|
# Coherence weight
|
||||||
|
coherence_weight = signature.coherence_value
|
||||||
|
|
||||||
|
# Self-correction: downgrade solaria origins
|
||||||
|
origin_weight = 0.7 if signature.origin == "solaria" else 1.0
|
||||||
|
|
||||||
|
return phase_sim * coherence_weight * decay * origin_weight
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
numpy>=1.24.0
|
numpy>=1.24.0
|
||||||
scipy>=1.10.0
|
scipy>=1.10.0
|
||||||
|
|
||||||
|
# Sentence transformers for phase encoding
|
||||||
|
sentence-transformers>=2.2.0
|
||||||
|
|
||||||
# Async
|
# Async
|
||||||
asyncio>=3.4.3
|
asyncio>=3.4.3
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user