FLEET-004: 22 milestone messages across 6 phases + 11 Fibonacci uptime milestones. FLEET-005: Resource tracking system — Capacity/Uptime/Innovation tension model. - Tracks capacity spending and regeneration (2/hr baseline) - Innovation generates only when utilization < 70% (5/hr scaled) - Fibonacci uptime milestone detection (95% through 99.5%) - Phase gate checks (P2: 95% uptime, P3: 95% + 100 innovation, P5: 95% + 500) - CLI: status, regen commands Fixes timmy-home#557 (FLEET-004), #558 (FLEET-005)
232 lines
7.6 KiB
Python
Executable File
232 lines
7.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Fleet Resource Tracker — Tracks Capacity, Uptime, and Innovation.
|
|
|
|
Paperclips-inspired tension model:
|
|
- Capacity: spent on fleet improvements, generates through utilization
|
|
- Uptime: earned when services stay up, Fibonacci milestones unlock capabilities
|
|
- Innovation: only generates when capacity < 70%. Fuels Phase 3+.
|
|
|
|
This is the heart of the fleet progression system.
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import time
|
|
import socket
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
# === CONFIG ===
|
|
DATA_DIR = Path(os.path.expanduser("~/.local/timmy/fleet-resources"))
|
|
RESOURCES_FILE = DATA_DIR / "resources.json"
|
|
|
|
# Tension thresholds
|
|
INNOVATION_THRESHOLD = 0.70 # Innovation only generates when capacity < 70%
|
|
INNOVATION_RATE = 5.0 # Innovation generated per hour when under threshold
|
|
CAPACITY_REGEN_RATE = 2.0 # Capacity regenerates per hour of healthy operation
|
|
FIBONACCI = [95.0, 95.5, 96.0, 97.0, 97.5, 98.0, 98.3, 98.6, 98.9, 99.0, 99.5]
|
|
|
|
|
|
def init():
|
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
if not RESOURCES_FILE.exists():
|
|
data = {
|
|
"capacity": {
|
|
"current": 100.0,
|
|
"max": 100.0,
|
|
"spent_on": [],
|
|
"history": []
|
|
},
|
|
"uptime": {
|
|
"current_pct": 100.0,
|
|
"milestones_reached": [],
|
|
"total_checks": 0,
|
|
"successful_checks": 0,
|
|
"history": []
|
|
},
|
|
"innovation": {
|
|
"current": 0.0,
|
|
"total_generated": 0.0,
|
|
"spent_on": [],
|
|
"last_calculated": time.time()
|
|
}
|
|
}
|
|
RESOURCES_FILE.write_text(json.dumps(data, indent=2))
|
|
print("Initialized resource tracker")
|
|
return RESOURCES_FILE.exists()
|
|
|
|
|
|
def load():
|
|
if RESOURCES_FILE.exists():
|
|
return json.loads(RESOURCES_FILE.read_text())
|
|
return None
|
|
|
|
|
|
def save(data):
|
|
RESOURCES_FILE.write_text(json.dumps(data, indent=2))
|
|
|
|
|
|
def update_uptime(checks: dict):
|
|
"""Update uptime stats from health check results.
|
|
checks = {'ezra': True, 'allegro': True, 'bezalel': True, 'gitea': True, ...}
|
|
"""
|
|
data = load()
|
|
if not data:
|
|
return
|
|
|
|
data["uptime"]["total_checks"] += 1
|
|
successes = sum(1 for v in checks.values() if v)
|
|
total = len(checks)
|
|
|
|
# Overall uptime percentage
|
|
overall = successes / max(total, 1) * 100.0
|
|
data["uptime"]["successful_checks"] += successes
|
|
|
|
# Calculate rolling uptime
|
|
if "history" not in data["uptime"]:
|
|
data["uptime"]["history"] = []
|
|
data["uptime"]["history"].append({
|
|
"ts": datetime.now(timezone.utc).isoformat(),
|
|
"checks": checks,
|
|
"overall": round(overall, 2)
|
|
})
|
|
|
|
# Keep last 1000 checks
|
|
if len(data["uptime"]["history"]) > 1000:
|
|
data["uptime"]["history"] = data["uptime"]["history"][-1000:]
|
|
|
|
# Calculate current uptime %, last 100 checks
|
|
recent = data["uptime"]["history"][-100:]
|
|
recent_ok = sum(c["overall"] for c in recent) / max(len(recent), 1)
|
|
data["uptime"]["current_pct"] = round(recent_ok, 2)
|
|
|
|
# Check Fibonacci milestones
|
|
new_milestones = []
|
|
for fib in FIBONACCI:
|
|
if fib not in data["uptime"]["milestones_reached"] and recent_ok >= fib:
|
|
data["uptime"]["milestones_reached"].append(fib)
|
|
new_milestones.append(fib)
|
|
|
|
save(data)
|
|
|
|
if new_milestones:
|
|
print(f" UPTIME MILESTONE: {','.join(str(m) + '%') for m in new_milestones}")
|
|
print(f" Current uptime: {recent_ok:.1f}%")
|
|
|
|
return data["uptime"]
|
|
|
|
|
|
def spend_capacity(amount: float, purpose: str):
|
|
"""Spend capacity on a fleet improvement."""
|
|
data = load()
|
|
if not data:
|
|
return False
|
|
if data["capacity"]["current"] < amount:
|
|
print(f" INSUFFICIENT CAPACITY: Need {amount}, have {data['capacity']['current']:.1f}")
|
|
return False
|
|
data["capacity"]["current"] -= amount
|
|
data["capacity"]["spent_on"].append({
|
|
"purpose": purpose,
|
|
"amount": amount,
|
|
"ts": datetime.now(timezone.utc).isoformat()
|
|
})
|
|
save(data)
|
|
print(f" Spent {amount} capacity on: {purpose}")
|
|
return True
|
|
|
|
|
|
def regenerate_resources():
|
|
"""Regenerate capacity and calculate innovation."""
|
|
data = load()
|
|
if not data:
|
|
return
|
|
|
|
now = time.time()
|
|
last = data["innovation"]["last_calculated"]
|
|
hours = (now - last) / 3600.0
|
|
if hours < 0.1: # Only update every ~6 minutes
|
|
return
|
|
|
|
# Regenerate capacity
|
|
capacity_gain = CAPACITY_REGEN_RATE * hours
|
|
data["capacity"]["current"] = min(
|
|
data["capacity"]["max"],
|
|
data["capacity"]["current"] + capacity_gain
|
|
)
|
|
|
|
# Calculate capacity utilization
|
|
utilization = 1.0 - (data["capacity"]["current"] / data["capacity"]["max"])
|
|
|
|
# Generate innovation only when under threshold
|
|
innovation_gain = 0.0
|
|
if utilization < INNOVATION_THRESHOLD:
|
|
innovation_gain = INNOVATION_RATE * hours * (1.0 - utilization / INNOVATION_THRESHOLD)
|
|
data["innovation"]["current"] += innovation_gain
|
|
data["innovation"]["total_generated"] += innovation_gain
|
|
|
|
# Record history
|
|
if "history" not in data["capacity"]:
|
|
data["capacity"]["history"] = []
|
|
data["capacity"]["history"].append({
|
|
"ts": datetime.now(timezone.utc).isoformat(),
|
|
"capacity": round(data["capacity"]["current"], 1),
|
|
"utilization": round(utilization * 100, 1),
|
|
"innovation": round(data["innovation"]["current"], 1),
|
|
"innovation_gain": round(innovation_gain, 1)
|
|
})
|
|
# Keep last 500 capacity records
|
|
if len(data["capacity"]["history"]) > 500:
|
|
data["capacity"]["history"] = data["capacity"]["history"][-500:]
|
|
|
|
data["innovation"]["last_calculated"] = now
|
|
|
|
save(data)
|
|
print(f" Capacity: {data['capacity']['current']:.1f}/{data['capacity']['max']:.1f}")
|
|
print(f" Utilization: {utilization*100:.1f}%")
|
|
print(f" Innovation: {data['innovation']['current']:.1f} (+{innovation_gain:.1f} this period)")
|
|
|
|
return data
|
|
|
|
|
|
def status():
|
|
"""Print current resource status."""
|
|
data = load()
|
|
if not data:
|
|
print("Resource tracker not initialized. Run --init first.")
|
|
return
|
|
|
|
print("\n=== Fleet Resources ===")
|
|
print(f" Capacity: {data['capacity']['current']:.1f}/{data['capacity']['max']:.1f}")
|
|
|
|
utilization = 1.0 - (data["capacity"]["current"] / data["capacity"]["max"])
|
|
print(f" Utilization: {utilization*100:.1f}%")
|
|
|
|
innovation_status = "GENERATING" if utilization < INNOVATION_THRESHOLD else "BLOCKED"
|
|
print(f" Innovation: {data['innovation']['current']:.1f} [{innovation_status}]")
|
|
|
|
print(f" Uptime: {data['uptime']['current_pct']:.1f}%")
|
|
print(f" Milestones: {', '.join(str(m)+'%' for m in data['uptime']['milestones_reached']) or 'None yet'}")
|
|
|
|
# Phase gate checks
|
|
phase_2_ok = data['uptime']['current_pct'] >= 95.0
|
|
phase_3_ok = phase_2_ok and data['innovation']['current'] > 100
|
|
phase_5_ok = phase_2_ok and data['innovation']['current'] > 500
|
|
|
|
print(f"\n Phase Gates:")
|
|
print(f" Phase 2 (Automation): {'UNLOCKED' if phase_2_ok else 'LOCKED (need 95% uptime)'}")
|
|
print(f" Phase 3 (Orchestration): {'UNLOCKED' if phase_3_ok else 'LOCKED (need 95% uptime + 100 innovation)'}")
|
|
print(f" Phase 5 (Scale): {'UNLOCKED' if phase_5_ok else 'LOCKED (need 95% uptime + 500 innovation)'}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
init()
|
|
if len(sys.argv) > 1 and sys.argv[1] == "status":
|
|
status()
|
|
elif len(sys.argv) > 1 and sys.argv[1] == "regen":
|
|
regenerate_resources()
|
|
else:
|
|
regenerate_resources()
|
|
status()
|