--- category: feature tags: [agents, irc, supervisor] last_updated: 2026-03-20 confidence: high --- # Directed Message Routing Event-driven delivery of IRC messages to specific agents, replacing poll-only communication. ## Problem Agents only see IRC messages when the supervisor's 30-second heartbeat prompts them to `read_messages`. If the PM or EM posts a message mentioning an agent by nick, it sits unread until the next poll. This creates up to 30 seconds of latency on directed communication and makes the system feel unresponsive. The `IrcWatcher` already has an event-driven IRC connection that fires instantly on `PRIVMSG`. It currently only handles `TASK:` prefixes. It should also route messages that mention a specific agent. ## Design Extend `IrcWatcher` to detect nick mentions and notify the supervisor, which immediately sends the message content to the targeted agent. ### Detection A message is "directed" if it contains `@AgentName` where `AgentName` is a currently active agent nick. The watcher already has access to a callback — add a second callback for directed messages alongside the existing `on_task`. ```python class IrcWatcher: def __init__( self, server: str, port: int, nick: str, channels: list[str], on_task: Callable[[str, str, str], None], on_mention: Callable[[str, str, str, str], None], # on_mention(channel, sender, mentioned_nick, full_message) ) -> None: ``` ### Routing In `_handle_message`, after the existing `TASK:` check: 1. Scan the message for `@{nick}` where nick is in the set of active agent names 2. If found, call `on_mention(channel, sender_nick, mentioned_nick, message)` 3. A message can match both `TASK:` and `@mention` — that's fine, they serve different purposes The supervisor registers `on_mention` and calls `agent.send()` with the message content, bypassing the poll loop: ```python def _on_mention(self, channel: str, sender: str, target_nick: str, message: str) -> None: agent = self.agents.get(target_nick) if agent: asyncio.get_running_loop().create_task( agent.session.send( f"[IRC {channel}] <{sender}> {message}\n\n" "Respond in the appropriate channel." ) ) ``` ### Active nick registry The `IrcWatcher` needs to know which nicks are active to avoid matching mentions of non-agent names. The supervisor already tracks this in `self.agents` (keyed by name). Add a method to update the watcher's nick set: ```python # On IrcWatcher def update_active_nicks(self, nicks: set[str]) -> None: self._active_nicks = nicks # Supervisor calls this after spawn/kill/shift-change self._watcher.update_active_nicks(set(self.agents.keys())) ``` ### Deduplication Without guard, an agent could receive the same message twice: once via directed routing, once via the next heartbeat's `read_messages`. Two options: 1. **Track delivered message timestamps per agent.** The supervisor records the timestamp of the last directed delivery. The heartbeat prompt changes to "Check your IRC channels for messages since {timestamp}." 2. **Accept duplicates.** Agents are LLMs — seeing a message twice is redundant but not harmful. The simpler option for MVP. **Recommendation:** Accept duplicates for MVP. Add dedup later if it becomes noisy. ## Channels to watch The watcher currently joins only `#standup-{slug}`. For mention routing to work across channels, it should also join `#project-{slug}` and any `#work-{task_id}` channels that get created. The supervisor should call `watcher.join_channel()` when creating work channels for new tasks. ## Scope ### In scope - Extend `IrcWatcher._handle_message` with nick-mention detection - Add `on_mention` callback to `IrcWatcher.__init__` - Add `update_active_nicks()` method - Wire `Supervisor._on_mention` to call `agent.send()` - Update active nicks on spawn/kill/shift-change - Join `#project-{slug}` in addition to `#standup-{slug}` on startup - Tests for mention detection regex ### Out of scope - DM (private message) routing — agents only operate in channels for now - Message deduplication — accept duplicates for MVP - Priority/interruption — directed messages queue behind any in-progress `send()` - Rate limiting — a burst of mentions could queue many `send()` calls ## Files to change - `supervisor/src/minsky_supervisor/irc_watcher.py` — add mention detection, `on_mention` callback, `update_active_nicks()` - `supervisor/src/minsky_supervisor/supervisor.py` — wire `_on_mention`, call `update_active_nicks()` after agent lifecycle changes, join `#project-{slug}` - `supervisor/tests/test_irc_watcher.py` — add mention detection tests
