Spaces:
Sleeping
Sleeping
| """ | |
| Agent-to-Agent Messaging Bus | |
| Async, in-process pub/sub. Each registered agent gets an inbox (asyncio.Queue). | |
| Messages can be sent directly to one agent (`send`) or broadcast to every | |
| subscriber of a topic (`publish`), e.g. the orchestrator publishing task | |
| status updates that other agents or a UI layer can subscribe to. | |
| """ | |
| from __future__ import annotations | |
| import asyncio | |
| import time | |
| import uuid | |
| from dataclasses import dataclass, field | |
| from typing import Any, Optional | |
| class AgentMessage: | |
| id: str | |
| sender: str | |
| recipient: Optional[str] | |
| topic: Optional[str] | |
| type: str | |
| payload: dict[str, Any] = field(default_factory=dict) | |
| created_at: float = field(default_factory=time.time) | |
| correlation_id: Optional[str] = None | |
| class MessageBus: | |
| """Central bus shared by the orchestrator and all agents in a run.""" | |
| def __init__(self) -> None: | |
| self._inboxes: dict[str, "asyncio.Queue[AgentMessage]"] = {} | |
| self._subscriptions: dict[str, set[str]] = {} | |
| self._history: list[AgentMessage] = [] | |
| def register(self, agent_id: str) -> "asyncio.Queue[AgentMessage]": | |
| self._inboxes.setdefault(agent_id, asyncio.Queue()) | |
| return self._inboxes[agent_id] | |
| def unregister(self, agent_id: str) -> None: | |
| self._inboxes.pop(agent_id, None) | |
| for subs in self._subscriptions.values(): | |
| subs.discard(agent_id) | |
| def subscribe(self, agent_id: str, topic: str) -> None: | |
| self._subscriptions.setdefault(topic, set()).add(agent_id) | |
| self._inboxes.setdefault(agent_id, asyncio.Queue()) | |
| async def send( | |
| self, | |
| sender: str, | |
| recipient: str, | |
| type: str, | |
| payload: Optional[dict[str, Any]] = None, | |
| correlation_id: Optional[str] = None, | |
| ) -> AgentMessage: | |
| message = AgentMessage( | |
| id=str(uuid.uuid4()), sender=sender, recipient=recipient, topic=None, | |
| type=type, payload=payload or {}, correlation_id=correlation_id, | |
| ) | |
| inbox = self._inboxes.setdefault(recipient, asyncio.Queue()) | |
| await inbox.put(message) | |
| self._history.append(message) | |
| return message | |
| async def publish( | |
| self, sender: str, topic: str, type: str, payload: Optional[dict[str, Any]] = None | |
| ) -> AgentMessage: | |
| message = AgentMessage( | |
| id=str(uuid.uuid4()), sender=sender, recipient=None, topic=topic, | |
| type=type, payload=payload or {}, | |
| ) | |
| for agent_id in self._subscriptions.get(topic, set()): | |
| inbox = self._inboxes.setdefault(agent_id, asyncio.Queue()) | |
| await inbox.put(message) | |
| self._history.append(message) | |
| return message | |
| async def receive(self, agent_id: str, timeout: Optional[float] = None) -> Optional[AgentMessage]: | |
| inbox = self._inboxes.setdefault(agent_id, asyncio.Queue()) | |
| try: | |
| if timeout is None: | |
| return await inbox.get() | |
| return await asyncio.wait_for(inbox.get(), timeout=timeout) | |
| except asyncio.TimeoutError: | |
| return None | |
| def history_for(self, agent_id: str, limit: int = 50) -> list[AgentMessage]: | |
| relevant = [m for m in self._history if m.sender == agent_id or m.recipient == agent_id] | |
| return relevant[-limit:] | |