Initial implementation of xtrm-agent multi-agent system
Multi-agent AI automation system with shared message bus, specialized roles (coder/researcher/reviewer), and deny-by-default security. - Config system with Pydantic validation and YAML loading - Async message bus with inter-agent delegation - LLM providers: Anthropic (Claude) and LiteLLM (DeepSeek/Kimi/MiniMax) - Tool system: registry, builtins (file/bash/web), approval engine, MCP client - Agent engine with tool-calling loop and orchestrator for multi-agent management - CLI channel (REPL) and Discord channel - Docker + Dockge deployment config - Typer CLI: chat, serve, status, agents commands Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
92
xtrm_agent/bus.py
Normal file
92
xtrm_agent/bus.py
Normal file
@@ -0,0 +1,92 @@
|
||||
"""Shared message bus — async queues for inter-component communication."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class InboundMessage:
|
||||
"""Message from a channel (user) heading to an agent."""
|
||||
|
||||
channel: str
|
||||
sender_id: str
|
||||
chat_id: str
|
||||
content: str
|
||||
target_agent: str | None = None
|
||||
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
metadata: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class OutboundMessage:
|
||||
"""Message from an agent heading back to a channel."""
|
||||
|
||||
channel: str
|
||||
chat_id: str
|
||||
content: str
|
||||
reply_to: str | None = None
|
||||
metadata: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentMessage:
|
||||
"""Inter-agent delegation message."""
|
||||
|
||||
from_agent: str
|
||||
to_agent: str
|
||||
task: str
|
||||
request_id: str = ""
|
||||
response: str | None = None
|
||||
|
||||
|
||||
class MessageBus:
|
||||
"""Async queue-based message bus."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.inbound: asyncio.Queue[InboundMessage] = asyncio.Queue()
|
||||
self.outbound: asyncio.Queue[OutboundMessage] = asyncio.Queue()
|
||||
self.agent_messages: asyncio.Queue[AgentMessage] = asyncio.Queue()
|
||||
self._outbound_subscribers: dict[str, list[asyncio.Queue[OutboundMessage]]] = {}
|
||||
|
||||
async def publish_inbound(self, msg: InboundMessage) -> None:
|
||||
await self.inbound.put(msg)
|
||||
|
||||
async def consume_inbound(self, timeout: float = 1.0) -> InboundMessage | None:
|
||||
try:
|
||||
return await asyncio.wait_for(self.inbound.get(), timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
return None
|
||||
|
||||
async def publish_outbound(self, msg: OutboundMessage) -> None:
|
||||
await self.outbound.put(msg)
|
||||
# Also dispatch to channel-specific subscribers
|
||||
channel_queues = self._outbound_subscribers.get(msg.channel, [])
|
||||
for q in channel_queues:
|
||||
await q.put(msg)
|
||||
|
||||
async def consume_outbound(self, timeout: float = 1.0) -> OutboundMessage | None:
|
||||
try:
|
||||
return await asyncio.wait_for(self.outbound.get(), timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
return None
|
||||
|
||||
def subscribe_outbound(self, channel: str) -> asyncio.Queue[OutboundMessage]:
|
||||
"""Subscribe to outbound messages for a specific channel."""
|
||||
q: asyncio.Queue[OutboundMessage] = asyncio.Queue()
|
||||
self._outbound_subscribers.setdefault(channel, []).append(q)
|
||||
return q
|
||||
|
||||
async def publish_agent_message(self, msg: AgentMessage) -> None:
|
||||
await self.agent_messages.put(msg)
|
||||
|
||||
async def consume_agent_message(
|
||||
self, timeout: float = 1.0
|
||||
) -> AgentMessage | None:
|
||||
try:
|
||||
return await asyncio.wait_for(self.agent_messages.get(), timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
return None
|
||||
Reference in New Issue
Block a user