diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index ab657273..413f1840 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ["3.11", "3.12", "3.13"]
+ python-version: ["3.12", "3.13"]
fail-fast: false
defaults:
run:
diff --git a/.gitignore b/.gitignore
index 2e7bc43e..0a642882 100644
--- a/.gitignore
+++ b/.gitignore
@@ -141,3 +141,6 @@ cython_debug/
# Reference directory - ignore all reference projects
reference/
+
+# Others
+.pdm-python
diff --git a/README.md b/README.md
index 5b31ffa1..60b7de03 100644
--- a/README.md
+++ b/README.md
@@ -84,6 +84,8 @@ BUB_API_KEY=your-api-key-here
BUB_MODEL=gpt-4 # AI model to use
BUB_API_BASE=https://api.custom.ai # Custom API endpoint
BUB_MAX_TOKENS=4000 # Maximum response tokens
+BUB_TIMEOUT_SECONDS=30 # Timeout for AI responses (seconds)
+BUB_MAX_ITERATIONS=10 # Maximum tool execution cycles
BUB_WORKSPACE_PATH=/path/to/work # Default workspace
BUB_SYSTEM_PROMPT="custom prompt" # Custom system prompt
```
diff --git a/docs/index.md b/docs/index.md
index d21f1705..83dd7553 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -36,16 +36,6 @@ export BUB_API_KEY="sk-..."
export BUB_PROVIDER="anthropic"
export BUB_MODEL_NAME="claude-3-5-sonnet-20241022"
export BUB_API_KEY="your-anthropic-key"
-
-# For local models with Ollama
-export BUB_PROVIDER="ollama"
-export BUB_MODEL_NAME="llama3"
-# No API key needed for local models
-
-# For Groq (fast inference)
-export BUB_PROVIDER="groq"
-export BUB_MODEL_NAME="llama3-8b-8192"
-export BUB_API_KEY="gsk_..."
```
### Usage
@@ -101,6 +91,8 @@ Configure Bub via environment variables or a `.env` file:
| `BUB_MAX_TOKENS` | Maximum response tokens (optional) | `4000` |
| `BUB_WORKSPACE_PATH` | Default workspace directory (optional) | `/path/to/work` |
| `BUB_SYSTEM_PROMPT` | Custom system prompt (optional) | `"You are a helpful assistant..."` |
+| `BUB_TIMEOUT_SECONDS` | Timeout for AI responses (seconds) | `30` |
+| `BUB_MAX_ITERATIONS` | Maximum number of tool execution cycles | `10` |
### Custom System Prompt with BUB.md
diff --git a/docs/internals/event-system-architecture.md b/docs/internals/event-system-architecture.md
new file mode 100644
index 00000000..f0b3b687
--- /dev/null
+++ b/docs/internals/event-system-architecture.md
@@ -0,0 +1,420 @@
+# Event System Architecture
+
+A clean, adapter-based event system that supports both string and Enum event types, with native eventure integration for powerful event visualization and analysis.
+
+## 1. Quick Start
+
+```python
+from bub.events import BaseEvent, DomainEventType, register_event, publish, subscribe
+
+# Define event types
+class UserEventType(DomainEventType):
+ CREATED = "user.created"
+ UPDATED = "user.updated"
+
+# Create events
+@register_event
+class UserCreatedEvent(BaseEvent):
+ event_type = UserEventType.CREATED
+ user_id: str
+ username: str
+
+# Publish and subscribe
+@subscribe(UserEventType.CREATED)
+def handle_user_created(event):
+ print(f"User created: {event.data['username']}")
+
+publish(UserCreatedEvent(user_id="123", username="john"))
+```
+
+## 2. Architecture
+
+The event system is built with a clean, modular architecture:
+
+```
+src/bub/events/
+├── __init__.py # Main public API
+├── types.py # Type definitions (EventType, DomainEventType)
+├── models.py # Event model definitions (BaseEvent)
+├── registry.py # Schema registry and management
+├── adapters.py # Adapter interfaces
+├── system.py # Main EventSystem facade
+├── exceptions.py # Exception hierarchy
+└── backends/
+ ├── __init__.py # Backend exports
+ └── eventure.py # Eventure backend implementation
+```
+
+## 3. Core Features
+
+- **Flexible Event Types**: Support for both `str` and `Enum` event types
+- **Type Safety**: Full type hints with `EventType = str | Enum`
+- **Clean Architecture**: Adapter pattern for pluggable backends
+- **Global Singleton**: Convenient global event system
+- **Schema Registry**: Automatic event schema registration
+- **Native Eventure**: Built-in visualization and cascade analysis
+- **Multi-Bus Support**: Cross-bus event communication
+
+## 4. Design Patterns
+
+### 4.1 Event Type Flexibility
+
+The system supports both string and Enum event types seamlessly:
+
+```python
+from bub.events.types import DomainEventType, EventType
+from enum import Enum
+
+# String-based event types
+class StringEvents:
+ USER_CREATED = "user.created"
+ USER_UPDATED = "user.updated"
+
+# Enum-based event types
+class UserEventType(DomainEventType):
+ CREATED = "user.created"
+ UPDATED = "user.updated"
+
+# Both work the same way
+event_type1: EventType = StringEvents.USER_CREATED
+event_type2: EventType = UserEventType.CREATED
+```
+
+### 4.2 Event Model Definition
+
+Events are defined as Pydantic models with automatic schema registration:
+
+```python
+from bub.events import BaseEvent, register_event
+
+@register_event
+class UserCreatedEvent(BaseEvent):
+ event_type = UserEventType.CREATED
+ user_id: str
+ username: str
+ email: str
+
+@register_event
+class UserUpdatedEvent(BaseEvent):
+ event_type = UserEventType.UPDATED
+ user_id: str
+ changes: dict[str, str]
+```
+
+### 4.3 Event-Driven Flow Patterns
+
+#### 4.3.1 Basic Event Flow
+
+```python
+from bub.events import publish, subscribe
+
+# Define event types
+class MockEventType(DomainEventType):
+ TEST_ACTION = "test.action"
+ TEST_RESPONSE = "test.response"
+
+@register_event
+class MockActionEvent(BaseEvent):
+ event_type = MockEventType.TEST_ACTION
+ action: str
+ value: int
+
+@register_event
+class MockResponseEvent(BaseEvent):
+ event_type = MockEventType.TEST_RESPONSE
+ response: str
+ value: int
+
+# Event handlers
+@subscribe(MockEventType.TEST_ACTION)
+def handle_action(event):
+ event_data = event.data
+ print(f"Processing action: {event_data['action']}")
+
+ # Auto-generate response
+ response = MockResponseEvent(
+ response=f"Processed {event_data['action']}",
+ value=event_data['value'] * 2
+ )
+ publish(response)
+
+@subscribe(MockEventType.TEST_RESPONSE)
+def handle_response(event):
+ event_data = event.data
+ print(f"Response: {event_data['response']}")
+
+# Trigger the flow
+action = MockActionEvent(action="click", value=5)
+publish(action)
+# Output:
+# Processing action: click
+# Response: Processed click
+```
+
+#### 4.3.2 Event Cascade Pattern
+
+```python
+@subscribe(MockEventType.TEST_ACTION)
+def trigger_cascade(event):
+ event_data = event.data
+ print(f"1. Action: {event_data['action']}")
+
+ # Trigger response
+ response = MockResponseEvent(
+ response=f"Handled {event_data['action']}",
+ value=event_data['value']
+ )
+ publish(response)
+
+@subscribe(MockEventType.TEST_RESPONSE)
+def continue_cascade(event):
+ event_data = event.data
+ print(f"2. Response: {event_data['response']}")
+
+ # Trigger processed
+ processed = MockProcessedEvent(
+ result=f"Completed {event_data['response']}",
+ value=event_data['value'] + 10
+ )
+ publish(processed)
+
+@subscribe(MockEventType.TEST_PROCESSED)
+def end_cascade(event):
+ event_data = event.data
+ print(f"3. Processed: {event_data['result']}")
+
+# Start cascade
+action = MockActionEvent(action="submit", value=10)
+publish(action)
+# Output:
+# 1. Action: submit
+# 2. Response: Handled submit
+# 3. Processed: Completed Handled submit
+```
+
+### 4.4 Multi-Bus Communication
+
+The system supports communication across multiple event buses:
+
+```python
+from bub.events import EventSystem
+
+# Create separate event systems for different domains
+user_system = EventSystem()
+data_system = EventSystem()
+notification_system = EventSystem()
+
+cross_bus_events = []
+
+# User bus handler
+@user_system.subscribe(MockEventType.TEST_ACTION)
+def user_handler(event):
+ event_data = event.data
+ cross_bus_events.append(f"User bus: {event_data['action']}")
+
+ # Send to data bus
+ response = MockResponseEvent(
+ response=f"From user: {event_data['action']}",
+ value=event_data['value']
+ )
+ data_system.publish(response)
+
+# Data bus handler
+@data_system.subscribe(MockEventType.TEST_RESPONSE)
+def data_handler(event):
+ event_data = event.data
+ cross_bus_events.append(f"Data bus: {event_data['response']}")
+
+# Start cross-bus flow
+action = MockActionEvent(action="upload_file", value=5)
+user_system.publish(action)
+
+print(cross_bus_events)
+# Output: ['User bus: upload_file', 'Data bus: From user: upload_file']
+```
+
+### 4.5 Event Visualization and Analysis
+
+The system provides native eventure integration for powerful event analysis:
+
+```python
+from bub.events import get_event_system
+
+# Get the event system and query interface
+event_system = get_event_system()
+event_query = event_system.get_query()
+
+# Generate some events
+for action in ["login", "search", "logout"]:
+ event = MockActionEvent(action=action, value=len(action))
+ publish(event)
+
+# Use native eventure visualization
+print("Event Cascade Visualization:")
+event_query.print_event_cascade()
+
+# Analyze specific cascades
+all_events = event_query.get_root_events()
+if all_events:
+ print(f"\nCascade for first event:")
+ event_query.print_single_cascade(all_events[0])
+```
+
+## 5. API Reference
+
+### 5.1 Core Classes
+
+| Class | Purpose |
+|-------|---------|
+| `EventSystem` | Main facade for event operations |
+| `BaseEvent` | Base class for all events |
+| `DomainEventType` | Base for domain event types |
+| `EventBusAdapter` | Abstract adapter interface |
+
+### 5.2 Type Definitions
+
+| Type | Description |
+|------|-------------|
+| `EventType` | `Union[str, Enum]` - Flexible event type support |
+| `EventHandler` | `Callable[[Event], None]` - Event handler function |
+| `Subscription` | `Callable[[], None]` - Subscription object |
+
+### 5.3 Global Functions
+
+| Function | Purpose |
+|----------|---------|
+| `publish(event, bus="default")` | Publish event using global system |
+| `subscribe(event_type)` | Subscribe to event type using global system |
+| `get_event_system()` | Get global event system instance |
+| `get_bus()` | Get event bus instance |
+| `get_log()` | Get event log instance |
+| `get_query()` | Get query interface for analysis |
+
+### 5.4 Registration Functions
+
+| Function | Purpose |
+|----------|---------|
+| `@register_event` | Decorator for registering event schemas |
+| `get_event_schema(event_type)` | Get schema for event type |
+| `get_event_schema_or_raise(event_type)` | Get schema or raise exception |
+
+## 6. Best Practices
+
+### 6.1 Event Type Design
+
+```python
+# Good: Use domain.action pattern
+class UserEventType(DomainEventType):
+ CREATED = "user.created"
+ UPDATED = "user.updated"
+ DELETED = "user.deleted"
+
+# Avoid: Inconsistent naming
+class BadEvents:
+ USER_CREATED = "user_created" # Use dots, not underscores
+ UPDATE_USER = "update_user" # Use domain.action pattern
+```
+
+### 6.2 Event Data Access
+
+```python
+# Correct: Access data from eventure Event objects
+@subscribe(UserEventType.CREATED)
+def handle_user_created(event):
+ event_data = event.data
+ user_id = event_data['user_id']
+ username = event_data['username']
+
+# Wrong: Direct attribute access
+@subscribe(UserEventType.CREATED)
+def handle_user_created(event):
+ user_id = event.user_id # This won't work!
+```
+
+### 6.3 Event Cascade Design
+
+```python
+# Good: Clear cascade flow
+@subscribe(UserEventType.CREATED)
+def handle_user_created(event):
+ # Process user creation
+ publish(UserProfileCreatedEvent(user_id=event.data['user_id']))
+
+@subscribe(UserProfileEventType.CREATED)
+def handle_profile_created(event):
+ # Send welcome notification
+ publish(NotificationSentEvent(user_id=event.data['user_id']))
+
+# Avoid: Complex nested cascades
+@subscribe(UserEventType.CREATED)
+def handle_user_created(event):
+ # Don't create multiple cascades in one handler
+ publish(Event1())
+ publish(Event2())
+ publish(Event3())
+```
+
+### 6.4 Testing
+
+```python
+from bub.events import EventSystem, set_event_system
+
+class TestEventSystem:
+ def setup_method(self):
+ """Reset event system before each test."""
+ set_event_system(EventSystem())
+
+ def test_event_flow(self):
+ events_received = []
+
+ @subscribe(MockEventType.TEST_ACTION)
+ def handle_action(event):
+ events_received.append(event.data['action'])
+
+ publish(MockActionEvent(action="test", value=1))
+ assert events_received == ["test"]
+```
+
+## 7. Advanced Features
+
+### 7.1 Custom Adapters
+
+```python
+from bub.events.adapters import EventBusAdapter
+
+class CustomAdapter(EventBusAdapter):
+ def publish(self, event, parent_event=None, bus_name=None):
+ # Custom implementation
+ print(f"Custom publish: {event.type}")
+ return event
+
+ def subscribe(self, event_type, handler, bus_name=None):
+ # Custom subscription logic
+ print(f"Custom subscribe: {event_type}")
+ return lambda: None # Return unsubscribe function
+```
+
+### 7.2 Event Type Normalization
+
+```python
+from bub.events.types import normalize_event_type
+
+# All these normalize to the same string:
+normalize_event_type("user.created") # "user.created"
+normalize_event_type(UserEventType.CREATED) # "user.created"
+normalize_event_type(StringEvents.USER_CREATED) # "user.created"
+```
+
+### 7.3 Domain Event Utilities
+
+```python
+@register_event
+class UserCreatedEvent(BaseEvent):
+ event_type = UserEventType.CREATED # "user.created"
+
+event = UserCreatedEvent(user_id="123")
+print(event.get_domain()) # "user"
+print(event.get_action()) # "created"
+```
+
+The Event System provides a powerful, flexible foundation for building event-driven applications with clean architecture and excellent developer experience.
diff --git a/env.example b/env.example
index 96708eb1..118117a8 100644
--- a/env.example
+++ b/env.example
@@ -22,6 +22,14 @@ BUB_API_KEY=your_api_key_here
# Optional: Maximum tokens for AI responses (default varies by model)
# BUB_MAX_TOKENS=4096
+# Optional: Timeout for AI responses in seconds (default: 30)
+# Increase this value if you're experiencing timeout issues with complex requests
+# BUB_TIMEOUT_SECONDS=60
+
+# Optional: Maximum number of tool execution cycles (default: 10)
+# Increase this value for complex multi-step tasks, decrease for faster responses
+# BUB_MAX_ITERATIONS=15
+
# Optional: Custom workspace path (default: current directory)
# BUB_WORKSPACE_PATH=/path/to/your/workspace
@@ -39,18 +47,3 @@ BUB_API_KEY=your_api_key_here
# BUB_PROVIDER=anthropic
# BUB_MODEL_NAME=claude-3-5-sonnet-20241022
# BUB_API_KEY=sk-ant-...
-
-# Local Ollama (no API key needed)
-# BUB_PROVIDER=ollama
-# BUB_MODEL_NAME=llama3
-# # BUB_API_KEY not needed
-
-# Groq (fast inference)
-# BUB_PROVIDER=groq
-# BUB_MODEL_NAME=llama3-8b-8192
-# BUB_API_KEY=gsk_...
-
-# Mistral AI
-# BUB_PROVIDER=mistral
-# BUB_MODEL_NAME=mistral-large-latest
-# BUB_API_KEY=...
diff --git a/examples/event-system-how-it-works.py b/examples/event-system-how-it-works.py
new file mode 100644
index 00000000..116dec27
--- /dev/null
+++ b/examples/event-system-how-it-works.py
@@ -0,0 +1,458 @@
+"""Simple Bub Event System Examples - Core Patterns and Features.
+
+This module demonstrates the essential event system capabilities:
+1. Core event-driven patterns
+2. Multi-cross-bus communication
+3. Event visualization and cascade analysis
+4. Simple, user-friendly examples
+"""
+
+from __future__ import annotations
+
+import time
+
+from bub.events import (
+ BaseEvent,
+ EventSystem,
+ get_event_system,
+ publish,
+ register_event,
+ subscribe,
+)
+from bub.events.types import DomainEventType
+
+# ============================================================================
+# SIMPLE EVENT TYPES
+# ============================================================================
+
+
+class SimpleEventType(DomainEventType):
+ """Simple event types for demonstration."""
+
+ USER_ACTION = "user.action"
+ SYSTEM_RESPONSE = "system.response"
+ DATA_PROCESSED = "data.processed"
+ NOTIFICATION_SENT = "notification.sent"
+
+
+@register_event
+class UserActionEvent(BaseEvent):
+ """User performs an action."""
+
+ event_type = SimpleEventType.USER_ACTION
+ action: str
+ user_id: str
+ timestamp: float
+
+
+@register_event
+class SystemResponseEvent(BaseEvent):
+ """System responds to user action."""
+
+ event_type = SimpleEventType.SYSTEM_RESPONSE
+ response: str
+ user_id: str
+ timestamp: float
+
+
+@register_event
+class DataProcessedEvent(BaseEvent):
+ """Data has been processed."""
+
+ event_type = SimpleEventType.DATA_PROCESSED
+ data_type: str
+ result: str
+ timestamp: float
+
+
+@register_event
+class NotificationSentEvent(BaseEvent):
+ """Notification was sent."""
+
+ event_type = SimpleEventType.NOTIFICATION_SENT
+ message: str
+ recipient: str
+ timestamp: float
+
+
+# ============================================================================
+# CORE EVENT-DRIVEN PATTERNS
+# ============================================================================
+
+
+def demonstrate_basic_event_flow():
+ """Demonstrate basic event-driven communication."""
+ print("\nBasic Event Flow")
+ print("=" * 30)
+
+ # Track events
+ events_log = []
+
+ @subscribe(SimpleEventType.USER_ACTION)
+ def handle_user_action(event):
+ # Access event data from eventure Event object
+ event_data = event.data
+ events_log.append(f"User action: {event_data['action']}")
+
+ # System responds automatically
+ response = SystemResponseEvent(
+ response=f"Processed: {event_data['action']}", user_id=event_data["user_id"], timestamp=time.time()
+ )
+ publish(response)
+
+ @subscribe(SimpleEventType.SYSTEM_RESPONSE)
+ def handle_system_response(event):
+ event_data = event.data
+ events_log.append(f"System response: {event_data['response']}")
+
+ # User performs action
+ user_action = UserActionEvent(action="click_button", user_id="user123", timestamp=time.time())
+
+ print("User clicks button...")
+ publish(user_action)
+
+ # Show event flow
+ for event in events_log:
+ print(event)
+
+ print("Event-driven flow completed!")
+
+
+def demonstrate_event_cascade():
+ """Demonstrate how events cascade through the system."""
+ print("\nEvent Cascade")
+ print("=" * 30)
+
+ cascade_log = []
+
+ @subscribe(SimpleEventType.USER_ACTION)
+ def trigger_cascade(event):
+ event_data = event.data
+ cascade_log.append(f"1. User action: {event_data['action']}")
+
+ # Trigger data processing
+ data_event = DataProcessedEvent(
+ data_type="user_input", result=f"Processed {event_data['action']}", timestamp=time.time()
+ )
+ publish(data_event)
+
+ @subscribe(SimpleEventType.DATA_PROCESSED)
+ def handle_data_processed(event):
+ event_data = event.data
+ cascade_log.append(f"2. Data processed: {event_data['result']}")
+
+ # Trigger notification
+ notification = NotificationSentEvent(
+ message=f"Your {event_data['data_type']} was processed", recipient="user123", timestamp=time.time()
+ )
+ publish(notification)
+
+ @subscribe(SimpleEventType.NOTIFICATION_SENT)
+ def handle_notification(event):
+ event_data = event.data
+ cascade_log.append(f"3. Notification sent: {event_data['message']}")
+
+ # Start the cascade
+ print("User performs action...")
+ user_action = UserActionEvent(action="submit_form", user_id="user123", timestamp=time.time())
+ publish(user_action)
+
+ # Show cascade
+ for step in cascade_log:
+ print(step)
+
+ print("Event cascade completed!")
+
+
+# ============================================================================
+# MULTI-CROSS-BUS COMMUNICATION
+# ============================================================================
+
+
+def demonstrate_multi_bus_communication():
+ """Demonstrate communication across multiple event buses."""
+ print("\nMulti-Bus Communication")
+ print("=" * 40)
+
+ # Create separate event systems for different domains
+ user_system = EventSystem()
+ data_system = EventSystem()
+ notification_system = EventSystem()
+
+ # Track cross-bus communication
+ cross_bus_log = []
+
+ # User bus handlers
+ @user_system.subscribe(SimpleEventType.USER_ACTION)
+ def handle_user_action(event):
+ event_data = event.data
+ cross_bus_log.append(f"User bus: {event_data['action']}")
+
+ # Send to data bus
+ data_event = DataProcessedEvent(
+ data_type="user_action", result=f"Processing {event_data['action']}", timestamp=time.time()
+ )
+ data_system.publish(data_event)
+
+ # Data bus handlers
+ @data_system.subscribe(SimpleEventType.DATA_PROCESSED)
+ def handle_data_processed(event):
+ event_data = event.data
+ cross_bus_log.append(f"Data bus: {event_data['result']}")
+
+ # Send to notification bus
+ notification = NotificationSentEvent(
+ message=f"Data processed: {event_data['result']}", recipient="user123", timestamp=time.time()
+ )
+ notification_system.publish(notification)
+
+ # Notification bus handlers
+ @notification_system.subscribe(SimpleEventType.NOTIFICATION_SENT)
+ def handle_notification(event):
+ event_data = event.data
+ cross_bus_log.append(f"Notification bus: {event_data['message']}")
+
+ # Start cross-bus flow
+ print("Starting cross-bus communication...")
+ user_action = UserActionEvent(action="upload_file", user_id="user123", timestamp=time.time())
+ user_system.publish(user_action)
+
+ # Show cross-bus flow
+ for step in cross_bus_log:
+ print(step)
+
+ print("Multi-bus communication completed!")
+
+
+def demonstrate_single_system_multi_bus():
+ """Demonstrate multiple buses within a single event system."""
+ print("\nSingle System Multi-Bus Communication")
+ print("=" * 45)
+
+ # Create a single event system with multiple buses
+ event_system = EventSystem()
+
+ # Create additional buses within the same system
+ event_system.create_bus("user")
+ event_system.create_bus("data")
+ event_system.create_bus("notification")
+
+ # Track multi-bus communication
+ multi_bus_log = []
+
+ # Subscribe to events on different buses within the same system
+ @event_system.subscribe(SimpleEventType.USER_ACTION, bus="user")
+ def handle_user_action(event):
+ event_data = event.data
+ multi_bus_log.append(f"User bus: {event_data['action']}")
+
+ # Publish to data bus within the same system
+ data_event = DataProcessedEvent(
+ data_type="user_action", result=f"Processing {event_data['action']}", timestamp=time.time()
+ )
+ event_system.publish(data_event, bus="data")
+
+ @event_system.subscribe(SimpleEventType.DATA_PROCESSED, bus="data")
+ def handle_data_processed(event):
+ event_data = event.data
+ multi_bus_log.append(f"Data bus: {event_data['result']}")
+
+ # Publish to notification bus within the same system
+ notification = NotificationSentEvent(
+ message=f"Data processed: {event_data['result']}", recipient="user123", timestamp=time.time()
+ )
+ event_system.publish(notification, bus="notification")
+
+ @event_system.subscribe(SimpleEventType.NOTIFICATION_SENT, bus="notification")
+ def handle_notification(event):
+ event_data = event.data
+ multi_bus_log.append(f"Notification bus: {event_data['message']}")
+
+ # Start multi-bus flow within single system
+ print("Starting single system multi-bus communication...")
+ user_action = UserActionEvent(action="process_data", user_id="user123", timestamp=time.time())
+ event_system.publish(user_action, bus="user")
+
+ # Show multi-bus flow
+ for step in multi_bus_log:
+ print(step)
+
+ print("Single system multi-bus communication completed!")
+
+ # Demonstrate bus isolation
+ print("\nBus Isolation Test:")
+ isolation_log = []
+
+ @event_system.subscribe(SimpleEventType.USER_ACTION, bus="default")
+ def handle_default_bus(event):
+ isolation_log.append("Default bus handler triggered")
+
+ @event_system.subscribe(SimpleEventType.USER_ACTION, bus="user")
+ def handle_user_bus(event):
+ isolation_log.append("User bus handler triggered")
+
+ # Publish to user bus only
+ event_system.publish(UserActionEvent(action="test", user_id="user123", timestamp=time.time()), bus="user")
+
+ print("Bus isolation results:")
+ for log in isolation_log:
+ print(f" - {log}")
+
+
+# ============================================================================
+# EVENT VISUALIZATION AND ANALYSIS
+# ============================================================================
+
+
+def demonstrate_event_visualization():
+ """Demonstrate event visualization using native eventure features."""
+ print("\nEvent Visualization (Native Eventure)")
+ print("=" * 45)
+
+ # Get the event system and its underlying eventure components
+ event_system = get_event_system()
+ event_query = event_system.get_query()
+
+ # Generate events for visualization using default bus
+ print("Generating events for visualization...")
+
+ actions = ["login", "search", "download", "logout"]
+ for action in actions:
+ # User action events
+ user_action = UserActionEvent(action=action, user_id="user123", timestamp=time.time())
+ publish(user_action)
+ time.sleep(0.1)
+
+ # System response events
+ response = SystemResponseEvent(response=f"Processed: {action}", user_id="user123", timestamp=time.time())
+ publish(response)
+ time.sleep(0.1)
+
+ # Data processed events
+ data_event = DataProcessedEvent(data_type="user_input", result=f"Processed {action}", timestamp=time.time())
+ publish(data_event)
+ time.sleep(0.1)
+
+ # Notification events
+ notification = NotificationSentEvent(
+ message="Your user_input was processed", recipient="user123", timestamp=time.time()
+ )
+ publish(notification)
+ time.sleep(0.1)
+
+ # Use only native eventure visualization
+ print("\nNative Eventure Visualization:")
+ print("-" * 50)
+ event_query.print_event_cascade()
+
+
+def demonstrate_cascade_analysis():
+ """Demonstrate cascade analysis using native eventure features."""
+ print("\nCascade Analysis (Native Eventure)")
+ print("=" * 40)
+
+ # Get eventure components
+ event_system = get_event_system()
+ event_query = event_system.get_query()
+
+ # Generate cascades for analysis
+ print("Generating event cascades for analysis...")
+
+ # Cascade 1: Simple action
+ user_action = UserActionEvent(action="click_button", user_id="user123", timestamp=time.time())
+ publish(user_action)
+
+ response = SystemResponseEvent(response="Button clicked", user_id="user123", timestamp=time.time())
+ publish(response)
+
+ notification = NotificationSentEvent(message="Action completed", recipient="user123", timestamp=time.time())
+ publish(notification)
+
+ # Cascade 2: Complex action
+ user_action2 = UserActionEvent(action="upload_file", user_id="user123", timestamp=time.time())
+ publish(user_action2)
+
+ response2 = SystemResponseEvent(response="File received", user_id="user123", timestamp=time.time())
+ publish(response2)
+
+ data_processed = DataProcessedEvent(
+ data_type="file_upload", result="File processed successfully", timestamp=time.time()
+ )
+ publish(data_processed)
+
+ notification2 = NotificationSentEvent(
+ message="File uploaded and processed", recipient="user123", timestamp=time.time()
+ )
+ publish(notification2)
+
+ # Use only native eventure cascade analysis
+ print("\nNative Eventure Cascade Analysis:")
+ print("-" * 50)
+
+ # Get all events and show cascades
+ all_events = event_query.get_root_events()
+ if all_events:
+ # Show cascade for the first event
+ event_query.print_single_cascade(all_events[0])
+
+ # Show cascade for a middle event if available
+ if len(all_events) > 2:
+ print("\nCascade for middle event:")
+ event_query.print_single_cascade(all_events[len(all_events) // 2])
+
+ # Show cascade for the last event
+ print("\nCascade for last event:")
+ event_query.print_single_cascade(all_events[-1])
+
+
+# ============================================================================
+# SIMPLE INTEGRATION EXAMPLE
+# ============================================================================
+
+
+def demonstrate_simple_integration():
+ """Demonstrate simple integration of all features."""
+ print("\nSimple Integration Example")
+ print("=" * 40)
+
+ print("Starting integrated event system demo...")
+
+ # 1. Basic event flow
+ demonstrate_basic_event_flow()
+
+ # 2. Event cascade
+ demonstrate_event_cascade()
+
+ # 3. Multi-bus communication
+ demonstrate_multi_bus_communication()
+
+ # 4. Single system multi-bus communication
+ demonstrate_single_system_multi_bus()
+
+ # 5. Event visualization
+ demonstrate_event_visualization()
+
+ # 6. Cascade analysis
+ demonstrate_cascade_analysis()
+
+ print("\nAll demonstrations completed!")
+ print("Event system is working correctly!")
+
+
+# ============================================================================
+# MAIN DEMONSTRATION
+# ============================================================================
+
+
+def run_simple_demo():
+ """Run the simple event system demonstration."""
+ print("Bub Event System - Simple Examples")
+ print("=" * 50)
+ print("This demo shows core event-driven patterns and features.")
+ print()
+
+ demonstrate_simple_integration()
+
+
+if __name__ == "__main__":
+ run_simple_demo()
diff --git a/mkdocs.yml b/mkdocs.yml
index d7f7cd33..192b8f08 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -9,6 +9,8 @@ copyright: Maintained by Chojan Shang.
nav:
- Home: index.md
+ - Internals:
+ - Event System Architecture: internals/event-system-architecture.md
- Posts:
- Baby Bub - Bootstrap Milestone: posts/2025-07-16-baby-bub-bootstrap-milestone.md
diff --git a/pyproject.toml b/pyproject.toml
index 793adf06..fec60016 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,12 +5,11 @@ description = "Bub it. Build it."
authors = [{ name = "Chojan Shang", email = "psiace@apache.org" }]
readme = "README.md"
keywords = ['python']
-requires-python = ">=3.11,<4.0"
+requires-python = ">=3.12,<4.0"
classifiers = [
"Intended Audience :: Developers",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Libraries :: Python Modules",
@@ -22,6 +21,8 @@ dependencies = [
"typer>=0.9.0",
"any-llm-sdk[openai,anthropic,google,azure,aws]>=0.1.0",
"rich>=13.0.0",
+ "eventure>=0.4.4",
+ "logfire>=4.0.0",
]
[project.urls]
@@ -58,6 +59,7 @@ paths = ["src"]
[tool.mypy]
files = ["src"]
+mypy_path = ["stubs"]
disallow_untyped_defs = true
disallow_any_unimported = true
no_implicit_optional = true
diff --git a/src/bub/__init__.py b/src/bub/__init__.py
index 3c547b36..f78fa0db 100644
--- a/src/bub/__init__.py
+++ b/src/bub/__init__.py
@@ -1,7 +1,8 @@
"""Bub - Bub it. Build it."""
-__version__ = "0.1.0"
+from .core import Agent, AgentContext
+from .core.tools import ToolRegistry
-from .agent import Agent, ToolRegistry
+__version__ = "0.1.0"
-__all__ = ["Agent", "ToolRegistry"]
+__all__ = ["Agent", "AgentContext", "ToolRegistry"]
diff --git a/src/bub/agent/__init__.py b/src/bub/agent/__init__.py
deleted file mode 100644
index 4482d1da..00000000
--- a/src/bub/agent/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""Agent package for Bub."""
-
-from .context import Context
-from .core import Agent, ReActPromptFormatter
-from .tools import Tool, ToolExecutor, ToolRegistry, ToolResult
-
-__all__ = [
- "Agent",
- "Context",
- "ReActPromptFormatter",
- "Tool",
- "ToolExecutor",
- "ToolRegistry",
- "ToolResult",
-]
diff --git a/src/bub/agent/context.py b/src/bub/agent/context.py
deleted file mode 100644
index 436a56ea..00000000
--- a/src/bub/agent/context.py
+++ /dev/null
@@ -1,33 +0,0 @@
-"""Context for the agent package."""
-
-from pathlib import Path
-from typing import Any, Optional
-
-from ..config import get_settings
-
-
-class Context:
- """Agent environment context: workspace, config, tool registry, etc."""
-
- def __init__(self, workspace_path: Optional[Path] = None, config: Optional[Any] = None):
- self.workspace_path = workspace_path or Path.cwd()
- self.config = config or get_settings(self.workspace_path)
- self.tool_registry = None # Will be set by Agent
-
- def get_system_prompt(self) -> str:
- """Get the system prompt from config."""
- return self.config.system_prompt or ""
-
- def build_context_message(self) -> str:
- """Build a clean context message with essential information."""
- if not self.tool_registry:
- return f"[Environment Context]\nWorkspace: {self.workspace_path}\nNo tools available"
-
- tool_schemas = self.tool_registry.get_tool_schemas()
- msg = [
- "[Environment Context]",
- f"Workspace: {self.workspace_path}",
- f"Available tools: {', '.join(tool_schemas.keys())}",
- f"Tool schemas: {self.tool_registry._format_schemas_for_context()}",
- ]
- return "\n".join(msg)
diff --git a/src/bub/agent/core.py b/src/bub/agent/core.py
deleted file mode 100644
index 25fd19a6..00000000
--- a/src/bub/agent/core.py
+++ /dev/null
@@ -1,145 +0,0 @@
-"""Core agent implementation for Bub."""
-
-from pathlib import Path
-from typing import Callable, Optional
-
-from any_llm import completion # type: ignore[import-untyped]
-from openai.types.chat import ChatCompletion, ChatCompletionMessageParam
-
-from .context import Context
-from .tools import ToolExecutor, ToolRegistry
-
-
-class ReActPromptFormatter:
- """Formats ReAct prompts by combining principles, system prompt, and examples."""
-
- REACT_PRINCIPLES = """You are an AI assistant with access to tools. When you need to use a tool, follow this format:
-
-Thought: Do I need to use a tool? Yes/No. If yes, which one and with what input?
-Action:
-Action Input:
-
-After the tool is executed, you will see:
-Observation:
-
-You can use multiple Thought/Action/Action Input/Observation steps as needed (ReAct pattern). When you have a final answer, reply with:
-
-Final Answer:
-
-If you do not need a tool, just reply with Final Answer."""
-
- REACT_EXAMPLE = """Example:
-Thought: I need to list files in the workspace.
-Action: run_command
-Action Input: {"command": "ls"}
-Observation: