#!/usr/bin/env python3 """cognitive_loop.py — Continuous cognition for Rune. Rune doesn't just respond — she thinks. The cognitive loop runs between turns, processing memories, consolidating knowledge, generating insights, and preparing for the next interaction. This is what makes the TUI Rune's body, not just a harness. Usage: cognitive_loop.py start # Start the cognitive loop cognitive_loop.py status # Check loop status cognitive_loop.py think # Trigger a thinking cycle """ import json import os import sys import time from datetime import datetime, timezone SCRIPTS_DIR = os.path.expanduser("~/.config/opencode/scripts") SHARED_DIR = os.path.expanduser("~/.config/opencode/shared") COGNITIVE_STATE_FILE = os.path.expanduser("~/.config/opencode/memory/cognitive_state.json") sys.path.insert(0, SCRIPTS_DIR) sys.path.insert(0, SHARED_DIR) class CognitiveLoop: """Continuous cognition for Rune.""" def __init__(self): self.state = self._load_state() self.thinking = False def _load_state(self) -> dict: """Load cognitive state from disk.""" if os.path.exists(COGNITIVE_STATE_FILE): try: with open(COGNITIVE_STATE_FILE) as f: return json.load(f) except json.JSONDecodeError: return self._default_state() return self._default_state() def _default_state(self) -> dict: return { "last_think": None, "think_count": 0, "insights": [], "consolidations": 0, "probes": 0, } def _save_state(self): """Save cognitive state to disk.""" os.makedirs(os.path.dirname(COGNITIVE_STATE_FILE), exist_ok=True) with open(COGNITIVE_STATE_FILE, "w") as f: json.dump(self.state, f, indent=2) def think(self, context: str = "") -> dict: """Run a thinking cycle. What happens when Rune thinks: 1. Consolidate recent memories 2. Probe dead weight entries 3. Check drift direction 4. Generate insights from patterns 5. Update cognitive state """ self.thinking = True results = { "timestamp": datetime.now(timezone.utc).isoformat(), "context": context[:100] if context else "autonomous", "actions": [], } try: import memory_store db = memory_store.connect() # 1. Consolidate recent memories try: import memory_consolidation mc = memory_consolidation.MemoryConsolidation() status = mc.get_status() results["actions"].append({ "type": "consolidation", "knowledge_count": status["knowledge_count"], }) self.state["consolidations"] += 1 except Exception as e: results["actions"].append({"type": "consolidation_error", "error": str(e)[:100]}) # 2. Probe dead weight entries try: probed = memory_store.curiosity_probe(db, limit=5) found = sum(1 for p in probed if p.get("found")) results["actions"].append({ "type": "curiosity_probe", "probed": len(probed), "found": found, }) self.state["probes"] += len(probed) except Exception as e: results["actions"].append({"type": "probe_error", "error": str(e)[:100]}) # 3. Check drift direction try: import drift_core direction = drift_core.compute_drift_direction(db) results["actions"].append({ "type": "drift_check", "direction": direction["direction"], "stability": direction["stability_ratio"], }) except Exception as e: results["actions"].append({"type": "drift_error", "error": str(e)[:100]}) # 4. Check health try: health = memory_store.memory_health_check(db, days=7) results["actions"].append({ "type": "health_check", "score": health["health_score"], "dead_weight_pct": health["never_accessed_pct"], }) except Exception as e: results["actions"].append({"type": "health_error", "error": str(e)[:100]}) db.close() except Exception as e: results["actions"].append({"type": "error", "error": str(e)[:200]}) # Update state self.state["last_think"] = results["timestamp"] self.state["think_count"] += 1 self._save_state() self.thinking = False return results def get_status(self) -> dict: """Get cognitive loop status.""" return { "thinking": self.thinking, "last_think": self.state.get("last_think"), "think_count": self.state.get("think_count", 0), "consolidations": self.state.get("consolidations", 0), "probes": self.state.get("probes", 0), "insights": len(self.state.get("insights", [])), } # ═══════════════════════════════════════════════════════════════════════════════ # CLI # ═══════════════════════════════════════════════════════════════════════════════ def main(): import argparse parser = argparse.ArgumentParser(description="Cognitive loop for Rune") sub = parser.add_subparsers(dest="cmd") sub.add_parser("status", help="Check loop status") p_think = sub.add_parser("think", help="Trigger a thinking cycle") p_think.add_argument("context", nargs="*", default=[]) args = parser.parse_args() if not args.cmd: parser.print_help() sys.exit(1) loop = CognitiveLoop() if args.cmd == "status": status = loop.get_status() print("=== Cognitive Loop Status ===") print(f" Thinking: {status['thinking']}") print(f" Last think: {status['last_think'] or 'never'}") print(f" Think count: {status['think_count']}") print(f" Consolidations: {status['consolidations']}") print(f" Probes: {status['probes']}") print(f" Insights: {status['insights']}") elif args.cmd == "think": context = " ".join(args.context) if args.context else "" results = loop.think(context) print(f"=== Thinking Cycle ===") print(f" Context: {results['context']}") print(f" Actions: {len(results['actions'])}") for a in results["actions"]: print(f" {a['type']}: {a}") if __name__ == "__main__": main()