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.
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user