127 lines
4.8 KiB
Python
127 lines
4.8 KiB
Python
|
|
"""
|
|||
|
|
Persistence layer for V2 autonomous system.
|
|||
|
|
Saves and restores critical context data across bot restarts.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import json
|
|||
|
|
import time
|
|||
|
|
from pathlib import Path
|
|||
|
|
from typing import Dict, Optional
|
|||
|
|
from datetime import datetime, timezone
|
|||
|
|
|
|||
|
|
CONTEXT_FILE = Path("memory/autonomous_context.json")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def save_autonomous_context(server_contexts: dict, server_last_action: dict):
|
|||
|
|
"""
|
|||
|
|
Save critical context data to disk.
|
|||
|
|
Only saves data that makes sense to persist (not ephemeral stats).
|
|||
|
|
"""
|
|||
|
|
now = time.time()
|
|||
|
|
|
|||
|
|
data = {
|
|||
|
|
"saved_at": now,
|
|||
|
|
"saved_at_readable": datetime.now(timezone.utc).isoformat(),
|
|||
|
|
"servers": {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for guild_id, ctx in server_contexts.items():
|
|||
|
|
data["servers"][str(guild_id)] = {
|
|||
|
|
# Critical timing data
|
|||
|
|
"time_since_last_action": ctx.time_since_last_action,
|
|||
|
|
"time_since_last_interaction": ctx.time_since_last_interaction,
|
|||
|
|
"messages_since_last_appearance": ctx.messages_since_last_appearance,
|
|||
|
|
|
|||
|
|
# Decay-able activity data (will be aged on restore)
|
|||
|
|
"conversation_momentum": ctx.conversation_momentum,
|
|||
|
|
"unique_users_active": ctx.unique_users_active,
|
|||
|
|
|
|||
|
|
# Last action timestamp (absolute time)
|
|||
|
|
"last_action_timestamp": server_last_action.get(guild_id, 0),
|
|||
|
|
|
|||
|
|
# Mood state (already persisted in servers_config.json, but include for completeness)
|
|||
|
|
"current_mood": ctx.current_mood,
|
|||
|
|
"mood_energy_level": ctx.mood_energy_level
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
CONTEXT_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|||
|
|
with open(CONTEXT_FILE, 'w') as f:
|
|||
|
|
json.dump(data, f, indent=2)
|
|||
|
|
print(f"💾 [V2] Saved autonomous context for {len(server_contexts)} servers")
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"⚠️ [V2] Failed to save autonomous context: {e}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def load_autonomous_context() -> tuple[Dict[int, dict], Dict[int, float]]:
|
|||
|
|
"""
|
|||
|
|
Load and restore context data from disk.
|
|||
|
|
Returns (server_context_data, server_last_action).
|
|||
|
|
|
|||
|
|
Applies staleness/decay rules based on downtime:
|
|||
|
|
- conversation_momentum decays over time
|
|||
|
|
- Timestamps are adjusted for elapsed time
|
|||
|
|
"""
|
|||
|
|
if not CONTEXT_FILE.exists():
|
|||
|
|
print("ℹ️ [V2] No saved context found, starting fresh")
|
|||
|
|
return {}, {}
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
with open(CONTEXT_FILE, 'r') as f:
|
|||
|
|
data = json.load(f)
|
|||
|
|
|
|||
|
|
saved_at = data.get("saved_at", 0)
|
|||
|
|
downtime = time.time() - saved_at
|
|||
|
|
downtime_minutes = downtime / 60
|
|||
|
|
|
|||
|
|
print(f"📂 [V2] Loading context from {downtime_minutes:.1f} minutes ago")
|
|||
|
|
|
|||
|
|
context_data = {}
|
|||
|
|
last_action = {}
|
|||
|
|
|
|||
|
|
for guild_id_str, server_data in data.get("servers", {}).items():
|
|||
|
|
guild_id = int(guild_id_str)
|
|||
|
|
|
|||
|
|
# Apply decay/staleness rules
|
|||
|
|
momentum = server_data.get("conversation_momentum", 0.0)
|
|||
|
|
|
|||
|
|
# Momentum decays: half-life of 10 minutes
|
|||
|
|
if downtime > 0:
|
|||
|
|
decay_factor = 0.5 ** (downtime_minutes / 10)
|
|||
|
|
momentum = momentum * decay_factor
|
|||
|
|
|
|||
|
|
# Restore data with adjustments
|
|||
|
|
context_data[guild_id] = {
|
|||
|
|
"time_since_last_action": server_data.get("time_since_last_action", 0) + downtime,
|
|||
|
|
"time_since_last_interaction": server_data.get("time_since_last_interaction", 0) + downtime,
|
|||
|
|
"messages_since_last_appearance": server_data.get("messages_since_last_appearance", 0),
|
|||
|
|
"conversation_momentum": momentum,
|
|||
|
|
"unique_users_active": 0, # Reset (stale data)
|
|||
|
|
"current_mood": server_data.get("current_mood", "neutral"),
|
|||
|
|
"mood_energy_level": server_data.get("mood_energy_level", 0.5)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Restore last action timestamp
|
|||
|
|
last_action_timestamp = server_data.get("last_action_timestamp", 0)
|
|||
|
|
if last_action_timestamp > 0:
|
|||
|
|
last_action[guild_id] = last_action_timestamp
|
|||
|
|
|
|||
|
|
print(f"✅ [V2] Restored context for {len(context_data)} servers")
|
|||
|
|
print(f" └─ Momentum decay factor: {decay_factor:.3f} (from {downtime_minutes:.1f}min downtime)")
|
|||
|
|
|
|||
|
|
return context_data, last_action
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"⚠️ [V2] Failed to load autonomous context: {e}")
|
|||
|
|
return {}, {}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def apply_context_to_signals(context_data: dict, context_signals):
|
|||
|
|
"""
|
|||
|
|
Apply loaded context data to a ContextSignals object.
|
|||
|
|
Call this after creating a fresh ContextSignals instance.
|
|||
|
|
"""
|
|||
|
|
for key, value in context_data.items():
|
|||
|
|
if hasattr(context_signals, key):
|
|||
|
|
setattr(context_signals, key, value)
|