route optimizer agent with dailygrubs ai assign

This commit is contained in:
2026-09-01 13:48:58 +05:30
parent aa4d4c6549
commit 90cdb57a38
28 changed files with 3281 additions and 135 deletions

View File

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