route optimizer agent with dailygrubs ai assign
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"""Base Agent class - All agents inherit from this."""
|
||||
import asyncio
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional, Any
|
||||
@@ -28,6 +29,7 @@ class Agent(ABC):
|
||||
self._task_queue: asyncio.Queue = asyncio.Queue()
|
||||
self._running = False
|
||||
self._task_handlers: Dict[str, callable] = {}
|
||||
self._last_state_emit = 0.0
|
||||
|
||||
# Register with message bus
|
||||
message_bus.register_agent(self)
|
||||
@@ -49,6 +51,9 @@ class Agent(ABC):
|
||||
except asyncio.TimeoutError:
|
||||
await self._heartbeat()
|
||||
|
||||
if time.monotonic() - self._last_state_emit >= 5.0:
|
||||
await self._emit_state()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in agent {self.agent_id}: {e}")
|
||||
self.state.status = "error"
|
||||
@@ -64,6 +69,8 @@ class Agent(ABC):
|
||||
"""Process a task from the queue."""
|
||||
self.state.status = "working"
|
||||
self.state.current_task = task.task_id
|
||||
started = time.monotonic()
|
||||
await self._emit_state()
|
||||
|
||||
try:
|
||||
if task.task_type in self._task_handlers:
|
||||
@@ -85,9 +92,19 @@ class Agent(ABC):
|
||||
self.state.tasks_failed += 1
|
||||
|
||||
finally:
|
||||
duration_ms = int((time.monotonic() - started) * 1000)
|
||||
await message_bus.publish_telemetry("task", {
|
||||
"agent_id": self.agent_id,
|
||||
"task_id": task.task_id,
|
||||
"task_type": task.task_type,
|
||||
"status": task.status,
|
||||
"error": task.error,
|
||||
"duration_ms": duration_ms,
|
||||
})
|
||||
self.state.current_task = None
|
||||
self.state.last_active = datetime.now()
|
||||
self.state.status = "idle"
|
||||
await self._emit_state()
|
||||
|
||||
@abstractmethod
|
||||
async def handle_task(self, task: AgentTask) -> Dict[str, Any]:
|
||||
@@ -98,6 +115,18 @@ class Agent(ABC):
|
||||
"""Called periodically when idle. Override for custom behavior."""
|
||||
pass
|
||||
|
||||
async def _emit_state(self):
|
||||
"""Publish this agent's live state as telemetry (for the command center)."""
|
||||
self._last_state_emit = time.monotonic()
|
||||
await message_bus.publish_telemetry("agent", {
|
||||
"agent_id": self.agent_id,
|
||||
"agent_type": self.agent_type,
|
||||
"status": self.state.status,
|
||||
"current_task": self.state.current_task,
|
||||
"tasks_completed": self.state.tasks_completed,
|
||||
"tasks_failed": self.state.tasks_failed,
|
||||
})
|
||||
|
||||
def register_task_handler(self, task_type: str, handler: callable):
|
||||
self._task_handlers[task_type] = handler
|
||||
|
||||
@@ -124,6 +153,39 @@ class Agent(ABC):
|
||||
async def receive_messages(self) -> List[AgentMessage]:
|
||||
return await message_bus.get_messages(self.agent_id)
|
||||
|
||||
async def deliver(self, message: AgentMessage):
|
||||
"""Consume an inbound directed message.
|
||||
|
||||
This is what makes agent-to-agent messaging actually work: the message
|
||||
bus calls it for every directed message addressed to this agent. Messages
|
||||
that carry a ``task_type`` in their payload are enqueued as tasks so
|
||||
``handle_task`` processes them exactly like a submitted task; everything
|
||||
else is handed to ``handle_message`` so the agent can react. Without this,
|
||||
directed messages sat unread in the bus queue forever.
|
||||
"""
|
||||
payload = message.payload if isinstance(message.payload, dict) else {}
|
||||
task_type = payload.get("task_type")
|
||||
if task_type:
|
||||
await self._task_queue.put(AgentTask(
|
||||
task_id=f"{message.correlation_id or message.message_id}:{task_type}",
|
||||
agent_type=self.agent_type,
|
||||
task_type=task_type,
|
||||
data=payload,
|
||||
))
|
||||
else:
|
||||
try:
|
||||
await self.handle_message(message)
|
||||
except Exception as e:
|
||||
logger.error(f"{self.agent_id} handle_message error: {e}")
|
||||
|
||||
async def handle_message(self, message: AgentMessage):
|
||||
"""React to a non-task directed message (a notification). Default just
|
||||
logs it; agents override this to act on events like EXCEPTION_DETECTED."""
|
||||
logger.debug(
|
||||
f"{self.agent_id} received {message.message_type.value} "
|
||||
f"from {message.sender} (no task_type; not handled)"
|
||||
)
|
||||
|
||||
def subscribe_to(self, message_type: MessageType, callback: callable):
|
||||
message_bus.subscribe(message_type, callback)
|
||||
|
||||
@@ -151,6 +213,24 @@ class MasterAgent(Agent):
|
||||
self._sub_agents[agent.agent_id] = agent
|
||||
logger.info(f"JARVIS registered sub-agent: {agent.agent_id}")
|
||||
|
||||
async def handle_message(self, message: AgentMessage):
|
||||
"""Surface notifications from sub-agents. Escalations (proposals a human
|
||||
must decide) are logged at WARNING and recorded so they are visible
|
||||
rather than silently dropped — the endpoint of the human-review path."""
|
||||
if message.message_type == MessageType.EXCEPTION_DETECTED:
|
||||
logger.warning(f"JARVIS: exception escalation from {message.sender}: {message.payload}")
|
||||
else:
|
||||
logger.info(f"JARVIS: {message.message_type.value} from {message.sender}")
|
||||
self._decision_log.append({
|
||||
"timestamp": datetime.now(),
|
||||
"action": "received_notification",
|
||||
"from": message.sender,
|
||||
"type": message.message_type.value,
|
||||
"payload": message.payload,
|
||||
})
|
||||
if len(self._decision_log) > 500:
|
||||
self._decision_log = self._decision_log[-500:]
|
||||
|
||||
async def handle_task(self, task: AgentTask) -> Dict[str, Any]:
|
||||
if task.task_type == "orchestrate_order":
|
||||
return await self._orchestrate_order(task)
|
||||
|
||||
Reference in New Issue
Block a user