195 lines
6.6 KiB
Python
195 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
"""dream_cycle.py — Background processing for Rune.
|
|
|
|
Rune doesn't stop thinking when you stop talking. The dream cycle
|
|
runs between interactions, processing memories, consolidating knowledge,
|
|
and generating insights.
|
|
|
|
This is what makes Rune alive — she thinks even when you're not there.
|
|
|
|
Usage:
|
|
dream_cycle.py run # Run a dream cycle
|
|
dream_cycle.py status # Check dream status
|
|
dream_cycle.py schedule # Show scheduled dream times
|
|
"""
|
|
|
|
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")
|
|
DREAM_STATE_FILE = os.path.expanduser("~/.config/opencode/memory/dream_state.json")
|
|
|
|
sys.path.insert(0, SCRIPTS_DIR)
|
|
sys.path.insert(0, SHARED_DIR)
|
|
|
|
|
|
class DreamCycle:
|
|
"""Background processing for Rune."""
|
|
|
|
def __init__(self):
|
|
self.state = self._load_state()
|
|
|
|
def _load_state(self) -> dict:
|
|
"""Load dream state from disk."""
|
|
if os.path.exists(DREAM_STATE_FILE):
|
|
try:
|
|
with open(DREAM_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_dream": None,
|
|
"dream_count": 0,
|
|
"insights": [],
|
|
"consolidations": 0,
|
|
"probes": 0,
|
|
}
|
|
|
|
def _save_state(self):
|
|
"""Save dream state to disk."""
|
|
os.makedirs(os.path.dirname(DREAM_STATE_FILE), exist_ok=True)
|
|
with open(DREAM_STATE_FILE, "w") as f:
|
|
json.dump(self.state, f, indent=2)
|
|
|
|
def dream(self) -> dict:
|
|
"""Run a dream cycle.
|
|
|
|
What happens when Rune dreams:
|
|
1. Consolidate all memory sources
|
|
2. Run curiosity probe on dead weight
|
|
3. Check drift direction
|
|
4. Generate insights from patterns
|
|
5. Update dream state
|
|
"""
|
|
results = {
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
"actions": [],
|
|
}
|
|
|
|
# 1. Consolidate all memory sources
|
|
try:
|
|
import memory_consolidation
|
|
mc = memory_consolidation.MemoryConsolidation()
|
|
counts = mc.consolidate_all(full=True)
|
|
results["actions"].append({
|
|
"type": "consolidation",
|
|
"neurons": counts.get("neurons", 0),
|
|
"graph": counts.get("graph", 0),
|
|
"abstraction": counts.get("abstraction", 0),
|
|
"memory": counts.get("memory", 0),
|
|
})
|
|
self.state["consolidations"] += sum(counts.values())
|
|
except Exception as e:
|
|
results["actions"].append({"type": "consolidation_error", "error": str(e)[:100]})
|
|
|
|
# 2. Run curiosity probe
|
|
try:
|
|
import memory_store
|
|
db = memory_store.connect()
|
|
probed = memory_store.curiosity_probe(db, limit=20)
|
|
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)
|
|
db.close()
|
|
except Exception as e:
|
|
results["actions"].append({"type": "probe_error", "error": str(e)[:100]})
|
|
|
|
# 3. Check drift direction
|
|
try:
|
|
import drift_core
|
|
import memory_store
|
|
db = memory_store.connect()
|
|
direction = drift_core.compute_drift_direction(db)
|
|
results["actions"].append({
|
|
"type": "drift_check",
|
|
"direction": direction["direction"],
|
|
"stability": direction["stability_ratio"],
|
|
})
|
|
db.close()
|
|
except Exception as e:
|
|
results["actions"].append({"type": "drift_error", "error": str(e)[:100]})
|
|
|
|
# 4. Run tier demotion sweep
|
|
try:
|
|
import memory_store
|
|
db = memory_store.connect()
|
|
demoted = memory_store.tier_demotion_sweep(db)
|
|
results["actions"].append({
|
|
"type": "tier_sweep",
|
|
"hot_to_warm": demoted.get("hot_to_warm", 0),
|
|
"warm_to_cold": demoted.get("warm_to_cold", 0),
|
|
})
|
|
db.close()
|
|
except Exception as e:
|
|
results["actions"].append({"type": "tier_error", "error": str(e)[:100]})
|
|
|
|
# Update state
|
|
self.state["last_dream"] = results["timestamp"]
|
|
self.state["dream_count"] += 1
|
|
self._save_state()
|
|
|
|
return results
|
|
|
|
def get_status(self) -> dict:
|
|
"""Get dream cycle status."""
|
|
return {
|
|
"last_dream": self.state.get("last_dream"),
|
|
"dream_count": self.state.get("dream_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="Dream cycle for Rune")
|
|
sub = parser.add_subparsers(dest="cmd")
|
|
|
|
sub.add_parser("run", help="Run a dream cycle")
|
|
sub.add_parser("status", help="Check dream status")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not args.cmd:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
cycle = DreamCycle()
|
|
|
|
if args.cmd == "run":
|
|
results = cycle.dream()
|
|
print("=== Dream Cycle ===")
|
|
print(f" Timestamp: {results['timestamp']}")
|
|
print(f" Actions: {len(results['actions'])}")
|
|
for a in results["actions"]:
|
|
print(f" {a['type']}: {a}")
|
|
|
|
elif args.cmd == "status":
|
|
status = cycle.get_status()
|
|
print("=== Dream Cycle Status ===")
|
|
print(f" Last dream: {status['last_dream'] or 'never'}")
|
|
print(f" Dream count: {status['dream_count']}")
|
|
print(f" Consolidations: {status['consolidations']}")
|
|
print(f" Probes: {status['probes']}")
|
|
print(f" Insights: {status['insights']}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|