Spaces:
Running
Running
File size: 6,456 Bytes
03e5649 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | """
backend/api/global_state_sync.py β Global State Sync Layer (S766-GRID-2)
Sincronizzazione della memoria e dello stato tra i profili A, B, C, D.
Permette a ogni daemon di leggere la memoria degli altri profili per evitare
duplicazioni e per mantenere una visione coerente dello stato globale.
Architettura:
- Supabase Federation: Legge da tutti i database (A, B, C, D) e unifica i risultati
- Memory Merge: Combina i risultati mantenendo la coerenza
- Conflict Resolution: In caso di conflitto, usa timestamp e versione per decidere
"""
import os
import asyncio
import logging
from typing import Optional, Dict, List, Any
from datetime import datetime, timedelta
import json
_logger = logging.getLogger("global_state_sync")
# ββ Configurazione βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SUPABASE_URLS = {
"A": os.getenv("SUPABASE_URL", ""),
"B": os.getenv("SUPABASE_URL_B", ""),
"C": os.getenv("SUPABASE_URL_C", ""),
"D": os.getenv("SUPABASE_URL_D", ""),
}
SUPABASE_KEYS = {
"A": os.getenv("SUPABASE_KEY", ""),
"B": os.getenv("SUPABASE_KEY_B", ""),
"C": os.getenv("SUPABASE_KEY_C", ""),
"D": os.getenv("SUPABASE_KEY_D", ""),
}
GLOBAL_STATE_SYNC_ENABLED = os.getenv("GLOBAL_STATE_SYNC_ENABLED", "true").lower() == "true"
class SupabaseClient:
"""Client per accedere a un singolo database Supabase."""
def __init__(self, url: str, key: str, profile: str):
self.url = url
self.key = key
self.profile = profile
self.base_url = f"{url}/rest/v1"
async def query(self, table: str, filters: Optional[Dict] = None) -> List[Dict]:
"""
Esegue una query su una tabella.
Esempio: query("agent_memory", {"session_id": "xyz"})
"""
import httpx
url = f"{self.base_url}/{table}"
headers = {
"apikey": self.key,
"Authorization": f"Bearer {self.key}",
"Content-Type": "application/json",
}
try:
async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers, timeout=10.0)
if response.status_code == 200:
return response.json()
else:
_logger.warning(f"Supabase {self.profile} query failed: {response.status_code}")
return []
except Exception as exc:
_logger.error(f"Supabase {self.profile} error: {exc}")
return []
class GlobalStateSync:
"""Sincronizzazione dello stato globale tra i profili."""
def __init__(self):
self.clients = {}
self._enabled = GLOBAL_STATE_SYNC_ENABLED
for profile, url in SUPABASE_URLS.items():
key = SUPABASE_KEYS.get(profile, "")
if url and key:
self.clients[profile] = SupabaseClient(url, key, profile)
async def get_unified_memory(self, session_id: str) -> Dict[str, Any]:
"""
Recupera la memoria unificata per una sessione da tutti i profili.
Combina i risultati e risolve i conflitti.
"""
if not self._enabled or not self.clients:
return {}
tasks = []
for profile, client in self.clients.items():
tasks.append(
self._fetch_profile_memory(client, session_id)
)
results = await asyncio.gather(*tasks, return_exceptions=True)
# Unifica i risultati
unified = {}
for profile, result in zip(self.clients.keys(), results):
if isinstance(result, dict):
unified[profile] = result
return self._merge_memories(unified)
async def _fetch_profile_memory(self, client: SupabaseClient, session_id: str) -> Dict:
"""Recupera la memoria da un singolo profilo."""
try:
rows = await client.query(
"agent_memory",
{"session_id": session_id}
)
if rows:
return {
"profile": client.profile,
"data": rows[0], # Prendi il primo risultato
"fetched_at": datetime.now().isoformat(),
}
return {}
except Exception as exc:
_logger.error(f"Error fetching memory from {client.profile}: {exc}")
return {}
def _merge_memories(self, unified: Dict[str, Dict]) -> Dict[str, Any]:
"""
Unisce le memorie da piΓΉ profili.
Regole di conflitto:
- Se un campo ha timestamp piΓΉ recente, usa quello
- Se Γ¨ un array, unisci senza duplicati
- Se Γ¨ un oggetto, fai merge ricorsivo
"""
merged = {
"profiles": list(unified.keys()),
"merged_at": datetime.now().isoformat(),
"data": {},
}
if not unified:
return merged
# Estrai i dati da tutti i profili
all_data = {}
for profile, result in unified.items():
if result and "data" in result:
all_data[profile] = result["data"]
# Merge semplice: prioritΓ al profilo A, poi B, C, D
for profile in ["A", "B", "C", "D"]:
if profile in all_data:
merged["data"].update(all_data[profile])
return merged
async def get_health_status(self) -> Dict[str, str]:
"""Restituisce lo stato di connettivitΓ di tutti i profili Supabase."""
status = {}
for profile, client in self.clients.items():
try:
rows = await client.query("agent_memory", {})
status[profile] = "online" if rows is not None else "offline"
except Exception:
status[profile] = "offline"
return status
# ββ Singleton globale ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
_global_state_sync_instance: Optional[GlobalStateSync] = None
def get_global_state_sync() -> GlobalStateSync:
"""Restituisce l'istanza globale del GlobalStateSync."""
global _global_state_sync_instance
if _global_state_sync_instance is None:
_global_state_sync_instance = GlobalStateSync()
return _global_state_sync_instance
|