[loop-cycle-43] refactor: remove 1035 lines of dead code (#136) (#146)

This commit is contained in:
2026-03-15 10:10:12 -04:00
parent 16b31b30cb
commit d2c51763d0
6 changed files with 2 additions and 1037 deletions

View File

@@ -1,193 +0,0 @@
"""Event Broadcaster - bridges event_log to WebSocket clients.
When events are logged, they are broadcast to all connected dashboard clients
via WebSocket for real-time activity feed updates.
"""
import asyncio
import logging
from typing import Optional
try:
from swarm.event_log import EventLogEntry
except ImportError:
EventLogEntry = None
logger = logging.getLogger(__name__)
class EventBroadcaster:
"""Broadcasts events to WebSocket clients.
Usage:
from infrastructure.events.broadcaster import event_broadcaster
event_broadcaster.broadcast(event)
"""
def __init__(self) -> None:
self._ws_manager: Optional = None
def _get_ws_manager(self):
"""Lazy import to avoid circular deps."""
if self._ws_manager is None:
try:
from infrastructure.ws_manager.handler import ws_manager
self._ws_manager = ws_manager
except Exception as exc:
logger.debug("WebSocket manager not available: %s", exc)
return self._ws_manager
async def broadcast(self, event: EventLogEntry) -> int:
"""Broadcast an event to all connected WebSocket clients.
Args:
event: The event to broadcast
Returns:
Number of clients notified
"""
ws_manager = self._get_ws_manager()
if not ws_manager:
return 0
# Build message payload
payload = {
"type": "event",
"payload": {
"id": event.id,
"event_type": event.event_type.value,
"source": event.source,
"task_id": event.task_id,
"agent_id": event.agent_id,
"timestamp": event.timestamp,
"data": event.data,
},
}
try:
# Broadcast to all connected clients
count = await ws_manager.broadcast_json(payload)
logger.debug("Broadcasted event %s to %d clients", event.id[:8], count)
return count
except Exception as exc:
logger.error("Failed to broadcast event: %s", exc)
return 0
def broadcast_sync(self, event: EventLogEntry) -> None:
"""Synchronous wrapper for broadcast.
Use this from synchronous code - it schedules the async broadcast
in the event loop if one is running.
"""
try:
asyncio.get_running_loop()
# Schedule in background, don't wait
asyncio.create_task(self.broadcast(event))
except RuntimeError:
# No event loop running, skip broadcast
pass
# Global singleton
event_broadcaster = EventBroadcaster()
# Event type to icon/emoji mapping
EVENT_ICONS = {
"task.created": "📝",
"task.bidding": "",
"task.assigned": "👤",
"task.started": "▶️",
"task.completed": "",
"task.failed": "",
"agent.joined": "🟢",
"agent.left": "🔴",
"agent.status_changed": "🔄",
"bid.submitted": "💰",
"auction.closed": "🏁",
"tool.called": "🔧",
"tool.completed": "⚙️",
"tool.failed": "💥",
"system.error": "⚠️",
"system.warning": "🔶",
"system.info": "",
"error.captured": "🐛",
"bug_report.created": "📋",
}
EVENT_LABELS = {
"task.created": "New task",
"task.bidding": "Bidding open",
"task.assigned": "Task assigned",
"task.started": "Task started",
"task.completed": "Task completed",
"task.failed": "Task failed",
"agent.joined": "Agent joined",
"agent.left": "Agent left",
"agent.status_changed": "Status changed",
"bid.submitted": "Bid submitted",
"auction.closed": "Auction closed",
"tool.called": "Tool called",
"tool.completed": "Tool completed",
"tool.failed": "Tool failed",
"system.error": "Error",
"system.warning": "Warning",
"system.info": "Info",
"error.captured": "Error captured",
"bug_report.created": "Bug report filed",
}
def get_event_icon(event_type: str) -> str:
"""Get emoji icon for event type."""
return EVENT_ICONS.get(event_type, "")
def get_event_label(event_type: str) -> str:
"""Get human-readable label for event type."""
return EVENT_LABELS.get(event_type, event_type)
def format_event_for_display(event: EventLogEntry) -> dict:
"""Format event for display in activity feed.
Returns dict with display-friendly fields.
"""
data = event.data or {}
# Build description based on event type
description = ""
if event.event_type.value == "task.created":
desc = data.get("description", "")
description = desc[:60] + "..." if len(desc) > 60 else desc
elif event.event_type.value == "task.assigned":
agent = event.agent_id[:8] if event.agent_id else "unknown"
bid = data.get("bid_sats", "?")
description = f"to {agent} ({bid} sats)"
elif event.event_type.value == "bid.submitted":
bid = data.get("bid_sats", "?")
description = f"{bid} sats"
elif event.event_type.value == "agent.joined":
persona = data.get("persona_id", "")
description = f"Persona: {persona}" if persona else "New agent"
else:
# Generic: use any string data
for key in ["message", "reason", "description"]:
if key in data:
val = str(data[key])
description = val[:60] + "..." if len(val) > 60 else val
break
return {
"id": event.id,
"icon": get_event_icon(event.event_type.value),
"label": get_event_label(event.event_type.value),
"type": event.event_type.value,
"source": event.source,
"description": description,
"timestamp": event.timestamp,
"time_short": event.timestamp[11:19] if event.timestamp else "",
"task_id": event.task_id,
"agent_id": event.agent_id,
}

View File

@@ -1,275 +0,0 @@
"""Ollama-based implementation of TimAgent interface.
This adapter wraps the existing Timmy Ollama agent to conform
to the substrate-agnostic TimAgent interface. It's the bridge
between the old codebase and the new embodiment-ready architecture.
Usage:
from timmy.agent_core import AgentIdentity, Perception
from timmy.agent_core.ollama_adapter import OllamaAgent
identity = AgentIdentity.generate("Timmy")
agent = OllamaAgent(identity)
perception = Perception.text("Hello!")
memory = agent.perceive(perception)
action = agent.reason("How should I respond?", [memory])
result = agent.act(action)
"""
from typing import Any
from timmy.agent import _resolve_model_with_fallback, create_timmy
from timmy.agent_core.interface import (
Action,
ActionType,
AgentCapability,
AgentEffect,
AgentIdentity,
Communication,
Memory,
Perception,
PerceptionType,
TimAgent,
)
class OllamaAgent(TimAgent):
"""TimAgent implementation using local Ollama LLM.
This is the production agent for Timmy Time v2. It uses
Ollama for reasoning and SQLite for memory persistence.
Capabilities:
- REASONING: LLM-based inference
- CODING: Code generation and analysis
- WRITING: Long-form content creation
- ANALYSIS: Data processing and insights
- COMMUNICATION: Multi-agent messaging
"""
def __init__(
self,
identity: AgentIdentity,
model: str | None = None,
effect_log: str | None = None,
require_vision: bool = False,
) -> None:
"""Initialize Ollama-based agent.
Args:
identity: Agent identity (persistent across sessions)
model: Ollama model to use (auto-resolves with fallback)
effect_log: Path to log agent effects (optional)
require_vision: Whether to select a vision-capable model
"""
super().__init__(identity)
# Resolve model with automatic pulling and fallback
resolved_model, is_fallback = _resolve_model_with_fallback(
requested_model=model,
require_vision=require_vision,
auto_pull=True,
)
if is_fallback:
import logging
logging.getLogger(__name__).info(
"OllamaAdapter using fallback model %s", resolved_model
)
# Initialize underlying Ollama agent
self._timmy = create_timmy(model=resolved_model)
# Set capabilities based on what Ollama can do
self._capabilities = {
AgentCapability.REASONING,
AgentCapability.CODING,
AgentCapability.WRITING,
AgentCapability.ANALYSIS,
AgentCapability.COMMUNICATION,
}
# Effect logging for audit/replay
self._effect_log = AgentEffect(effect_log) if effect_log else None
# Simple in-memory working memory (short term)
self._working_memory: list[Memory] = []
self._max_working_memory = 10
def perceive(self, perception: Perception) -> Memory:
"""Process perception and store in memory.
For text perceptions, we might do light preprocessing
(summarization, keyword extraction) before storage.
"""
# Create memory from perception
memory = Memory(
id=f"mem_{len(self._working_memory)}",
content={
"type": perception.type.name,
"data": perception.data,
"source": perception.source,
},
created_at=perception.timestamp,
tags=self._extract_tags(perception),
)
# Add to working memory
self._working_memory.append(memory)
if len(self._working_memory) > self._max_working_memory:
self._working_memory.pop(0) # FIFO eviction
# Log effect
if self._effect_log:
self._effect_log.log_perceive(perception, memory.id)
return memory
def reason(self, query: str, context: list[Memory]) -> Action:
"""Use LLM to reason and decide on action.
This is where the Ollama agent does its work. We construct
a prompt from the query and context, then interpret the
response as an action.
"""
# Build context string from memories
context_str = self._format_context(context)
# Construct prompt
prompt = f"""You are {self._identity.name}, an AI assistant.
Context from previous interactions:
{context_str}
Current query: {query}
Respond naturally and helpfully."""
# Run LLM inference
result = self._timmy.run(prompt, stream=False)
response_text = result.content if hasattr(result, "content") else str(result)
# Create text response action
action = Action.respond(response_text, confidence=0.9)
# Log effect
if self._effect_log:
self._effect_log.log_reason(query, action.type)
return action
def act(self, action: Action) -> Any:
"""Execute action in the Ollama substrate.
For text actions, the "execution" is just returning the
text (already generated during reasoning). For future
action types (MOVE, SPEAK), this would trigger the
appropriate Ollama tool calls.
"""
result = None
if action.type == ActionType.TEXT:
result = action.payload
elif action.type == ActionType.SPEAK:
# Would call TTS here
result = {"spoken": action.payload, "tts_engine": "pyttsx3"}
elif action.type == ActionType.CALL:
# Would make API call
result = {"status": "not_implemented", "payload": action.payload}
else:
result = {"error": f"Action type {action.type} not supported by OllamaAgent"}
# Log effect
if self._effect_log:
self._effect_log.log_act(action, result)
return result
def remember(self, memory: Memory) -> None:
"""Store memory in working memory.
Adds the memory to the sliding window and bumps its importance.
"""
memory.touch()
# Deduplicate by id
self._working_memory = [m for m in self._working_memory if m.id != memory.id]
self._working_memory.append(memory)
# Evict oldest if over capacity
if len(self._working_memory) > self._max_working_memory:
self._working_memory.pop(0)
def recall(self, query: str, limit: int = 5) -> list[Memory]:
"""Retrieve relevant memories.
Simple keyword matching for now. Future: vector similarity.
"""
query_lower = query.lower()
scored = []
for memory in self._working_memory:
score = 0
content_str = str(memory.content).lower()
# Simple keyword overlap
query_words = set(query_lower.split())
content_words = set(content_str.split())
overlap = len(query_words & content_words)
score += overlap
# Boost recent memories
score += memory.importance
scored.append((score, memory))
# Sort by score descending
scored.sort(key=lambda x: x[0], reverse=True)
# Return top N
return [m for _, m in scored[:limit]]
def communicate(self, message: Communication) -> bool:
"""Send message to another agent.
Swarm comms removed — inter-agent communication will be handled
by the unified brain memory layer.
"""
return False
def _extract_tags(self, perception: Perception) -> list[str]:
"""Extract searchable tags from perception."""
tags = [perception.type.name, perception.source]
if perception.type == PerceptionType.TEXT:
# Simple keyword extraction
text = str(perception.data).lower()
keywords = ["code", "bug", "help", "question", "task"]
for kw in keywords:
if kw in text:
tags.append(kw)
return tags
def _format_context(self, memories: list[Memory]) -> str:
"""Format memories into context string for prompt."""
if not memories:
return "No previous context."
parts = []
for mem in memories[-5:]: # Last 5 memories
if isinstance(mem.content, dict):
data = mem.content.get("data", "")
parts.append(f"- {data}")
else:
parts.append(f"- {mem.content}")
return "\n".join(parts)
def get_effect_log(self) -> list[dict] | None:
"""Export effect log if logging is enabled."""
if self._effect_log:
return self._effect_log.export()
return None

View File

@@ -1,105 +0,0 @@
"""Agent-to-agent messaging for the Timmy serve layer.
Provides a simple message-passing interface that allows agents to
communicate with each other. Messages are routed through the swarm
comms layer when available, or stored in an in-memory queue for
single-process operation.
"""
import logging
import uuid
from collections import deque
from dataclasses import dataclass, field
from datetime import UTC, datetime
logger = logging.getLogger(__name__)
@dataclass
class AgentMessage:
id: str = field(default_factory=lambda: str(uuid.uuid4()))
from_agent: str = ""
to_agent: str = ""
content: str = ""
message_type: str = "text" # text | command | response | error
timestamp: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
replied: bool = False
class InterAgentMessenger:
"""In-memory message queue for agent-to-agent communication."""
def __init__(self, max_queue_size: int = 1000) -> None:
self._queues: dict[str, deque[AgentMessage]] = {}
self._max_size = max_queue_size
self._all_messages: list[AgentMessage] = []
def send(
self,
from_agent: str,
to_agent: str,
content: str,
message_type: str = "text",
) -> AgentMessage:
"""Send a message from one agent to another."""
msg = AgentMessage(
from_agent=from_agent,
to_agent=to_agent,
content=content,
message_type=message_type,
)
queue = self._queues.setdefault(to_agent, deque(maxlen=self._max_size))
queue.append(msg)
self._all_messages.append(msg)
logger.info(
"Message %s%s: %s (%s)",
from_agent,
to_agent,
content[:50],
message_type,
)
return msg
def receive(self, agent_id: str, limit: int = 10) -> list[AgentMessage]:
"""Receive pending messages for an agent (FIFO, non-destructive peek)."""
queue = self._queues.get(agent_id, deque())
return list(queue)[:limit]
def pop(self, agent_id: str) -> AgentMessage | None:
"""Pop the oldest message from an agent's queue."""
queue = self._queues.get(agent_id, deque())
if not queue:
return None
return queue.popleft()
def pop_all(self, agent_id: str) -> list[AgentMessage]:
"""Pop all pending messages for an agent."""
queue = self._queues.get(agent_id, deque())
messages = list(queue)
queue.clear()
return messages
def broadcast(self, from_agent: str, content: str, message_type: str = "text") -> int:
"""Broadcast a message to all known agents. Returns count sent."""
count = 0
for agent_id in list(self._queues.keys()):
if agent_id != from_agent:
self.send(from_agent, agent_id, content, message_type)
count += 1
return count
def history(self, limit: int = 50) -> list[AgentMessage]:
"""Return recent message history across all agents."""
return self._all_messages[-limit:]
def clear(self, agent_id: str | None = None) -> None:
"""Clear message queue(s)."""
if agent_id:
self._queues.pop(agent_id, None)
else:
self._queues.clear()
self._all_messages.clear()
# Module-level singleton
messenger = InterAgentMessenger()