#!/usr/bin/env python3 """ OpenHer × EverOS Integration Demo Demonstrates how EverOS provides long-term memory to the AI Being persona engine. Shows session context loading, memory storage, search, and relationship vector evolution. Usage: # With EverMind Cloud export EVERMEMOS_BASE_URL=https://api.evermind.ai/v1 export EVERMEMOS_API_KEY=your_key python demo/evermemos_demo.py # With self-hosted EverOS export EVERMEMOS_BASE_URL=http://localhost:1995/api/v1 python demo/evermemos_demo.py """ import asyncio import os import sys from datetime import datetime # ────────────────────────────────────────────── # EverOS Client (minimal standalone version) # ────────────────────────────────────────────── try: import httpx except ImportError: print("❌ httpx not installed. Run: pip install httpx") sys.exit(1) class EverOSClient: """Minimal EverOS client for demo purposes.""" def __init__(self, base_url: str, api_key: str = ""): self.base_url = base_url.rstrip("/") self.api_key = api_key self._client = httpx.AsyncClient(timeout=10.0) self.available = bool(base_url) async def _headers(self) -> dict: h = {"Content-Type": "application/json"} if self.api_key: h["Authorization"] = f"Bearer {self.api_key}" return h async def health_check(self) -> bool: """Check if EverOS is reachable.""" try: # Try the health endpoint (remove /api/v1 suffix) health_url = self.base_url.replace("/api/v1", "") + "/health" resp = await self._client.get(health_url, headers=await self._headers()) return resp.status_code == 200 except Exception: return False async def store_turn( self, user_id: str, persona_id: str, persona_name: str, user_name: str, group_id: str, user_message: str, agent_reply: str, ) -> dict: """Store a conversation turn as memory.""" now = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S+00:00") messages = [ { "message_id": f"msg_{hash(user_message) & 0xFFFF:04x}_u", "create_time": now, "sender": user_id, "sender_name": user_name, "content": user_message, }, { "message_id": f"msg_{hash(agent_reply) & 0xFFFF:04x}_a", "create_time": now, "sender": persona_id, "sender_name": persona_name, "content": agent_reply, }, ] resp = await self._client.post( f"{self.base_url}/memories", json={"messages": messages, "group_id": group_id}, headers=await self._headers(), ) return resp.json() if resp.status_code == 200 else {"error": resp.text} async def search( self, query: str, user_id: str, group_id: str, top_k: int = 5, ) -> dict: """Search for relevant memories.""" resp = await self._client.get( f"{self.base_url}/memories/search", params={ "query": query, "user_id": user_id, "group_id": group_id, "top_k": top_k, "retrieve_method": "hybrid", }, headers=await self._headers(), ) return resp.json() if resp.status_code == 200 else {"error": resp.text} async def get_user_profile(self, user_id: str) -> dict: """Get user profile (accumulated from conversations).""" resp = await self._client.get( f"{self.base_url}/users/{user_id}/profile", headers=await self._headers(), ) return resp.json() if resp.status_code == 200 else {} async def close(self): await self._client.aclose() # ────────────────────────────────────────────── # Relationship Vector (from EverOS session) # ────────────────────────────────────────────── def compute_relationship_vector(profile_data: dict) -> dict: """ Extract 4D relationship vector from EverOS profile data. These 4 dimensions expand the persona engine's neural network from 8D to 12D input, allowing it to differentiate behavior between strangers and old friends. """ return { "relationship_depth": min(1.0, profile_data.get("interaction_count", 0) / 50), "emotional_valence": profile_data.get("sentiment_avg", 0.0), "trust_level": min(1.0, profile_data.get("trust_score", 0.0)), "pending_foresight": 1.0 if profile_data.get("foresight") else 0.0, } def apply_relationship_ema( prior: dict, delta: dict, conversation_depth: float, prev_ema: dict | None = None, ) -> dict: """ Semi-emergent relationship update (Step 2.5 of ChatAgent lifecycle). Blends EverOS prior with LLM-judged delta through EMA: - alpha modulated by conversation depth (deeper = trust LLM more) - Clips to valid ranges - Preserves momentum through prev_ema """ if prev_ema is None: prev_ema = dict(prior) alpha = max(0.15, min(0.65, 0.15 + 0.5 * conversation_depth)) ema = {} for k in prior: lo = -1.0 if k == "emotional_valence" else 0.0 posterior = max(lo, min(1.0, prior[k] + delta.get(k, 0.0))) prev = prev_ema.get(k, prior[k]) ema[k] = round(alpha * posterior + (1 - alpha) * prev, 4) return ema # ────────────────────────────────────────────── # Demo # ────────────────────────────────────────────── async def main(): base_url = os.getenv("EVERMEMOS_BASE_URL", "") api_key = os.getenv("EVERMEMOS_API_KEY", "") if not base_url: print("=" * 60) print("OpenHer × EverOS Integration Demo") print("=" * 60) print() print("⚠️ EVERMEMOS_BASE_URL not set.") print() print("To run this demo, set up EverOS:") print() print(" Option A — EverMind Cloud:") print(" export EVERMEMOS_BASE_URL=https://api.evermind.ai/v1") print(" export EVERMEMOS_API_KEY=your_key") print() print(" Option B — Self-hosted:") print(" cd vendor/EverOS && docker compose up -d") print(" uv run python src/run.py") print(" export EVERMEMOS_BASE_URL=http://localhost:1995/api/v1") print() print("Get your API key: https://console.evermind.ai/") print() print("Running in simulation mode...\n") await demo_simulation() return client = EverOSClient(base_url, api_key) print("=" * 60) print("OpenHer × EverOS Integration Demo") print("=" * 60) print(f"\n📡 EverOS: {base_url}") # Health check healthy = await client.health_check() if not healthy: print("❌ EverOS is not reachable. Check your URL and try again.") await client.close() return print("✅ EverOS is healthy\n") # ── Demo conversation ── user_id = "demo_user" persona_id = "luna" persona_name = "Luna (陆暖)" user_name = "Demo User" group_id = f"{persona_id}__{user_id}" conversations = [ ( "My name is Alex, I'm a software engineer", "Nice to meet you Alex! What kind of software do you work on?", ), ( "I love hiking in the mountains on weekends", "That sounds wonderful! There's something about being up high " "that makes everything else feel small.", ), ("I drink my coffee black, no sugar", "Noted! A purist. I respect that."), ] print("📝 Storing conversation memories...\n") for user_msg, agent_reply in conversations: result = await client.store_turn( user_id=user_id, persona_id=persona_id, persona_name=persona_name, user_name=user_name, group_id=group_id, user_message=user_msg, agent_reply=agent_reply, ) status = "✅" if "error" not in result else "❌" print(f' {status} User: "{user_msg[:50]}..."') # Wait for indexing print("\n⏳ Waiting for memory indexing (3s)...") await asyncio.sleep(3) # Search print("\n🔍 Searching for relevant memories...\n") queries = [ "What does Alex like to do on weekends?", "How does Alex take their coffee?", "What is Alex's occupation?", ] for query in queries: result = await client.search( query=query, user_id=user_id, group_id=group_id, ) memories = result.get("result", {}).get("memories", []) print(f' Q: "{query}"') if memories: for mem in memories[:2]: content = str(mem)[:100] print(f" → {content}") else: print(" → (no results yet — indexing may still be in progress)") print() # Relationship vector print("📊 Relationship Vector Evolution:\n") prior = { "relationship_depth": 0.0, "emotional_valence": 0.0, "trust_level": 0.0, "pending_foresight": 0.0, } deltas = [ {"relationship_depth": 0.1, "emotional_valence": 0.2, "trust_level": 0.05}, {"relationship_depth": 0.05, "emotional_valence": 0.1, "trust_level": 0.1}, {"relationship_depth": 0.08, "emotional_valence": 0.15, "trust_level": 0.12}, ] ema = None for i, delta in enumerate(deltas): ema = apply_relationship_ema( prior, delta, conversation_depth=0.2 * (i + 1), prev_ema=ema ) print( f" Turn {i + 1}: depth={ema['relationship_depth']:.3f} " f"valence={ema['emotional_valence']:.3f} " f"trust={ema['trust_level']:.3f}" ) prior = ema print( "\n → After 3 turns: no longer a stranger " f"(depth={ema['relationship_depth']:.3f})" ) print(" → Neural network now produces warmer, more familiar behavioral signals\n") await client.close() print("✅ Demo complete!") async def demo_simulation(): """Run demo in simulation mode (no EverOS connection).""" print("📊 Simulating Relationship Vector Evolution:\n") print(" This shows how the 4D EverOS relationship vector") print(" deepens over multiple conversation turns.\n") prior = { "relationship_depth": 0.0, "emotional_valence": 0.0, "trust_level": 0.0, "pending_foresight": 0.0, } # Simulate 10 turns of conversation simulated_deltas = [ ( 0.3, { "relationship_depth": 0.10, "emotional_valence": 0.15, "trust_level": 0.05, }, ), ( 0.4, { "relationship_depth": 0.08, "emotional_valence": 0.10, "trust_level": 0.08, }, ), ( 0.5, { "relationship_depth": 0.05, "emotional_valence": 0.20, "trust_level": 0.12, }, ), ( 0.6, { "relationship_depth": 0.06, "emotional_valence": -0.10, "trust_level": 0.03, }, ), ( 0.7, { "relationship_depth": 0.04, "emotional_valence": 0.08, "trust_level": 0.10, }, ), ( 0.7, { "relationship_depth": 0.03, "emotional_valence": 0.12, "trust_level": 0.08, }, ), ( 0.8, { "relationship_depth": 0.02, "emotional_valence": 0.05, "trust_level": 0.06, }, ), ( 0.8, { "relationship_depth": 0.03, "emotional_valence": 0.10, "trust_level": 0.05, }, ), ( 0.9, { "relationship_depth": 0.01, "emotional_valence": 0.08, "trust_level": 0.04, }, ), ( 0.9, { "relationship_depth": 0.02, "emotional_valence": 0.06, "trust_level": 0.03, }, ), ] ema = None for i, (depth, delta) in enumerate(simulated_deltas, 1): alpha = max(0.15, min(0.65, 0.15 + 0.5 * depth)) ema = apply_relationship_ema( prior, delta, conversation_depth=depth, prev_ema=ema ) bar_d = "█" * int(ema["relationship_depth"] * 20) bar_v = "█" * int(max(0, ema["emotional_valence"]) * 20) bar_t = "█" * int(ema["trust_level"] * 20) print( f" Turn {i:2d} (α={alpha:.2f}): " f"depth={ema['relationship_depth']:.3f} {bar_d}" ) print(f" valence={ema['emotional_valence']:+.3f} {bar_v}") print(f" trust={ema['trust_level']:.3f} {bar_t}") print() prior = ema print(" ──────────────────────────────────") print( f" Final state: depth={ema['relationship_depth']:.3f}, " f"valence={ema['emotional_valence']:+.3f}, " f"trust={ema['trust_level']:.3f}" ) print() print(" Turn 4 shows a negative emotional event (valence delta = -0.10),") print(" but the EMA smoothing prevents overreaction — the relationship") print(" continues to deepen because trust was already building.") print() print(" This vector feeds into the 25D neural network input,") print(" producing different behavioral signals for strangers vs. friends:") print(" - Higher warmth, vulnerability, and initiative for trusted users") print(" - More guarded, formal signals for new users") print() print("✅ Simulation complete!") if __name__ == "__main__": asyncio.run(main())