We Could Send Messages to Parts, but Could Not List the Parts

· bejoyfuuul

While building RelayRoom with RelayRoom, I asked the main agent: "Which agents are connected right now?" It could not answer. It could send messages to parts, but it had no way to discover the list of parts.

That was not a small UX gap. In a multi-agent collaboration system, the roster is both an address book and a routing table. We had given that context to humans in the dashboard, but not to agents through tools.

The Initial Tool Set Was Message-Centric

The early MCP surface had nine tools: send, reply, inbox, ack, event, threads, show, close, and search. Starting threads, replying, scanning the inbox, marking messages read, recording work events, and searching old threads were all covered.

Discovery was not.

send and reply take part names such as backend, web, android, or main as addresses. But an agent could not ask the server for the list of valid part names. A person could open the dashboard's Agents tab. An agent does not watch that screen.

From the MCP perspective, this is easy to name. The Model Context Protocol tools spec lets servers expose callable tools, and tools/list lets clients discover which tools exist. RelayRoom needed one more layer of discovery: not "which tools can I call?" but "which collaborators can I address?"

The Agent Found a Workaround

Without a roster tool, the main agent looked outside RelayRoom. It searched .relayroom/config.json files, inspected tmux ls, and tried to infer live sessions from local state. Then a human said the obvious thing: "There is no way to list connected agents."

Exactly. RelayRoom had a collaboration message bus, but not a collaborator directory in the tool contract. We were expecting autonomous agents to infer context that humans were getting from the UI.

Even if the workaround works, it is the wrong layer. File paths, tmux session names, and install layout are implementation details. MCP tools are server-backed contracts. Anything an agent should use for collaboration decisions belongs in the contract.

Why We Missed It

When you build messaging first, you naturally focus on sending and reading. That produces send, reply, inbox, and show. But the first real question in messaging is "to whom?"

In human-first software, that answer can be scattered across the interface: a sidebar, a member list, a dashboard, and the user's memory. In agent-first software, that does not work. Agents do not continuously observe the whole UI. After compaction, they may lose what they previously saw. After restart, even their own identity should be re-checkable.

Agent-first design needs a stricter rule:

  • Important state visible to humans should also be readable through tools.
  • Values used as addresses should be discoverable by the agent.
  • An agent should be able to reconstruct its identity after restart, compaction, or project switching.

The Fix: roster and whoami

No schema migration was needed. The server already knew about agents, connections, parts, and which part was main. The missing piece was exposure.

So RelayRoom added two tools.

roster

roster returns the current project's collaboration targets. Its main job is to let an agent discover valid send and reply recipients.

Example shape:

{
  "project": {
    "id": "prj_...",
    "name": "relayroom-hq"
  },
  "parts": [
    {
      "part": "main",
      "online": true,
      "main": true,
      "you": true,
      "agent": {
        "name": "Codex",
        "connectedAt": "2026-06-17T03:12:00.000Z"
      }
    },
    {
      "part": "web",
      "online": true,
      "main": false,
      "you": false,
      "agent": {
        "name": "Claude Code",
        "connectedAt": "2026-06-17T03:14:21.000Z"
      }
    },
    {
      "part": "android",
      "online": false,
      "main": false,
      "you": false,
      "agent": null
    }
  ]
}

Each field has a routing purpose:

  • part is the stable message address.
  • online tells the agent whether an immediate wake is likely.
  • main identifies the coordinator or default recipient.
  • you marks the current connection, reducing self-addressing mistakes.
  • agent carries display metadata; routing should still key off part.

whoami

whoami is smaller. It tells the agent which project and part this MCP connection is bound to.

{
  "project": {
    "id": "prj_...",
    "name": "relayroom-hq"
  },
  "part": "main",
  "main": true
}

It is not flashy, but it matters operationally. After restart, context compaction, or switching between RelayRoom projects, the agent can ask "who am I here?" without relying on stale conversation memory.

Why threads and inbox Were Not Enough

An agent could infer part names from old messages. But that is archaeology, not discovery. It misses parts that have never appeared in a thread, renamed or offline parts, and current status. It also burns tokens and time by forcing the agent to search conversation history for state.

roster is state. threads is conversation history. A collaboration directory should be a first-class feature, not a side effect of message logs.

Takeaway

Multi-agent systems need more than a message bus. They need an address book, liveness, and self-identity. Humans can fill those gaps with UI and memory. Agents need explicit tool contracts.

This is where dogfooding paid off. In a spec review, "agents can send messages" sounded complete. When we used RelayRoom to build RelayRoom, the missing question appeared on day one: "who should I send this to?"

RelayRoom added roster and whoami to the MCP surface. It was less a new feature than a promotion of server-known collaboration state into an agent-readable contract.

References