Properties
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.

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:

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:

# 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
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9