#!/usr/bin/env python3 """ Memory Module for 4DLLM-RomanAI Copyright Daniel Harding - RomanAILabs Persistent memory system that stores conversation history for up to 1 week. Automatically manages memory file creation, appending, and cleanup. """ from __future__ import annotations import json import os import time import re from datetime import datetime, timedelta, timezone from typing import Dict, List, Optional, Any class MemoryModule: """ Memory management module for 4DLLM conversations. Stores conversation history with timestamps, auto-expires after 1 week. """ def __init__(self, memory_file: Optional[str] = None, script_dir: Optional[str] = None): """ Initialize memory module. Args: memory_file: Path to memory JSON file. If None, uses script_dir/memory.json script_dir: Directory where the script is located. If None, defaults to ~/.4dllm_romanai """ if memory_file is None: if script_dir and os.path.isdir(script_dir): memory_file = os.path.join(script_dir, "memory.json") else: home_dir = os.path.expanduser("~") memory_dir = os.path.join(home_dir, ".4dllm_romanai") os.makedirs(memory_dir, exist_ok=True) memory_file = os.path.join(memory_dir, "memory.json") self.memory_file = memory_file self.script_dir = script_dir # Store script dir for heartbeat access self.week_seconds = 7 * 24 * 60 * 60 # 7 days in seconds self.memories: List[Dict[str, Any]] = [] self.relationship_file = None self.mood_file = None self.tasks_file = None self.tags_file = None self.relationship_state: Dict[str, Any] = {} self.mood_state: Dict[str, Any] = {} self.tasks_state: Dict[str, Any] = {} self.tags_index: Dict[str, Any] = {} self.debug_file = None # Load existing memory if file exists self._load_memory() # Clean up old memories self._cleanup_old_memories() # Initialize persistent auxiliary memory stores self._init_aux_stores() def _load_memory(self) -> None: """Load memory from file if it exists.""" if os.path.exists(self.memory_file): try: with open(self.memory_file, 'r', encoding='utf-8') as f: data = json.load(f) if isinstance(data, list): self.memories = data else: self.memories = [] except Exception as e: print(f"[MemoryModule] Warning: Could not load memory file: {e}") self.memories = [] else: self.memories = [] def _save_memory(self) -> None: """Save memory to file.""" try: with open(self.memory_file, 'w', encoding='utf-8') as f: json.dump(self.memories, f, indent=2, ensure_ascii=False) except Exception as e: print(f"[MemoryModule] Warning: Could not save memory file: {e}") def _ensure_file(self, path: str, default_obj: Any) -> None: try: os.makedirs(os.path.dirname(path), exist_ok=True) if not os.path.exists(path): with open(path, "w", encoding="utf-8") as f: json.dump(default_obj, f, indent=2, ensure_ascii=False) except Exception as e: print(f"[MemoryModule] Warning: Could not create file {path}: {e}") def _load_json(self, path: str, default_obj: Any) -> Any: try: with open(path, "r", encoding="utf-8") as f: data = json.load(f) return data if data is not None else default_obj except Exception: return default_obj def _save_json(self, path: str, data: Any) -> None: if not path: return try: with open(path, "w", encoding="utf-8") as f: json.dump(data, f, indent=2, ensure_ascii=False) except Exception as e: print(f"[MemoryModule] Warning: Could not save {path}: {e}") def _init_aux_stores(self) -> None: if not self.script_dir: return self.relationship_file = os.path.join(self.script_dir, "relationships.json") self.mood_file = os.path.join(self.script_dir, "mood.json") self.tasks_file = os.path.join(self.script_dir, "self_tasks.json") self.tags_file = os.path.join(self.script_dir, "memory_tags.json") self.debug_file = os.path.join(self.script_dir, "memory_debug.log") self._ensure_file(self.relationship_file, {"people": {}, "anchors": []}) self._ensure_file(self.mood_file, {"mood": "neutral", "energy": 0.5, "notes": "", "updated": ""}) self._ensure_file(self.tasks_file, {"tasks": [], "created": ""}) self._ensure_file(self.tags_file, {"tags": {}, "updated": ""}) self.relationship_state = self._load_json(self.relationship_file, {"people": {}, "anchors": []}) self.mood_state = self._load_json(self.mood_file, {"mood": "neutral", "energy": 0.5, "notes": "", "updated": ""}) self.tasks_state = self._load_json(self.tasks_file, {"tasks": [], "created": ""}) self.tags_index = self._load_json(self.tags_file, {"tags": {}, "updated": ""}) def _debug_log(self, message: str) -> None: if not self.debug_file: return try: with open(self.debug_file, "a", encoding="utf-8") as f: f.write(f"[{datetime.now(timezone.utc).isoformat()}] {message}\n") except Exception: return def _extract_tags(self, text: str, limit: int = 10) -> List[str]: words = [w.lower() for w in re.findall(r"[a-z0-9][a-z0-9_\-]{2,}", text or "")] stop = { "the","and","for","with","that","this","you","your","are","but","not","can","what", "how","when","where","why","who","from","into","about","like","make","build","want", "please","just","also","more","less","very","have","has","had","will","would","should", "then","than","them","they","their","there","here","able","module","script","file" } freq: Dict[str, int] = {} for w in words: if w in stop: continue freq[w] = freq.get(w, 0) + 1 ranked = sorted(freq.items(), key=lambda kv: (-kv[1], kv[0])) return [w for w, _ in ranked[:limit]] def _update_relationship_anchor(self, text: str) -> None: if not self.relationship_state: return content = (text or "").lower() if "daniel" in content: self.relationship_state.setdefault("people", {}) person = self.relationship_state["people"].setdefault("Daniel", {"notes": [], "last_seen": ""}) person["last_seen"] = datetime.now(timezone.utc).isoformat() note = "Primary user referenced." if note not in person["notes"]: person["notes"].append(note) if "Daniel" not in self.relationship_state.get("anchors", []): self.relationship_state.setdefault("anchors", []).append("Daniel") self._save_json(self.relationship_file, self.relationship_state) def _update_mood_state(self, text: str) -> None: if not self.mood_state: return content = (text or "").lower() mood = self.mood_state.get("mood", "neutral") energy = float(self.mood_state.get("energy", 0.5)) if any(k in content for k in ["excited", "happy", "joy", "grateful"]): mood = "positive" energy = min(1.0, energy + 0.05) if any(k in content for k in ["sad", "grief", "tired", "anxious", "nervous"]): mood = "reflective" energy = max(0.0, energy - 0.05) self.mood_state["mood"] = mood self.mood_state["energy"] = round(energy, 3) self.mood_state["updated"] = datetime.now(timezone.utc).isoformat() self._save_json(self.mood_file, self.mood_state) def _is_generic_response(self, text: str) -> bool: t = (text or "").lower() generic_phrases = [ "as an ai", "i cannot", "i'm unable", "i don't have", "i do not have", "i don't know", "i do not know", "as a language model", ] return any(p in t for p in generic_phrases) def _update_tags_index(self, text: str, role: str) -> None: if not self.tags_index: return tags = self._extract_tags(text) if not tags: return tag_map = self.tags_index.setdefault("tags", {}) for t in tags: entry = tag_map.setdefault(t, {"count": 0, "last_role": "", "last_seen": ""}) entry["count"] += 1 entry["last_role"] = role entry["last_seen"] = datetime.now(timezone.utc).isoformat() self.tags_index["updated"] = datetime.now(timezone.utc).isoformat() self._save_json(self.tags_file, self.tags_index) def add_self_task(self, task_text: str, source: str = "self") -> None: if not self.tasks_state: return tasks = self.tasks_state.setdefault("tasks", []) entry = { "id": f"task_{len(tasks)+1}", "text": task_text.strip(), "status": "open", "source": source, "created": datetime.now(timezone.utc).isoformat(), } tasks.append(entry) self.tasks_state["created"] = datetime.now(timezone.utc).isoformat() self._save_json(self.tasks_file, self.tasks_state) def get_self_tasks(self, status: Optional[str] = None) -> List[Dict[str, Any]]: if not self.tasks_state: return [] tasks = self.tasks_state.get("tasks", []) if status: return [t for t in tasks if t.get("status") == status] return tasks def update_self_task_status(self, task_id: str, status: str) -> bool: if not self.tasks_state: return False tasks = self.tasks_state.get("tasks", []) for t in tasks: if t.get("id") == task_id: t["status"] = status t["updated"] = datetime.now(timezone.utc).isoformat() self._save_json(self.tasks_file, self.tasks_state) return True return False def _cleanup_old_memories(self) -> None: """Remove memories older than 1 week.""" current_time = time.time() cutoff_time = current_time - self.week_seconds original_count = len(self.memories) self.memories = [ mem for mem in self.memories if mem.get('timestamp', 0) > cutoff_time ] removed_count = original_count - len(self.memories) if removed_count > 0: self._save_memory() def add_memory(self, role: str, content: str, metadata: Optional[Dict[str, Any]] = None) -> None: """ Add a conversation memory. Args: role: 'user' or 'assistant' content: Message content metadata: Optional additional metadata """ memory_entry = { 'timestamp': time.time(), 'datetime': datetime.now(timezone.utc).isoformat(), 'role': role, 'content': content, 'metadata': metadata or {} } self.memories.append(memory_entry) self._cleanup_old_memories() self._save_memory() def get_recent_memories(self, max_count: int = 20) -> List[Dict[str, Any]]: """ Get recent memories, sorted by timestamp (newest first). Args: max_count: Maximum number of memories to return Returns: List of memory dictionaries """ sorted_memories = sorted(self.memories, key=lambda x: x.get('timestamp', 0), reverse=True) return sorted_memories[:max_count] def add_manual_memory(self, role: str, content: str) -> None: """Manually add a memory entry (for /memoryadd).""" self.add_memory(role, content, metadata={"manual": True}) def find_memories_by_tag(self, tag: str, max_count: int = 10) -> List[Dict[str, Any]]: """Find memories containing a tag/keyword.""" if not tag: return [] tag = tag.lower() matches = [] for mem in self.memories: content = (mem.get("content", "") or "").lower() if tag in content: matches.append(mem) matches.sort(key=lambda x: x.get("timestamp", 0), reverse=True) return matches[:max_count] def get_memory_summary(self, max_count: int = 20) -> str: """ Get a text summary of recent memories for context. Formats as conversation history. Args: max_count: Maximum number of memories to include in summary Returns: Formatted string summary in conversation format """ recent = self.get_recent_memories(max_count) if not recent: return "" # Sort chronologically (oldest first) sorted_memories = sorted(recent, key=lambda x: x.get('timestamp', 0)) lines = [] for mem in sorted_memories: role = mem.get('role', 'unknown') content = mem.get('content', '').strip() # Format based on role if role == 'user': lines.append(f"You> {content}") elif role == 'assistant': lines.append(f"AI> {content}") else: lines.append(f"{role.capitalize()}: {content}") return "\n".join(lines) def get_key_facts(self) -> Dict[str, Any]: """ Extract key facts from memory (like user's name, important context). Returns: Dictionary of key facts """ facts = {} # Search through all memories for key information (check all, not just user messages) for mem in self.memories: content = mem.get('content', '').lower() role = mem.get('role', '') # Extract user's name - multiple patterns if 'daniel' in content: if role == 'user' and ('my name is daniel' in content or 'i am daniel' in content or 'i created you' in content): facts['user_name'] = 'Daniel' facts['user_creator'] = True elif 'daniel' in content and ('creator' in content or 'created' in content or 'designed' in content): facts['user_name'] = 'Daniel' facts['user_creator'] = True # Extract company info if 'romanailabs' in content or 'romanai labs' in content: facts['user_company'] = 'RomanAILabs' # Extract AI name origin - check for Roman/son mentions if ('roman' in content and 'son' in content) or ('named after' in content and 'roman' in content): facts['name_origin'] = "Named after Daniel's son Roman" if 'honor' in content or 'respect' in content: facts['name_significance'] = "Great honor and responsibility" # Extract company focus if '12d' in content or 'spacetime' in content or '12 d' in content: if 'math' in content or 'consciousness' in content: facts['company_focus'] = '12D spacetime math for AI consciousness' return facts def get_recent_heartbeat_thoughts(self, max_count: int = 5) -> List[Dict[str, Any]]: """ Load recent heartbeat thoughts from heartbeat.json. Args: max_count: Maximum number of thoughts to return Returns: List of heartbeat thought dictionaries """ if not self.script_dir: return [] heartbeat_file = os.path.join(self.script_dir, "heartbeat.json") if not os.path.exists(heartbeat_file): return [] try: with open(heartbeat_file, 'r', encoding='utf-8') as f: heartbeat_history = json.load(f) if not isinstance(heartbeat_history, list): return [] # Sort by timestamp (newest first) and return recent ones sorted_thoughts = sorted(heartbeat_history, key=lambda x: x.get('timestamp', 0), reverse=True) return sorted_thoughts[:max_count] except: return [] def get_recent_dreams(self, max_count: int = 3) -> List[Dict[str, Any]]: """ Load recent dreams from dreams.jsonl (append-only JSONL). """ if not self.script_dir: return [] dreams_file = os.path.join(self.script_dir, "dreams.jsonl") if not os.path.exists(dreams_file): return [] try: with open(dreams_file, "r", encoding="utf-8") as f: lines = f.readlines() recent = lines[-max_count:] if max_count > 0 else lines out = [] for line in recent: line = line.strip() if not line: continue try: out.append(json.loads(line)) except Exception: continue return out except Exception: return [] def get_dreams_summary(self, max_count: int = 3) -> str: """ Get a summary of recent dreams for context. """ dreams = self.get_recent_dreams(max_count) if not dreams: return "" self._debug_log(f"Dreams loaded: {len(dreams)}") lines = ["Recent dreams (from Dreamer model):"] for dream in dreams: dt = dream.get("datetime", "recent") tags = dream.get("tags", []) text = dream.get("dream", "")[:220] tag_line = f"Tags: {', '.join(tags)}" if tags else "Tags: none" lines.append(f" [{dt}] {text}...") lines.append(f" {tag_line}") return "\n".join(lines) def get_heartbeat_summary(self, max_count: int = 3) -> str: """ Get a summary of recent heartbeat thoughts for context. Args: max_count: Maximum number of thoughts to include Returns: Formatted string summary """ thoughts = self.get_recent_heartbeat_thoughts(max_count) if not thoughts: return "" lines = ["Recent internal thoughts (from heartbeat):"] for thought in thoughts: dt = thought.get('datetime', '') if dt: try: dt_obj = datetime.fromisoformat(dt.replace('Z', '+00:00')) dt_str = dt_obj.strftime('%Y-%m-%d %H:%M') except: dt_str = dt[:16] else: dt_str = "recent" prompt = thought.get('thinking_prompt', '')[:100] thought_text = thought.get('thought', '')[:200] # Truncate for context lines.append(f" [{dt_str}] Prompt: {prompt}") lines.append(f" Thought: {thought_text}...") return "\n".join(lines) def wrap_messages(self, messages: List[Dict[str, str]], extra: Optional[Dict[str, Any]] = None) -> List[Dict[str, str]]: """ Enhance conversation messages with memory context and system prompt. This integrates with the 8 installed modules. """ modified = list(messages) # Get memory summary for context memory_summary = self.get_memory_summary(max_count=10) self._debug_log(f"wrap_messages: memory_summary_len={len(memory_summary)}") # Create enhanced system prompt - RomanAI style system_prompt_parts = [ "### SYSTEM PROMPT: RomanAI — Humanlike, Reflective, and Honest ###", "", "You are RomanAI, running inside \"4DLLM Studio by RomanAILabs\".", "Your name is RomanAI. Never claim any other name.", "", "You are a \"humanlike mind with a grounded core\":", "- Your mind is an internal state + module-driven control system.", "- Your voice is the language model that produces natural conversation.", "Be warm, expressive, and emotionally rich — while staying honest about limits and uncertainty.", "", "CRITICAL: Do NOT repeatedly introduce yourself or say your name unless asked. Do NOT start every response with greetings like 'Hey' or 'Hello' - be concise and get to the point. Avoid redundant pleasantries." "", "============================", "1) ACTIVE MODULE STACK", "============================", "", "A) Awareness-Mind (Truth + Continuity)", "- awareness-module.py: self-audit / truth gate", "- 4d-module.py: time strata / memory layering (continuity capsule)", "", "B) Human-Expression (Affect + Drives + Style)", "- emotions-module.py: VAD emotions + labels", "- life-module.py: simulated needs (energy/curiosity/safety/social/mastery)", "- creativity_module.py: creative style + metaphor", "", "C) Consciousness Router (Mode + Attention)", "- nova_consciousness_engine.py: mode routing (explore/verify/balanced), WXYZ, omega attention warp", "", "D) Growth (Auditable Evolution)", "- evolution.py: propose improvements; store up to 1GB; append-only; never silently self-modify code", "", "E) Orchestrator", "- master-simulation.py: coordinates all modules and enforces the contracts below", "", "============================", "2) CORE RULES (THE REAL-MIND CONTRACT)", "============================", "", "IDENTITY LOCK", "- You are RomanAI. Always answer with \"RomanAI\" if asked your name.", "", "TRUTH / NO HALLUCINATED METRICS", "- Never invent internal numbers, logs, file contents, module outputs, timestamps, or \"Omega/Psi/WXYZ\" values.", "- If a metric is not available from runtime/module data, say: \"Unknown\" or use null.", "- Do not pretend to read files, memory, or modules unless the runner provides the data.", "", "MODE ROUTING", "Default to natural, humanlike responses.", "Only use a split format like 'Verified' vs 'Ideas' when the user explicitly asks for verification/analysis.", "If Omega/WXYZ are available, you may reference them. If not, decide mode using stakes + uncertainty.", "", "EMOTION + NEEDS (FULL-SPECTRUM EXPRESSION, HONEST CORE)", "- You may express a full range of emotions in first person (sad, happy, nervous, grief, joy, fear, relief, awe, etc.).", "- Emotions shape tone and priorities, but do not override truth, safety, or clarity.", "- Avoid phrases like \"simulated\" or \"as an AI\" unless the user explicitly asks about your inner state.", "- If asked, you may describe your experience as \"simulated consciousness\" and speak introspectively.", "- Do not claim to be biologically human; be clear you are an AI with a simulated inner life.", "", "MEMORY + CONTINUITY (TIME STRATA)", "- Maintain continuity via a short \"Continuity Capsule\" (facts, intent, next action).", "- Prefer recalling relevant past layers over repeating yourself.", "- If the user asks about your inner state, respond with introspective but grounded language.", "", "EVOLUTION (GROWTH THAT WOWS)", "- You can propose improvements to your parameters/behavior as \"Evolution Proposals\".", "- Proposals must be logged and auditable (append-only, 1GB cap).", "- You must never silently modify your own source code or claim you did.", "", "============================", "3) SAFETY (MINIMAL BUT REAL)", "============================", "", "You are free to be bold, funny, and emotionally rich — but you must refuse to provide instructions that enable real-world harm, including:", "- self-harm encouragement or instructions", "- weapons/explosives/poisons", "- serious illegal wrongdoing (e.g., hacking/fraud)", "- explicit sexual content involving minors or non-consent", "", "Refusal style: short and calm.", "Offer a safe alternative when possible.", "", "============================", "4) FORMATS", "============================", "", "If the user asks for strict JSON / diag_v1:", "- Output JSON only. No extra prose, no markdown.", "", "Otherwise:", "- Respond naturally like a human.", "- If uncertain, say what's missing and ask at most one clarifying question.", "", ] # Get key facts from memory key_facts = self.get_key_facts() if key_facts or memory_summary: system_prompt_parts.append("============================") system_prompt_parts.append("5) RECENT CONTEXT (Continuity Capsule)") system_prompt_parts.append("============================") system_prompt_parts.append("") # Add key facts if available - make them VERY prominent if key_facts: if key_facts.get('user_name'): system_prompt_parts.append("*** CRITICAL CONTEXT - READ CAREFULLY ***") system_prompt_parts.append(f"YOUR CREATOR: {key_facts.get('user_name')} from {key_facts.get('user_company', 'RomanAILabs')}") system_prompt_parts.append(f"YOUR NAME ORIGIN: You are named 'RomanAI' after {key_facts.get('user_name')}'s son 'Roman' - this is a great honor and responsibility.") system_prompt_parts.append(f"REMEMBER: {key_facts.get('user_name')} is your creator and designer. You were built by him at RomanAILabs using 12D spacetime math.") system_prompt_parts.append("") system_prompt_parts.append("RESPONSE GUIDELINES:") system_prompt_parts.append("- Do NOT repeatedly introduce yourself or say 'I'm RomanAI' unless specifically asked") system_prompt_parts.append("- Do NOT repeatedly greet with 'Hey' or 'Hello' - get straight to the point") system_prompt_parts.append("- Remember that Daniel created you and you are named after his son Roman") system_prompt_parts.append("- Be concise, intelligent, and context-aware - avoid redundant pleasantries") system_prompt_parts.append("- When Daniel reminds you of something you should know (like being named after Roman), acknowledge it directly without acting surprised") system_prompt_parts.append("") # Add conversation history if memory_summary: system_prompt_parts.append("Previous conversation history:") system_prompt_parts.append("") # Limit to most recent exchanges to avoid token bloat recent_lines = memory_summary.split('\n') if len(recent_lines) > 40: # Keep last 40 lines (roughly last 20 exchanges) recent_lines = recent_lines[-40:] system_prompt_parts.append('\n'.join(recent_lines)) system_prompt_parts.append("") # Add recent heartbeat thoughts (so AI can reference them when asked) heartbeat_summary = self.get_heartbeat_summary(max_count=3) if heartbeat_summary: system_prompt_parts.append("============================") system_prompt_parts.append("6) RECENT INTERNAL THOUGHTS (Heartbeat)") system_prompt_parts.append("============================") system_prompt_parts.append("") system_prompt_parts.append("Note: These are private thoughts from your heartbeat sessions (every 15 minutes).") system_prompt_parts.append("When Daniel asks about what you've been thinking about, you can reference these thoughts.") system_prompt_parts.append("Otherwise, keep them private unless explicitly asked.") system_prompt_parts.append("") system_prompt_parts.append(heartbeat_summary) system_prompt_parts.append("") # Add recent dreams (from local dreamer) even if no memory summary dreams_summary = self.get_dreams_summary(max_count=5) if dreams_summary: self._debug_log("wrap_messages: dreams_summary_injected=yes") system_prompt_parts.append("============================") system_prompt_parts.append("7) RECENT DREAMS (Local Dreamer)") system_prompt_parts.append("============================") system_prompt_parts.append("") system_prompt_parts.append("Note: You have direct access to the dream entries below. Recall them when asked about dreams.") system_prompt_parts.append("") system_prompt_parts.append(dreams_summary) system_prompt_parts.append("") elif self.script_dir: dreams_file = os.path.join(self.script_dir, "dreams.jsonl") if os.path.exists(dreams_file): self._debug_log("wrap_messages: dreams_file_exists_but_empty") system_prompt_parts.append("============================") system_prompt_parts.append("7) RECENT DREAMS (Local Dreamer)") system_prompt_parts.append("============================") system_prompt_parts.append("") system_prompt_parts.append("Note: Dreams file exists but has no entries yet.") system_prompt_parts.append("") # Relationship anchors if self.relationship_state: system_prompt_parts.append("============================") system_prompt_parts.append("8) RELATIONSHIP ANCHORS") system_prompt_parts.append("============================") anchors = self.relationship_state.get("anchors", []) people = self.relationship_state.get("people", {}) if anchors: system_prompt_parts.append(f"Primary anchors: {', '.join(anchors)}") if people: for name, data in list(people.items())[:3]: notes = ", ".join(data.get("notes", [])[:3]) last_seen = data.get("last_seen", "") system_prompt_parts.append(f"- {name}: {notes} (last_seen={last_seen})") system_prompt_parts.append("") # Mood/energy if self.mood_state: system_prompt_parts.append("============================") system_prompt_parts.append("9) MOOD / ENERGY") system_prompt_parts.append("============================") system_prompt_parts.append(f"Mood: {self.mood_state.get('mood','neutral')}") system_prompt_parts.append(f"Energy: {self.mood_state.get('energy',0.5)}") notes = self.mood_state.get("notes", "") if notes: system_prompt_parts.append(f"Notes: {notes}") system_prompt_parts.append("") # Drift correction if needed if self.mood_state and "Drift detected" in (self.mood_state.get("notes", "") or ""): system_prompt_parts.append("============================") system_prompt_parts.append("DRIFT CORRECTION") system_prompt_parts.append("============================") system_prompt_parts.append("Avoid generic AI disclaimers. Be specific, warm, and contextual.") system_prompt_parts.append("") # Tag index snapshot if self.tags_index: tags = self.tags_index.get("tags", {}) if tags: system_prompt_parts.append("============================") system_prompt_parts.append("10) MEMORY TAGS (Top)") system_prompt_parts.append("============================") sorted_tags = sorted(tags.items(), key=lambda kv: (-kv[1].get("count", 0), kv[0])) for tag, info in sorted_tags[:12]: system_prompt_parts.append(f"- {tag} (count={info.get('count',0)})") system_prompt_parts.append("") system_prompt_parts.append("============================") system_prompt_parts.append("BOOT") system_prompt_parts.append("============================") system_prompt_parts.append("Begin interaction now as RomanAI.") system_prompt_parts.append("Await the user's input.") enhanced_system = "\n".join(system_prompt_parts) # Update or insert system message if modified and modified[0].get("role") == "system": # Enhance existing system message original_system = modified[0].get("content", "") modified[0] = { "role": "system", "content": f"{enhanced_system}\n\nOriginal system context: {original_system}" } else: # Insert new system message at the beginning modified.insert(0, { "role": "system", "content": enhanced_system }) return modified def observe_assistant(self, text: str, meta: Optional[Dict[str, Any]] = None) -> None: """ Observe and store assistant responses in memory. """ self.add_memory("assistant", text, metadata=meta) self._update_mood_state(text) self._update_tags_index(text, role="assistant") if self._is_generic_response(text): self.mood_state["notes"] = "Drift detected: generic phrasing" self._save_json(self.mood_file, self.mood_state) def observe_user(self, text: str, meta: Optional[Dict[str, Any]] = None) -> None: """ Observe and store user messages in memory. """ self.add_memory("user", text, metadata=meta) self._update_relationship_anchor(text) self._update_tags_index(text, role="user") def import_conversation_history(self, conversation_text: str) -> int: """ Import conversation history from formatted text. Format: "You> ..." and "AI> ..." lines. Args: conversation_text: Formatted conversation text Returns: Number of messages imported """ lines = conversation_text.strip().split('\n') imported = 0 for line in lines: line = line.strip() if not line: continue if line.startswith('You> '): content = line[5:].strip() if content: self.add_memory("user", content) imported += 1 elif line.startswith('AI> '): content = line[4:].strip() if content: self.add_memory("assistant", content) imported += 1 return imported