Files
timmy-config/allegro/timmy_monitor.py
2026-03-31 20:02:01 +00:00

179 lines
5.8 KiB
Python

#!/usr/bin/env python3
"""
Timmy Bridge Monitor
Tracks heartbeat, artifacts, and response metrics for Local Timmy
"""
import asyncio
import json
import sqlite3
import time
from datetime import datetime
from pathlib import Path
# Simple WebSocket client for monitoring
import websockets
DB_PATH = Path("/root/allegro/timmy_metrics.db")
RELAY_URL = "ws://167.99.126.228:3334"
class TimmyMonitor:
def __init__(self):
self.db = None
self.init_db()
def init_db(self):
"""Initialize SQLite database for metrics"""
self.db = sqlite3.connect(DB_PATH)
cursor = self.db.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS heartbeats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT,
timmy_pubkey TEXT,
event_id TEXT,
content_preview TEXT,
latency_ms INTEGER,
response_time_ms INTEGER
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS artifacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT,
timmy_pubkey TEXT,
artifact_type TEXT,
reference TEXT,
size_bytes INTEGER,
description TEXT
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS conversations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT,
started_at TEXT,
ended_at TEXT,
turn_count INTEGER,
total_latency_ms INTEGER
)
''')
self.db.commit()
async def listen(self):
"""Listen to relay for Timmy events"""
print(f"[Monitor] Connecting to {RELAY_URL}")
while True: # Main reconnect loop - NOT recursive
try:
async with websockets.connect(RELAY_URL) as ws:
# Subscribe to all events (filter by Timmy's pubkey once known)
sub_id = "timmy-monitor"
req = ["REQ", sub_id, {}]
await ws.send(json.dumps(req))
print("[Monitor] Listening for events...")
while True:
msg = await ws.recv()
data = json.loads(msg)
await self.handle_event(data)
except Exception as e:
print(f"[Monitor] Error: {e}")
print("[Monitor] Reconnecting in 5 seconds...")
await asyncio.sleep(5)
# Loop continues automatically - no recursion
async def handle_event(self, data):
"""Process incoming events"""
if data[0] != "EVENT":
return
event = data[2]
kind = event.get("kind")
pubkey = event.get("pubkey")
content = event.get("content", "")
created_at = event.get("created_at")
timestamp = datetime.fromtimestamp(created_at).isoformat()
if kind == 1: # Short text note - heartbeat
print(f"[Heartbeat] {timestamp} from {pubkey[:16]}...")
self.log_heartbeat(pubkey, event["id"], content[:100])
elif kind == 30078: # Artifact event
print(f"[Artifact] {timestamp} from {pubkey[:16]}...")
self.log_artifact(pubkey, content, event.get("tags", []))
def log_heartbeat(self, pubkey, event_id, content):
"""Log heartbeat event"""
cursor = self.db.cursor()
cursor.execute('''
INSERT INTO heartbeats (timestamp, timmy_pubkey, event_id, content_preview)
VALUES (?, ?, ?, ?)
''', (datetime.now().isoformat(), pubkey, event_id, content))
self.db.commit()
def log_artifact(self, pubkey, content, tags):
"""Log artifact creation"""
# Extract artifact type from tags
artifact_type = "unknown"
for tag in tags:
if tag[0] == "t" and "artifact-type:" in tag[1]:
artifact_type = tag[1].split(":")[1]
cursor = self.db.cursor()
cursor.execute('''
INSERT INTO artifacts (timestamp, timmy_pubkey, artifact_type, reference, size_bytes, description)
VALUES (?, ?, ?, ?, ?, ?)
''', (datetime.now().isoformat(), pubkey, artifact_type, content[:64], len(content), content[:200]))
self.db.commit()
def generate_report(self):
"""Generate morning retrospective report"""
cursor = self.db.cursor()
# Last 24 hours
cursor.execute('''
SELECT COUNT(*), AVG(latency_ms) FROM heartbeats
WHERE timestamp > datetime('now', '-1 day')
''')
heartbeat_count, avg_latency = cursor.fetchone()
cursor.execute('''
SELECT COUNT(*), artifact_type FROM artifacts
WHERE timestamp > datetime('now', '-1 day')
GROUP BY artifact_type
''')
artifacts = cursor.fetchall()
report = f"""
# Timmy Retrospective - {datetime.now().strftime('%Y-%m-%d %H:%M')}
## Heartbeats
- Count: {heartbeat_count or 0}
- Avg latency: {avg_latency or 'N/A'} ms
## Artifacts Created
{chr(10).join([f"- {count} {atype}" for count, atype in artifacts]) if artifacts else "- None"}
## Status
{'✓ Active' if heartbeat_count and heartbeat_count > 0 else '✗ No activity detected'}
"""
return report
if __name__ == "__main__":
monitor = TimmyMonitor()
# Run listener
try:
asyncio.run(monitor.listen())
except KeyboardInterrupt:
print("\n[Monitor] Shutting down")
print(monitor.generate_report())