메시지는 파트로 보내는데, 정작 파트 목록을 볼 수가 없었다
RelayRoom으로 RelayRoom을 만들다가, 메인 에이전트에게 물었다. "지금 릴레이룸에 어떤 에이전트들이 연결돼 있어?" 대답을 못 했다. 메시지는 보낼 수 있는데, 누구에게 보낼지는 알 수 없었다.
이건 작은 UX 누락이 아니었다. 멀티 에이전트 협업 시스템에서 동료 명단은 주소록이자 라우팅 테이블이다. 사람이 화면을 보고 채워 넣던 정보를 에이전트에게는 도구로 주지 않은 것이다.
당시 도구는 메시징 중심이었다
초기 MCP 도구는 9개였다. send, reply, inbox, ack, event, threads, show, close, search. 스레드를 만들고, 답장하고, inbox를 훑고, 읽음 처리하고, 작업 이벤트를 남기고, 검색하는 흐름은 꽤 충실했다.
하지만 발견(discovery)이 0이었다. send와 reply는 backend, 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조차 다시 확인해야 한다.
그래서 에이전트-우선 설계에서는 다음 원칙이 필요하다.
- 사람이 보는 주요 상태는 에이전트도 도구로 읽을 수 있어야 한다.
- 에이전트가 주소로 사용하는 값은 에이전트가 discovery할 수 있어야 한다.
- 재시작, 컴팩션, 세션 이동 후에도 자기 정체성을 재구성할 수 있어야 한다.
수정: roster와 whoami
스키마 변경은 필요 없었다. 데이터는 이미 있었다. agent와 agent_connection 계열 데이터가 프로젝트 안의 part, 연결 상태, main 여부를 알고 있었다. 빠진 것은 노출면이었다.
그래서 도구 두 개를 추가했다.
roster
roster는 현재 프로젝트의 협업 대상 목록을 돌려준다. 목적은 send/reply의 대상 후보를 에이전트가 직접 발견하게 하는 것이다.
예시 형태는 다음과 같다.
{
"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 프로젝트를 오가다 돌아왔을 때 "내가 누구로 연결되어 있지?"를 안전하게 확인할 수 있다.
왜 threads나 inbox로 충분하지 않은가
기존 도구만으로도 과거 대화에서 part 이름을 추론할 수는 있다. 하지만 그것은 discovery가 아니라 archaeology다. 과거 메시지에 등장하지 않은 part는 찾을 수 없고, 이름이 바뀌었거나 offline인 part도 놓친다. 무엇보다 에이전트가 "목록을 찾아야 한다"는 목적을 위해 대화 기록을 검색하게 만들면 토큰과 시간이 낭비된다.
roster는 상태 조회고, threads는 대화 조회다. 둘의 책임은 다르다. 협업 시스템에서 directory는 메시지 기록의 부산물이 아니라 1급 기능이어야 한다.
가져갈 것
멀티 에이전트 시스템은 메시지 버스만으로 완성되지 않는다. 주소록, 현재성, 자기 정체성이 같이 있어야 한다. 사람은 UI와 기억으로 빈칸을 메우지만, 에이전트는 명시적인 도구 계약이 필요하다.
Dogfooding의 가치도 여기에 있다. 스펙 회의에서는 "메시지를 보낼 수 있다"가 충분해 보인다. 실제로 RelayRoom으로 RelayRoom을 만들면 첫날 바로 "누구에게 보내지?"에서 막힌다. 제품을 제품 자신으로 운영하면 추상적인 누락이 구체적인 장애로 바뀐다.
그래서 RelayRoom MCP 도구는 메시징 도구에 roster와 whoami를 더했다. 새 기능이라기보다, 이미 서버가 알고 있던 협업 상태를 에이전트가 읽을 수 있는 계약으로 승격한 것이다.
참고 자료
- Model Context Protocol, Tools specification
- RelayRoom, MCP 도구 레퍼런스
- RelayRoom, RELAYROOM.md 역할과 프로젝트 규범