Files
ai/backend.old/src/trigger/handlers.py
2026-03-11 18:47:11 -04:00

305 lines
9.2 KiB
Python

"""
Trigger handlers - concrete implementations for common trigger types.
Provides ready-to-use trigger handlers for:
- Agent execution (WebSocket user messages)
- Lambda/callable execution
- Data update triggers
- Indicator updates
"""
import logging
import time
from typing import Any, Awaitable, Callable, Optional
from .coordinator import CommitCoordinator
from .types import CommitIntent, Priority, Trigger
logger = logging.getLogger(__name__)
class AgentTriggerHandler(Trigger):
"""
Trigger for agent execution from WebSocket user messages.
Wraps the Gateway's agent execution flow and captures any
store modifications as commit intents.
Priority tuple: (USER_AGENT, message_timestamp, queue_seq)
"""
def __init__(
self,
session_id: str,
message_content: str,
message_timestamp: Optional[int] = None,
attachments: Optional[list] = None,
gateway_handler: Optional[Callable] = None,
coordinator: Optional[CommitCoordinator] = None,
):
"""
Initialize agent trigger.
Args:
session_id: User session ID
message_content: User message content
message_timestamp: When user sent message (unix timestamp, defaults to now)
attachments: Optional message attachments
gateway_handler: Callable to route to Gateway (set during integration)
coordinator: CommitCoordinator for accessing stores
"""
if message_timestamp is None:
message_timestamp = int(time.time())
# Priority tuple: sort by USER_AGENT priority, then message timestamp
super().__init__(
name=f"agent_{session_id}",
priority=Priority.USER_AGENT,
priority_tuple=(Priority.USER_AGENT.value, message_timestamp)
)
self.session_id = session_id
self.message_content = message_content
self.message_timestamp = message_timestamp
self.attachments = attachments or []
self.gateway_handler = gateway_handler
self.coordinator = coordinator
async def execute(self) -> list[CommitIntent]:
"""
Execute agent interaction.
This will call into the Gateway, which will run the agent.
The agent may read from stores and generate responses.
Any store modifications are captured as commit intents.
Returns:
List of commit intents (typically empty for now, as agent
modifies stores via tools which will be integrated later)
"""
if not self.gateway_handler:
logger.error("No gateway_handler configured for AgentTriggerHandler")
return []
logger.info(
f"Agent trigger executing: session={self.session_id}, "
f"content='{self.message_content[:50]}...'"
)
try:
# Call Gateway to handle message
# In future, Gateway/agent tools will use coordinator stores
await self.gateway_handler(
self.session_id,
self.message_content,
self.attachments,
)
# For now, agent doesn't directly modify stores
# Future: agent tools will return commit intents
return []
except Exception as e:
logger.error(f"Agent execution error: {e}", exc_info=True)
raise
class LambdaHandler(Trigger):
"""
Generic trigger that executes an arbitrary async callable.
Useful for custom triggers, one-off tasks, or testing.
"""
def __init__(
self,
name: str,
func: Callable[[], Awaitable[list[CommitIntent]]],
priority: Priority = Priority.SYSTEM,
):
"""
Initialize lambda handler.
Args:
name: Descriptive name for this trigger
func: Async callable that returns commit intents
priority: Execution priority
"""
super().__init__(name, priority)
self.func = func
async def execute(self) -> list[CommitIntent]:
"""Execute the callable"""
logger.info(f"Lambda trigger executing: {self.name}")
return await self.func()
class DataUpdateTrigger(Trigger):
"""
Trigger for DataSource bar updates.
Fired when new market data arrives. Can update indicators,
trigger strategy logic, or notify the agent of market events.
Priority tuple: (DATA_SOURCE, event_time, queue_seq)
Ensures older bars process before newer ones.
"""
def __init__(
self,
source_name: str,
symbol: str,
resolution: str,
bar_data: dict,
coordinator: Optional[CommitCoordinator] = None,
):
"""
Initialize data update trigger.
Args:
source_name: Name of data source (e.g., "binance")
symbol: Trading pair symbol
resolution: Time resolution
bar_data: Bar data dict (time, open, high, low, close, volume)
coordinator: CommitCoordinator for accessing stores
"""
event_time = bar_data.get('time', int(time.time()))
# Priority tuple: sort by DATA_SOURCE priority, then event time
super().__init__(
name=f"data_{source_name}_{symbol}_{resolution}",
priority=Priority.DATA_SOURCE,
priority_tuple=(Priority.DATA_SOURCE.value, event_time)
)
self.source_name = source_name
self.symbol = symbol
self.resolution = resolution
self.bar_data = bar_data
self.coordinator = coordinator
async def execute(self) -> list[CommitIntent]:
"""
Process bar update.
Future implementations will:
- Update indicator values
- Check strategy conditions
- Trigger alerts/notifications
Returns:
Commit intents for any store updates
"""
logger.info(
f"Data update trigger: {self.source_name}:{self.symbol}@{self.resolution}, "
f"time={self.bar_data.get('time')}"
)
# TODO: Update indicators
# TODO: Check strategy conditions
# TODO: Notify agent of significant events
# For now, just log
return []
class IndicatorUpdateTrigger(Trigger):
"""
Trigger for updating indicator values.
Can be fired by cron (periodic recalculation) or by data updates.
"""
def __init__(
self,
indicator_id: str,
force_full_recalc: bool = False,
coordinator: Optional[CommitCoordinator] = None,
priority: Priority = Priority.SYSTEM,
):
"""
Initialize indicator update trigger.
Args:
indicator_id: ID of indicator to update
force_full_recalc: If True, recalculate entire history
coordinator: CommitCoordinator for accessing stores
priority: Execution priority
"""
super().__init__(f"indicator_{indicator_id}", priority)
self.indicator_id = indicator_id
self.force_full_recalc = force_full_recalc
self.coordinator = coordinator
async def execute(self) -> list[CommitIntent]:
"""
Update indicator value.
Reads from IndicatorStore, recalculates, prepares commit.
Returns:
Commit intents for updated indicator data
"""
if not self.coordinator:
logger.error("No coordinator configured")
return []
# Get indicator store
indicator_store = self.coordinator.get_store("IndicatorStore")
if not indicator_store:
logger.error("IndicatorStore not registered")
return []
# Read snapshot
snapshot_seq, indicator_data = indicator_store.read_snapshot()
logger.info(
f"Indicator update trigger: {self.indicator_id}, "
f"snapshot_seq={snapshot_seq}, force_full={self.force_full_recalc}"
)
# TODO: Implement indicator recalculation logic
# For now, just return empty (no changes)
return []
class CronTrigger(Trigger):
"""
Trigger fired by APScheduler on a schedule.
Wraps another trigger or callable to execute periodically.
Priority tuple: (TIMER, scheduled_time, queue_seq)
Ensures jobs scheduled for earlier times run first.
"""
def __init__(
self,
name: str,
inner_trigger: Trigger,
scheduled_time: Optional[int] = None,
):
"""
Initialize cron trigger.
Args:
name: Descriptive name (e.g., "hourly_sync")
inner_trigger: Trigger to execute on schedule
scheduled_time: When this was scheduled to run (defaults to now)
"""
if scheduled_time is None:
scheduled_time = int(time.time())
# Priority tuple: sort by TIMER priority, then scheduled time
super().__init__(
name=f"cron_{name}",
priority=Priority.TIMER,
priority_tuple=(Priority.TIMER.value, scheduled_time)
)
self.inner_trigger = inner_trigger
self.scheduled_time = scheduled_time
async def execute(self) -> list[CommitIntent]:
"""Execute the wrapped trigger"""
logger.info(f"Cron trigger firing: {self.name}")
return await self.inner_trigger.execute()