Files
2125_GCE/glyphos/events.py
T
GlyphRunner System 9792449157 Implement GlyphOS Event System
Add lightweight, in-process event bus with Cognitive Kernel integration:

New Components:
- glyphos/events.py: EventBus class + functional API
  * EventBus: publish/subscribe pattern with history
  * Event type definitions (EventType literal)
  * Singleton: get_event_bus(), emit(), on()
  * History filtering and limits
  * Graceful handler error handling

- tests/test_events.py: Comprehensive test suite (16 tests, 100% pass)
  * EventBus subscription/publishing/history
  * Global singleton behavior
  * Functional API (on, emit, get_event_bus)
  * Kernel integration tests
  * Cognition event emission tests

Modified:
- glyphos/cognitive_kernel.py: Event emissions at key points
  * kernel.warmup.completed: After warmup() completes
  * cognition.started: At start of execute_gx()
  * cognition.completed: After execute_gx() completes
  * glyph.resonance.updated: When glyph resonance present

- glyphos/__init__.py: Export events module

Test Results:
- Registry tests:          12/12 
- Bridge tests:            10/10 
- Kernel tests:             8/8  
- Event system tests:      16/16  (NEW)
- Integration tests:        6/6  
- Total:                   52/52 

No breaking changes - all 36 existing tests still pass.
2026-05-20 18:11:25 -04:00

184 lines
5.3 KiB
Python

"""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)