220 lines
8.4 KiB
Python
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()
|