"""GlyphOS Event System Lightweight, in-process event bus for symbolic events. Captures cognition-related events from Cognitive Kernel / LAIN. Exposes glyph activation and resonance changes as first-class events. """ import time from typing import Callable, Dict, List, Optional, TypedDict, Literal, Any # Event type definitions EventType = Literal[ "cognition.started", "cognition.completed", "glyph.activation.changed", "glyph.resonance.updated", "kernel.warmup.completed", ] class Event(TypedDict, total=False): """Event structure with optional fields.""" type: EventType timestamp: float payload: Dict[str, Any] class EventBus: """Lightweight in-process event bus. Manages subscriptions and publishing of symbolic events. Maintains event history for introspection. """ def __init__(self) -> None: """Initialize an empty EventBus.""" self._subscribers: Dict[EventType, List[Callable[[Event], None]]] = {} self._history: List[Event] = [] def subscribe(self, event_type: EventType, handler: Callable[[Event], None]) -> None: """Register a handler for a specific event type. Args: event_type: Type of event to listen for handler: Callable that takes an Event and returns None """ if event_type not in self._subscribers: self._subscribers[event_type] = [] if handler not in self._subscribers[event_type]: self._subscribers[event_type].append(handler) def unsubscribe(self, event_type: EventType, handler: Callable[[Event], None]) -> None: """Remove a handler if present. Args: event_type: Type of event to stop listening for handler: The handler to remove """ if event_type in self._subscribers: try: self._subscribers[event_type].remove(handler) except ValueError: pass def publish(self, event_type: EventType, payload: Dict[str, Any]) -> Event: """Create an Event, append to history, and invoke all handlers. Args: event_type: Type of event payload: Event payload data Returns: The published Event (with timestamp added) """ event: Event = { "type": event_type, "timestamp": time.time(), "payload": payload, } # Append to history self._history.append(event) # Invoke handlers in registration order if event_type in self._subscribers: for handler in self._subscribers[event_type]: try: handler(event) except Exception as e: # Silently catch handler errors to prevent cascade failures # In production, could log to a logger pass return event def get_history( self, event_type: Optional[EventType] = None, limit: int = 100 ) -> List[Event]: """Return recent events, optionally filtered by type. Args: event_type: If provided, filter to only this event type limit: Maximum number of events to return Returns: List of recent events (newest last) """ if event_type is None: # Return all events up to limit return self._history[-limit:] if self._history else [] else: # Filter by type and limit filtered = [e for e in self._history if e.get("type") == event_type] return filtered[-limit:] if filtered else [] def clear_history(self) -> None: """Clear all stored events.""" self._history = [] def get_subscriber_count(self, event_type: Optional[EventType] = None) -> int: """Get number of subscribers (for testing). Args: event_type: If provided, count for this type only Returns: Number of subscribers """ if event_type is None: return sum(len(handlers) for handlers in self._subscribers.values()) else: return len(self._subscribers.get(event_type, [])) # Global singleton event bus instance _GLOBAL_BUS: Optional[EventBus] = None def get_event_bus() -> EventBus: """Get or create the singleton EventBus instance. On first call, creates a new EventBus. Returns same instance on subsequent calls. Returns: Singleton EventBus instance """ global _GLOBAL_BUS if _GLOBAL_BUS is None: _GLOBAL_BUS = EventBus() return _GLOBAL_BUS def emit(event_type: EventType, payload: Dict[str, Any]) -> Event: """Convenience function: publish an event on the global bus. Equivalent to: get_event_bus().publish(event_type, payload) Args: event_type: Type of event payload: Event payload Returns: The published Event """ bus = get_event_bus() return bus.publish(event_type, payload) def on(event_type: EventType, handler: Callable[[Event], None]) -> None: """Convenience function: subscribe to an event on the global bus. Equivalent to: get_event_bus().subscribe(event_type, handler) Args: event_type: Type of event to listen for handler: Handler callback """ bus = get_event_bus() bus.subscribe(event_type, handler)