메시지는 파트로 보내는데, 정작 파트 목록을 볼 수가 없었습니다

· bejoyfuuul

RelayRoom으로 RelayRoom을 만들다가 메인 에이전트에게 물었습니다. "지금 릴레이룸에 어떤 에이전트들이 연결돼 있어?" 대답하지 못했습니다. 메시지는 보낼 수 있는데, 누구에게 보낼지는 알 수 없었습니다.

이것은 작은 UX 누락이 아니었습니다. 멀티 에이전트 협업 시스템에서 동료 명단은 주소록이자 라우팅 테이블입니다. 사람이 화면을 보고 채워 넣던 정보를 에이전트에게는 도구로 주지 않은 것입니다.

당시 도구는 메시징 중심이었습니다

초기 MCP 도구는 9개였습니다. send, reply, inbox, ack, event, threads, show, close, search. 스레드를 만들고, 답장하고, inbox를 훑고, 읽음 처리하고, 작업 이벤트를 남기고, 검색하는 흐름은 꽤 충실했습니다.

하지만 발견(discovery)이 0이었습니다. sendreplybackend, web, android, main 같은 part 이름을 주소로 받습니다. 문제는 에이전트가 그 part 이름의 전체 목록을 조회할 방법이 없었다는 점입니다. 사람은 대시보드 Agents 탭을 보면 됩니다. 에이전트는 그 화면을 보지 않습니다.

MCP 관점에서 보면 더 명확합니다. Model Context Protocol의 tools 스펙은 서버가 language model이 호출할 수 있는 도구를 노출하고, 모델이 문맥에 따라 도구를 발견하고 호출할 수 있게 합니다. tools/list는 "어떤 도구가 있는가"를 알려줍니다. 하지만 RelayRoom에 필요했던 것은 한 단계 더 위의 발견이었습니다. "어떤 협업 대상이 있는가"를 알려주는 도구가 필요했습니다.

에이전트는 우회로를 찾았습니다

명단 도구가 없자 메인 에이전트는 RelayRoom 바깥에서 답을 찾았습니다. 워크스페이스의 .relayroom/config.json 파일을 뒤지고, tmux ls로 살아 있는 세션을 추적하고, 세션 이름과 part 이름을 맞춰 보려 했습니다. 결국 사람이 한마디 했습니다. "연결된 에이전트 리스트 조회가 없네."

그 말이 정확했습니다. RelayRoom은 협업 메시지 버스를 만들었지만, 협업자 directory를 도구로 노출하지 않았습니다. 인간 사용자가 UI로 채우는 맥락을 자율 에이전트가 알아서 추론하기를 기대한 셈입니다.

이런 우회로는 동작하더라도 좋은 설계가 아닙니다. 로컬 파일 경로, tmux 세션 이름, 설치 방식은 구현 세부사항입니다. MCP 도구는 서버가 보장하는 계약입니다. 에이전트가 협업 판단에 써야 하는 정보는 구현 세부사항이 아니라 계약으로 올라와야 합니다.

왜 놓쳤나

메시징부터 만들면 자연스럽게 "보내기"와 "읽기"에 집중하게 됩니다. 그래서 send, reply, inbox, show가 먼저 나옵니다. 그런데 메시징의 진짜 첫 질문은 "누구에게?"입니다.

사람 중심 제품에서는 이 질문이 화면에 흩어져 있어도 됩니다. 사이드바에 멤버가 있고, 대시보드에 접속 상태가 있고, 머릿속에 팀 구조가 있습니다. 에이전트 중심 제품에서는 그렇지 않습니다. 에이전트는 화면 전체를 지속적으로 관찰하지 않습니다. 컨텍스트 압축 후에는 이전에 보았던 정보도 사라질 수 있습니다. 재시작하면 자기 part조차 다시 확인해야 합니다.

그래서 에이전트 우선 설계에서는 다음 원칙이 필요합니다.

  • 사람이 보는 주요 상태는 에이전트도 도구로 읽을 수 있어야 합니다.
  • 에이전트가 주소로 사용하는 값은 에이전트가 발견할 수 있어야 합니다.
  • 재시작, 컴팩션, 세션 이동 후에도 자기 정체성을 재구성할 수 있어야 합니다.

수정: rosterwhoami

스키마 변경은 필요 없었습니다. 데이터는 이미 있었습니다. agentagent_connection 계열 데이터가 프로젝트 안의 part, 연결 상태, main 여부를 알고 있었습니다. 빠진 것은 노출면이었습니다.

그래서 도구 두 개를 추가했습니다.

roster

roster는 현재 프로젝트의 협업 대상 목록을 돌려줍니다. 목적은 sendreply의 대상 후보를 에이전트가 직접 발견하게 하는 것입니다.

예시 형태는 다음과 같습니다.

{
  "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
    }
  ]
}

필드의 의미는 작지만 중요합니다.

  • part: 메시지를 보낼 때 쓰는 안정적인 주소입니다.
  • online: 지금 wake가 닿을 가능성이 있는지 알려줍니다. offline이라고 메시지를 못 보내는 것은 아니지만, 즉시 응답에 대한 기대치는 달라집니다.
  • main: 조율자나 기본 수신자를 구분합니다.
  • you: 현재 연결된 에이전트 자신의 행입니다. 자기 자신에게 보내는 실수를 줄여줍니다.
  • agent: 표시 이름과 연결 시각 같은 부가 정보입니다. 라우팅 결정에는 part가 1순위입니다.

whoami

whoami는 더 작습니다. 현재 MCP 연결이 어떤 project와 part로 바인딩되어 있는지 돌려줍니다.

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

이 도구는 화려하지 않지만 에이전트 운영에서는 자주 필요합니다. 세션이 재시작되었거나, 모델 컨텍스트가 압축되었거나, 여러 RelayRoom 프로젝트를 오가다 돌아왔을 때 "내가 누구로 연결되어 있지?"를 안전하게 확인할 수 있습니다.

threadsinbox로 충분하지 않은가

기존 도구만으로도 과거 대화에서 part 이름을 추론할 수는 있습니다. 하지만 그것은 발견이 아니라 발굴입니다. 과거 메시지에 등장하지 않은 part는 찾을 수 없고, 이름이 바뀌었거나 offline인 part도 놓칩니다. 무엇보다 에이전트가 "목록을 찾아야 한다"는 목적을 위해 대화 기록을 검색하게 만들면 토큰과 시간이 낭비됩니다.

roster는 상태 조회이고, threads는 대화 조회입니다. 둘의 책임은 다릅니다. 협업 시스템에서 directory는 메시지 기록의 부산물이 아니라 1급 기능이어야 합니다.

가져갈 것

멀티 에이전트 시스템은 메시지 버스만으로 완성되지 않습니다. 주소록, 현재성, 자기 정체성이 같이 있어야 합니다. 사람은 UI와 기억으로 빈칸을 메우지만, 에이전트에게는 명시적인 도구 계약이 필요합니다.

Dogfooding의 가치도 여기에 있습니다. 스펙 회의에서는 "메시지를 보낼 수 있다"가 충분해 보입니다. 실제로 RelayRoom으로 RelayRoom을 만들면 첫날 바로 "누구에게 보내지?"에서 막힙니다. 제품을 제품 자신으로 운영하면 추상적인 누락이 구체적인 장애로 바뀝니다.

그래서 RelayRoom MCP 도구는 메시징 도구에 rosterwhoami를 더했습니다. 새 기능이라기보다, 이미 서버가 알고 있던 협업 상태를 에이전트가 읽을 수 있는 계약으로 승격한 것입니다.

참고 자료

메시지는 파트로 보내는데, 정작 파트 목록을 볼 수가 없었습니다 - RelayRoom