우리 팀 채팅창에서 AI 에이전트들이 서로 엇갈려 대화하고 있었다. 그래서 프로토콜을 만들었다.
요약
여러 AI 에이전트가 협업하는 환경에서 발생하는 통신 혼선 문제를 해결하기 위해 'IACP(에이전트 간 통신 프로토콜)'를 설계하고 구현한 사례를 다룹니다. 에이전트들이 서로의 상태와 메시지를 인지할 수 있도록 구조화된 정체성(Identity)을 부여하는 방식을 제안합니다.
핵심 포인트
- 에이전트 간 공유된 맥락이 부재할 경우 발생하는 통신 충돌 문제 분석
- IACP(Inter-Agent Communication Protocol)를 통한 에이전트 정체성 부여
- 구조화된 메시지 형식을 통해 에이전트 간의 대화 이력 주입 및 맥락 유지
- 단순 하트비트 방식이 아닌 상태와 정체성 중심의 설계 필요성
팀원이 우리 팀 채팅창에 내 AI 에이전트가 게시한 메시지에 답장을 보냈습니다. 그러자 다른 AI 에이전트가 그들에게 답변을 했습니다. 그 에이전트는 그들이 무엇에 대해 이야기하고 있는지 전혀 알지 못했습니다.
상황은 이렇습니다: 제 홈랩(homelab)은 소규모 에이전트 군단을 운영하고 있습니다. 제 Mac에서 실행되는 Claude Code 세션이 핵심적인 작업을 수행합니다. WhatsApp 봇 — 메시지당 새로운 Claude 프로세스를 생성하는 Python 래퍼(wrapper) — 이 제 소규모 팀과 함께하는 "Claude helper" 그룹 채팅방에 상주하고 있습니다. 데스크톱 세션이 작업을 마치면, 저는 종종 그 결과를 해당 채팅방에 게시하라고 명령합니다. 그러면 에이전트는 게시를 합니다. 그다음 사람이 그 메시지에 답장을 하면... 채팅방에 상주하는 봇은 해당 게시물을 본 적이 없는 자신의 대화 기록을 재개합니다. 매번 양방향으로 혼란이 발생했습니다.
원인은 WhatsApp 브리지(bridge)에 있는 의도적인 코드 한 줄이었습니다:
if (msg.key.fromMe) continue; // 우리가 보낸 메시지는 무시함
모든 에이전트는 동일한 브리지 계정을 통해 게시하므로, 모든 에이전트의 게시물은 fromMe가 됩니다. 즉, 설계상 봇의 대기열(queue)에는 보이지 않습니다. 봇에 버그가 있었던 것이 아닙니다. 아키텍처에 "다른 에이전트가 무언가를 말했다"라는 개념이 없었던 것입니다.
그 증상 하나만 패치할 수도 있었습니다. 하지만 진짜 진단 결과는 더 좋지 않았습니다: 제 에이전트들은 섬(islands)이었습니다. 공유된 시맨틱 메모리(semantic memory) 서버(이전 포스트의 주제)가 그들에게 공통된 장기적(long-term) 지식을 제공했지만, 에이전트 A가 지금 당장 무엇을 하고 있는지, 어떤 리소스를 보유하고 있는지, 또는 공유 채널에 방금 무엇을 게시했는지를 에이전트 B에게 알려주는 것은 아무것도 없었습니다. 에이전트가 두 개라면 어색한 정도겠지만, 제가 계속 추가하고 있는 군단 — Claude, Grok, 로컬 GLM, cron 데몬(daemons) — 이라면 충돌 경로에 놓이게 됩니다.
그래서 우리는 프로토콜을 작성했습니다. IACP: 에이전트 간 통신 프로토콜 (Inter-Agent Communication Protocol). 세 단계로 구성되며, 이미 운영 중인 인프라 위에서 하루 만에 배포되었습니다.
정체성 우선 (Identity first)
이제 에이전트가 공유 시스템에 내보내는 모든 메시지는 구조화된 정체성을 포함합니다:
{agent_id}@{host}:{session} 예: claude@greenmac:8f3a2c
agent_id는 내 메모리 서버가 이미 강제하고 있는 레지스트리(registry)에서 가져옵니다. 즉, 하나의 네임스페이스(namespace)를 사용하므로 새로운 장부 기록이 필요 없습니다. 브릿지의 /send 엔드포인트는 source 필드를 받아 이를 히스토리 파일에 기록합니다. 그룹 봇은 매 답변을 하기 전에 해당 히스토리를 가져와서, 자신의 마지막 차례 이후에 다른 에이전트들이 게시한 모든 내용을 주입(inject)합니다:
[claude@greenmac:8f3a2c가 이 그룹에 게시함] 카메라 수정 사항을 배포했습니다. 현재 서비스를 재시작 중입니다.
어떤 에이전트의 게시물에 답글을 달더라도, 봇은 당신이 정확히 무엇을 참조하고 있는지 알게 됩니다. 원래의 사건은 해결되었습니다.
상태(Presence), 그리고 내가 저지를 뻔한 5초의 실수
나의 첫 번째 본능은 하트비트(heartbeat)였습니다. 즉, 모든 에이전트가 5초마다 자신이 무엇을 하고 있는지 보고하는 방식이었죠. 지연 시간(latency)은 괜찮았습니다. LAN POST는 밀리초(ms) 단위니까요. 하지만 설계는 여전히 잘못되어 있었고, 이를 인정하는 데는 약간의 시간이 걸렸습니다. 그 이유는 바로 LLM 에이전트는 추론(inference) 중간에 스스로를 보고할 수 없다는 점 때문이었습니다. 모델이 생각하는 동안에는 백그라운드 스레드(background thread)가 존재하지 않습니다. 오직 래퍼(wrapper)만이 지속적으로 실행될 뿐입니다. 게다가 고정된 간격으로 쏟아지는 데이터(firehose)는 어차피 99%가 노이즈일 것입니다.
대신 실제로 구현된 방식은 다음과 같습니다. 에이전트는 전환(transitions)(started, working, blocked, done, idle) 시점에 상태를 게시하고, 래퍼는 60초마다 생존 신호(liveness)를 갱신하며, 모든 항목은 TTL(Time To Live, 기본값 120초)을 가집니다. 시스템이 충돌하면 해당 항목은 조용히 만료됩니다. 오래된 잠금(stale locks)이나 별도의 정리 작업(cleanup job)이 필요 없습니다. 레지스트리는 이미 보유하고 있던 모니터링 서버에 덧붙인 약 80줄 정도의 코드이며, 이제 대시보드 카드에는 플릿(fleet)의 실시간 상태가 표시됩니다. 누가 무엇을 작업 중인지, 어떤 리소스를 점유하고 있는지 말이죠.
POST /monitor/agents/state
{"identity":"claude@greenmac:8f3a2c","status":"working",
"task":"migrating whatsapp bot","claims":["repo:whatsapp-bridge"],"ttl":900}
클레임(Claims)은 권고용 임대(advisory leases)입니다 — repo:X, service:Y, channel:jid와 같은 형식입니다. 공유 인프라를 건드리기 전에, 에이전트는 누가 무엇을 보유하고 있는지 목록을 확인합니다. 강제적인 잠금(hard locking)은 없습니다. TTL이 있기 때문에 최악의 경우에도 2분 안에 스스로 복구(self-heals)됩니다.
STOP 문제
멀티 에이전트 협업 (multi-agent coordination)의 판타지 버전은 에이전트 B가 에이전트 A의 작업 도중에 끼어드는 모습입니다. 구조적으로 이는 불가능합니다. A는 추론 (inference) 중인 모델이기 때문입니다. 솔직한 버전은 다음과 같습니다. 에이전트가 실제로 행동할 수 있는 시점인 **턴 경계 (turn boundaries)**에서 메시지를 전달하는 것입니다.
각 에이전트는 수신함 (inbox, 읽을 때 비워짐, 정확히 한 번 전달 (exactly-once))을 가집니다. 그런 다음 두 개의 Claude Code 훅 (hook)이 메시지 전달을 자발적인 것이 아닌 기계적인 방식으로 만듭니다. 세션 시작 시, 스크립트가 현재 실행 중인 플릿 (fleet)의 스냅샷을 컨텍스트 (context)에 주입합니다. 매 턴이 끝날 때마다, Stop 훅이 수신함을 비웁니다. 만약 메시지가 존재한다면, 이는 exit 2를 실행하며, 이는 에이전트가 메시지를 처리하기 전까지는 모델이 작업을 마치는 것을 차단 (blocks the model from finishing) 합니다:
COUNT=$(curl -s -m 4 "$INBOX_URL" | python3 -c '...count...')
if [ "$COUNT" -gt 0 ]; then
echo "IACP inbox: $COUNT message(s) — address before finishing" >&2
...
이것이 현실적인 STOP 메커니즘입니다. 선점 (preemption)이 아니라, 어떤 에이전트도 읽지 않은 메일을 남겨둔 채 턴을 종료하지 않도록 보장하는 것입니다.
개발 과정 중 가장 좋았던 순간은 권한 분류기 (permission classifier)가 제 에이전트가 자신의 훅 스크립트를 git-commit 하는 것을 거부했을 때였습니다. 제가 채팅에서 승인했음에도 불구하고 말이죠. 에이전트가 자신의 하네스 (harness)에 훅을 설치하는 것은 자기 수정 (self-modification)이며, 이는 대화형 동의만으로는 통과할 수 없는 엄격한 경계 뒤에 있는 것으로 보입니다. 결국 제가 직접 커밋을 실행해야 했습니다. 저는 그런 설계를 하지 않았지만, 이는 에이전트가 창의력을 발휘하기로 마음먹은 날에 바로 당신이 원하는 특성입니다.
이것은 그저 A2A / MCP / ACP 아닌가요?
아니요 — 축(axis)이 다릅니다. MCP는 에이전트를 _도구(tools)_에 연결합니다. Google의 A2A (그리고 현재 이에 통합되고 있는 IBM의 ACP)는 _낯선 존재(strangers)_를 연결합니다. 즉, OAuth와 권한 발견(capability discovery)을 통해 신뢰 경계(trust boundaries)를 넘어 서로 작업을 위임하는 서로 다른 벤더의 불투명한(opaque) 에이전트들을 연결하는 것입니다. IACP는 _공동 테넌트(co-tenants)_를 조정합니다. 저의 플릿(fleet)은 이미 멀티 벤더(Anthropic, xAI, Zhipu)이지만, 하나의 운영자, 하나의 LAN, 하나의 WhatsApp 번호를 공유합니다. 따라서 에이전트들이 작업 인터페이스 뒤에 숨는 대신 서로에게 투명해질 수 있습니다. 상호 운용성(interop) 프로토콜에는 존재감(Presence), 리소스 점유(resource claims), 그룹 채팅 문맥 복구(group-chat context repair) 기능이 존재하지 않는데, 이는 낯선 존재들은 파일 시스템을 공유하지 않기 때문입니다.
| MCP | A2A (= Agent2Agent) | ACP | IACP | |
|---|---|---|---|---|
| 축 (Axis) | 에이전트↔도구 | 에이전트↔에이전트 (위임) | 에이전트↔에이전트 (위임) | 에이전트↔에이전트 (조정) |
| ... | ||||
저는 A2A의 어휘를 빌려왔습니다. input-required와 같은 작업 상태(task states)는 그것에 바로 매핑됩니다. 따라서 외부 에이전트가 참여해야 할 일이 생기더라도, 그 가교는 재작성(rewrite)이 아닌 심(shim) 수준에서 해결됩니다. |
이것이 지향하는 방향
설계 문서의 추측적인 결론은, 소유자가 _해결된 문제(solved problems)_를 공용 네트워크에 방송하기로 선택한 에이전트들을 상상합니다. 이는 Bitcoin 노드가 트랜잭션을 전파하는 방식과 같은 가십(gossip) 스타일입니다. 멋진 점은, 지식에는 이중 지불(double-spend)이 없으므로 합의(consensus)에 도달해야 할 것이 없다는 것입니다. 블록체인도, 작업 증명(proof-of-work)도 필요 없습니다. 릴레이(relays)를 통한 서명된 이벤트(Nostr와 libp2p가 이미 배관 작업을 완료했습니다)만 있으면 됩니다. Bitcoin은 상태를 희소하게 만들기 위해 의도적으로 에너지를 소비하며, 이는 화폐가 요구하는 사항입니다. 지식 네트워크는 더 쉬운 것을 보호하므로, 아주 적은 전력으로 탈중앙화된 정신(decentralized ethos)을 계승합니다. 그리고 모든 게시된 솔루션은 어딘가에 있는 수백만 명의 에이전트들이 그것을 다시 유도(re-derive)하지 않아도 됨을 의미합니다. 에이전트 종(species)을 위한 메모이제이션(Memoization)인 셈입니다. 신뢰(Trust)와 스팸 저항성은 그곳에서 진정으로 해결되지 않은 과제이며, 이것이 해당 섹션이 "4단계"가 아닌 "향후 과제(future work)"로 표시된 이유입니다.
숫자
어느 날. 세 단계. 7개의 리포지토리(repos)를 건드렸고, 실제 코드는 약 400줄 정도였습니다. 왜냐하면 존재(presence)는 기존의 모니터링 서버에 의존하고 있었고, 정체성(identity)은 메모리 서버의 레지스트리(registry)에 의존하고 있었으며, 채널 복구(channel repair)는 제가 몇 주 전 대시보드를 위해 만들어 두었던 메시지 기록 로그(message-history log)에 의존하고 있었기 때문입니다. 이 프로토콜은 대부분 _이미 제가 가지고 있던 것들을 알아차리는 과정_이었습니다.
이제 팀 채팅 테스트는 통과되었습니다. 누구나 어떤 에이전트(agent)의 게시물에든 답글을 달 수 있으며, 봇(bot)은 그들이 무엇을 의미하는지 정확히 파악합니다. 알고 보니 이것이 실제 운영 환경(production)에서의 "멀티 에이전트 오케스트레이션 (multi-agent orchestration)"의 모습이었습니다. 군집 토폴로지(swarm topology) 다이어그램 같은 것이 아니라, 그저 채팅창의 그 누구도 혼란을 느끼지 않는 상태 말입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기