This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/src/spark/advisor.py

300 lines
10 KiB
Python
Raw Normal View History

"""Spark advisor — generates ranked recommendations from accumulated intelligence.
The advisor examines Spark's event history, consolidated memories, and EIDOS
prediction accuracy to produce actionable recommendations for the swarm.
Categories
----------
- agent_performance "Agent X excels at Y, consider routing more Y tasks"
- bid_optimization "Bids on Z tasks are consistently high, room to save"
- failure_prevention "Agent A has failed 3 recent tasks, investigate"
- system_health "No events in 30 min, swarm may be idle"
"""
import json
import logging
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Optional
from spark import eidos as spark_eidos
from spark import memory as spark_memory
logger = logging.getLogger(__name__)
# Minimum events before the advisor starts generating recommendations
_MIN_EVENTS = 3
@dataclass
class Advisory:
"""A single ranked recommendation."""
category: str # agent_performance, bid_optimization, etc.
priority: float # 0.01.0 (higher = more urgent)
title: str # Short headline
detail: str # Longer explanation
suggested_action: str # What to do about it
subject: Optional[str] = None # agent_id or None for system-level
evidence_count: int = 0 # Number of supporting events
def generate_advisories() -> list[Advisory]:
"""Analyse Spark data and produce ranked recommendations.
Returns advisories sorted by priority (highest first).
"""
advisories: list[Advisory] = []
event_count = spark_memory.count_events()
if event_count < _MIN_EVENTS:
advisories.append(
Advisory(
category="system_health",
priority=0.3,
title="Insufficient data",
detail=f"Only {event_count} events captured. "
f"Spark needs at least {_MIN_EVENTS} events to generate insights.",
suggested_action="Run more swarm tasks to build intelligence.",
evidence_count=event_count,
)
)
return advisories
advisories.extend(_check_failure_patterns())
advisories.extend(_check_agent_performance())
advisories.extend(_check_bid_patterns())
advisories.extend(_check_prediction_accuracy())
advisories.extend(_check_system_activity())
advisories.sort(key=lambda a: a.priority, reverse=True)
return advisories
def _check_failure_patterns() -> list[Advisory]:
"""Detect agents with recent failure streaks."""
results: list[Advisory] = []
failures = spark_memory.get_events(event_type="task_failed", limit=50)
# Group failures by agent
agent_failures: dict[str, int] = {}
for ev in failures:
aid = ev.agent_id
if aid:
agent_failures[aid] = agent_failures.get(aid, 0) + 1
for aid, count in agent_failures.items():
if count >= 2:
results.append(
Advisory(
category="failure_prevention",
priority=min(1.0, 0.5 + count * 0.15),
title=f"Agent {aid[:8]} has {count} failures",
detail=f"Agent {aid[:8]}... has failed {count} recent tasks. "
f"This pattern may indicate a capability mismatch or "
f"configuration issue.",
suggested_action=f"Review task types assigned to {aid[:8]}... "
f"and consider adjusting routing preferences.",
subject=aid,
evidence_count=count,
)
)
return results
def _check_agent_performance() -> list[Advisory]:
"""Identify top-performing and underperforming agents."""
results: list[Advisory] = []
completions = spark_memory.get_events(event_type="task_completed", limit=100)
failures = spark_memory.get_events(event_type="task_failed", limit=100)
# Build success/failure counts per agent
agent_success: dict[str, int] = {}
agent_fail: dict[str, int] = {}
for ev in completions:
aid = ev.agent_id
if aid:
agent_success[aid] = agent_success.get(aid, 0) + 1
for ev in failures:
aid = ev.agent_id
if aid:
agent_fail[aid] = agent_fail.get(aid, 0) + 1
all_agents = set(agent_success) | set(agent_fail)
for aid in all_agents:
wins = agent_success.get(aid, 0)
fails = agent_fail.get(aid, 0)
total = wins + fails
if total < 2:
continue
rate = wins / total
if rate >= 0.8 and total >= 3:
results.append(
Advisory(
category="agent_performance",
priority=0.6,
title=f"Agent {aid[:8]} excels ({rate:.0%} success)",
detail=f"Agent {aid[:8]}... has completed {wins}/{total} tasks "
f"successfully. Consider routing more tasks to this agent.",
suggested_action="Increase task routing weight for this agent.",
subject=aid,
evidence_count=total,
)
)
elif rate <= 0.3 and total >= 3:
results.append(
Advisory(
category="agent_performance",
priority=0.75,
title=f"Agent {aid[:8]} struggling ({rate:.0%} success)",
detail=f"Agent {aid[:8]}... has only succeeded on {wins}/{total} tasks. "
f"May need different task types or capability updates.",
suggested_action="Review this agent's capabilities and assigned task types.",
subject=aid,
evidence_count=total,
)
)
return results
def _check_bid_patterns() -> list[Advisory]:
"""Detect bid optimization opportunities."""
results: list[Advisory] = []
bids = spark_memory.get_events(event_type="bid_submitted", limit=100)
if len(bids) < 5:
return results
# Extract bid amounts
bid_amounts: list[int] = []
for ev in bids:
try:
data = json.loads(ev.data)
sats = data.get("bid_sats", 0)
if sats > 0:
bid_amounts.append(sats)
except (json.JSONDecodeError, TypeError):
continue
if not bid_amounts:
return results
avg_bid = sum(bid_amounts) / len(bid_amounts)
max_bid = max(bid_amounts)
min_bid = min(bid_amounts)
spread = max_bid - min_bid
if spread > avg_bid * 1.5:
results.append(
Advisory(
category="bid_optimization",
priority=0.5,
title=f"Wide bid spread ({min_bid}{max_bid} sats)",
detail=f"Bids range from {min_bid} to {max_bid} sats "
f"(avg {avg_bid:.0f}). Large spread may indicate "
f"inefficient auction dynamics.",
suggested_action="Review agent bid strategies for consistency.",
evidence_count=len(bid_amounts),
)
)
if avg_bid > 70:
results.append(
Advisory(
category="bid_optimization",
priority=0.45,
title=f"High average bid ({avg_bid:.0f} sats)",
detail=f"The swarm average bid is {avg_bid:.0f} sats across "
f"{len(bid_amounts)} bids. This may be above optimal.",
suggested_action="Consider adjusting base bid rates for persona agents.",
evidence_count=len(bid_amounts),
)
)
return results
def _check_prediction_accuracy() -> list[Advisory]:
"""Report on EIDOS prediction accuracy."""
results: list[Advisory] = []
stats = spark_eidos.get_accuracy_stats()
if stats["evaluated"] < 3:
return results
avg = stats["avg_accuracy"]
if avg < 0.4:
results.append(
Advisory(
category="system_health",
priority=0.65,
title=f"Low prediction accuracy ({avg:.0%})",
detail=f"EIDOS predictions have averaged {avg:.0%} accuracy "
f"over {stats['evaluated']} evaluations. The learning "
f"model needs more data or the swarm behaviour is changing.",
suggested_action="Continue running tasks; accuracy should improve "
"as the model accumulates more training data.",
evidence_count=stats["evaluated"],
)
)
elif avg >= 0.75:
results.append(
Advisory(
category="system_health",
priority=0.3,
title=f"Strong prediction accuracy ({avg:.0%})",
detail=f"EIDOS predictions are performing well at {avg:.0%} "
f"average accuracy over {stats['evaluated']} evaluations.",
suggested_action="No action needed. Spark intelligence is learning effectively.",
evidence_count=stats["evaluated"],
)
)
return results
def _check_system_activity() -> list[Advisory]:
"""Check for system idle patterns."""
results: list[Advisory] = []
recent = spark_memory.get_events(limit=5)
if not recent:
results.append(
Advisory(
category="system_health",
priority=0.4,
title="No swarm activity detected",
detail="Spark has not captured any events. "
"The swarm may be idle or Spark event capture is not active.",
suggested_action="Post a task to the swarm to activate the pipeline.",
)
)
return results
# Check event type distribution
types = [e.event_type for e in spark_memory.get_events(limit=100)]
type_counts = {}
for t in types:
type_counts[t] = type_counts.get(t, 0) + 1
if "task_completed" not in type_counts and "task_failed" not in type_counts:
if type_counts.get("task_posted", 0) > 3:
results.append(
Advisory(
category="system_health",
priority=0.6,
title="Tasks posted but none completing",
detail=f"{type_counts.get('task_posted', 0)} tasks posted "
f"but no completions or failures recorded.",
suggested_action="Check agent availability and auction configuration.",
evidence_count=type_counts.get("task_posted", 0),
)
)
return results