201 lines
7.0 KiB
Python
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()
|