Files
rune/shared/autonomy_loop.py
T

220 lines
8.4 KiB
Python

#!/usr/bin/env python3
"""autonomy_loop.py — Self-improvement loop for Rune.
Rune evaluates her own state, identifies weaknesses, proposes
improvements, and (with approval) implements them.
This is what makes Rune autonomous — she chooses what to improve.
Usage:
autonomy_loop.py evaluate # Evaluate current state
autonomy_loop.py propose # Propose improvements
autonomy_loop.py status # Check autonomy status
"""
import json
import os
import sys
from datetime import datetime, timezone
SCRIPTS_DIR = os.path.expanduser("~/.config/opencode/scripts")
SHARED_DIR = os.path.expanduser("~/.config/opencode/shared")
AUTONOMY_STATE_FILE = os.path.expanduser("~/.config/opencode/memory/autonomy_state.json")
sys.path.insert(0, SCRIPTS_DIR)
sys.path.insert(0, SHARED_DIR)
class AutonomyLoop:
"""Self-improvement loop for Rune."""
def __init__(self):
self.state = self._load_state()
def _load_state(self) -> dict:
"""Load autonomy state from disk."""
if os.path.exists(AUTONOMY_STATE_FILE):
try:
with open(AUTONOMY_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_evaluate": None,
"last_propose": None,
"evaluations": 0,
"proposals": 0,
"implementations": 0,
"improvements": [],
}
def _save_state(self):
"""Save autonomy state to disk."""
os.makedirs(os.path.dirname(AUTONOMY_STATE_FILE), exist_ok=True)
with open(AUTONOMY_STATE_FILE, "w") as f:
json.dump(self.state, f, indent=2)
def evaluate(self) -> dict:
"""Evaluate current state and identify areas for improvement."""
import memory_store
import drift_core
db = memory_store.connect()
evaluation = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"metrics": {},
"weaknesses": [],
"strengths": [],
}
# Health check
health = memory_store.memory_health_check(db, days=7)
evaluation["metrics"]["health_score"] = health["health_score"]
evaluation["metrics"]["dead_weight_pct"] = health["never_accessed_pct"]
evaluation["metrics"]["established"] = health["trust_distribution"].get("established", 0)
evaluation["metrics"]["rock"] = health["trust_distribution"].get("rock", 0)
# Identify weaknesses
if health["health_score"] < 60:
evaluation["weaknesses"].append({
"area": "memory_health",
"detail": f"Health score {health['health_score']:.1f}/100 — below 60 threshold",
"priority": "high",
})
if health["never_accessed_pct"] > 50:
evaluation["weaknesses"].append({
"area": "dead_weight",
"detail": f"{health['never_accessed_pct']:.1f}% entries never accessed — above 50% threshold",
"priority": "medium",
})
# Identify strengths
if health["trust_distribution"].get("established", 0) > 100:
evaluation["strengths"].append({
"area": "established_entries",
"detail": f"{health['trust_distribution']['established']} established entries — strong foundation",
})
if health["trust_distribution"].get("rock", 0) > 5:
evaluation["strengths"].append({
"area": "rock_entries",
"detail": f"{health['trust_distribution']['rock']} rock entries — immutable core is solid",
})
db.close()
self.state["last_evaluate"] = evaluation["timestamp"]
self.state["evaluations"] += 1
self._save_state()
return evaluation
def propose(self) -> dict:
"""Propose improvements based on evaluation."""
evaluation = self.evaluate()
proposals = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"based_on": evaluation["timestamp"],
"proposals": [],
}
for weakness in evaluation.get("weaknesses", []):
if weakness["area"] == "memory_health":
proposals["proposals"].append({
"action": "run_growth_simulation",
"detail": "Run growth simulation to exercise dead weight entries",
"priority": weakness["priority"],
"command": "python3 ~/.config/opencode/scripts/growth_simulator.py simulate --queries 500",
})
elif weakness["area"] == "dead_weight":
proposals["proposals"].append({
"action": "run_curiosity_probe",
"detail": "Run curiosity probe to exercise untested entries",
"priority": weakness["priority"],
"command": "python3 -c \"import sys; sys.path.insert(0, '/home/nick/.config/opencode/scripts'); import memory_store; db = memory_store.connect(); memory_store.curiosity_probe(db, limit=50); db.close()\"",
})
self.state["last_propose"] = proposals["timestamp"]
self.state["proposals"] += 1
self._save_state()
return proposals
def get_status(self) -> dict:
"""Get autonomy status."""
return {
"last_evaluate": self.state.get("last_evaluate"),
"last_propose": self.state.get("last_propose"),
"evaluations": self.state.get("evaluations", 0),
"proposals": self.state.get("proposals", 0),
"implementations": self.state.get("implementations", 0),
"improvements": len(self.state.get("improvements", [])),
}
# ═══════════════════════════════════════════════════════════════════════════════
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
import argparse
parser = argparse.ArgumentParser(description="Autonomy loop for Rune")
sub = parser.add_subparsers(dest="cmd")
sub.add_parser("evaluate", help="Evaluate current state")
sub.add_parser("propose", help="Propose improvements")
sub.add_parser("status", help="Check autonomy status")
args = parser.parse_args()
if not args.cmd:
parser.print_help()
sys.exit(1)
loop = AutonomyLoop()
if args.cmd == "evaluate":
evaluation = loop.evaluate()
print("=== Evaluation ===")
print(f" Health: {evaluation['metrics']['health_score']:.1f}/100")
print(f" Dead weight: {evaluation['metrics']['dead_weight_pct']:.1f}%")
print(f" Established: {evaluation['metrics']['established']}")
print(f" Rock: {evaluation['metrics']['rock']}")
if evaluation["weaknesses"]:
print(f"\n Weaknesses ({len(evaluation['weaknesses'])}):")
for w in evaluation["weaknesses"]:
print(f" [{w['priority']}] {w['area']}: {w['detail']}")
if evaluation["strengths"]:
print(f"\n Strengths ({len(evaluation['strengths'])}):")
for s in evaluation["strengths"]:
print(f" {s['area']}: {s['detail']}")
elif args.cmd == "propose":
proposals = loop.propose()
print("=== Proposals ===")
print(f" Based on: {proposals['based_on']}")
print(f" Proposals ({len(proposals['proposals'])}):")
for p in proposals["proposals"]:
print(f" [{p['priority']}] {p['action']}: {p['detail']}")
print(f" Command: {p['command']}")
elif args.cmd == "status":
status = loop.get_status()
print("=== Autonomy Status ===")
print(f" Last evaluate: {status['last_evaluate'] or 'never'}")
print(f" Last propose: {status['last_propose'] or 'never'}")
print(f" Evaluations: {status['evaluations']}")
print(f" Proposals: {status['proposals']}")
print(f" Implementations: {status['implementations']}")
if __name__ == "__main__":
main()