Files
rune/shared/cognitive_loop.py
T

201 lines
7.0 KiB
Python

#!/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 <context> # 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()