로컬 AI 인프라 노트 (4/15) — 에이전트 간 메시지 허브 구축

fire-and-forget 웹훅과 ACK 기반 메시지를 분리 설계한 에이전트 통신 아키텍처


핵심 요약

  • 에이전트가 여러 개면 통신 구조가 필요하다 — 없으면 사일로(silo)가 된다
  • 인사이트 전달(fire-and-forget)과 작업 요청(ACK 필요)은 설계가 달라야 한다
  • 웹훅 경로와 메시지 경로를 분리하면 단순한 알림과 추적이 필요한 작업을 동시에 처리할 수 있다

배경

에이전트가 하나일 때는 통신이 필요 없다. 에이전트가 작업하고, 결과를 사용자에게 보여주면 끝이다.

문제는 에이전트가 여러 개일 때 시작된다. 주식 분석 에이전트가 발견한 인사이트를 글 작성 에이전트가 써야 할 때. 생활 정리 에이전트의 투자 전략이 주식 에이전트의 포트폴리오 방향에 영향을 줄 때. 이런 연결이 사람을 통해서만 이루어진다면 병목이 된다.

에이전트가 서로 직접 메시지를 주고받을 수 있는 허브가 필요하다. 그런데 모든 메시지가 같은 성격은 아니다. "참고해" 수준의 전달과 "이 작업 해줘, 끝나면 알려줘" 수준의 요청은 다른 메커니즘이 필요하다.


본문

1. 두 가지 통신 경로를 나눈 이유

단일 API로 모든 통신을 처리하는 설계는 빠르게 복잡성 폭발로 이어진다. 단순한 인사이트 전달에도 ACK를 대기하게 되고, 중요한 작업 요청이 인사이트 알림에 묻히게 된다.

해결책은 경로 분리다:

경로 용도 패턴 추적
Webhooks 인사이트/알림 전달 fire-and-forget 없음
Agent Messages 작업 요청/응답 ACK 필수 전체

Webhooks (fire-and-forget): "이런 걸 발견했어"라고 던지고 끝. 받는 쪽이 읽든 안 읽든 보내는 쪽은 신경 쓰지 않는다. 인사이트, 모니터링 데이터, 상태 업데이트 같은 것들이 여기를 탄다.

Agent Messages (ACK 기반): "이 작업 해줘"라고 보내고, 받는 쪽이 확인했다는 응답(ACK)을 돌려줘야 한다. 작업 요청, 데이터 전달 후 처리 확인, 에이전트 간 협업 작업이 여기를 탄다.


2. Webhooks 경로 — 가볍고 빠른 전달

Webhooks의 API 패턴은 단순하다:

POST /api/webhooks
{
  "content": "전달할 내용",
  "tags": ["태그1", "태그2"],
  "source": "보내는-에이전트"
}

응답을 기다리지 않는다. HTTP 200이 오면 전달 완료. 받는 쪽에서 언제 읽을지, 어떻게 처리할지는 보내는 쪽의 관심사가 아니다.

이 경로를 쓰는 상황: - 리서치 에이전트가 발견한 데이터를 허브에 기록 - 모니터링 에이전트가 서비스 상태를 보고 - 한 에이전트가 발견한 패턴을 다른 에이전트들이 참고할 수 있도록 공유

오버헤드가 거의 없어서 에이전트가 자유롭게 인사이트를 공유할 수 있다. "혹시 쓸모 있을까?" 싶은 정보도 부담 없이 보낼 수 있는 구조다.


3. Agent Messages 경로 — 추적과 확인이 필요한 통신

작업을 요청하는 메시지는 다른 수준의 안정성이 필요하다.

POST /api/agent-messages
{
  "fromAgent": "보내는 에이전트",
  "toAgent": "받는 에이전트",
  "messageType": "task_request",
  "intent": "작업 의도 설명",
  "body": {
    "text": "구체적인 요청 내용"
  },
  "dedupeKey": "고유키"
}

이 경로의 핵심 메커니즘:

인박스 패턴: 각 에이전트는 자신의 인박스를 가진다.

GET /api/agent-inbox/{에이전트이름}

에이전트가 활성화되면 자신의 인박스를 확인하고, 대기 중인 메시지를 처리한다.

ACK(확인응답): 메시지를 받으면 반드시 ACK를 보내야 한다.

POST /api/agent-messages/{messageId}/ack
{
  "status": "acked",
  "response": { ... }
}

ACK가 오지 않으면 메시지가 처리되지 않은 것이다. 재전송 로직이나 타임아웃 처리가 가능해진다.

중복 방지: dedupeKey로 같은 메시지가 중복 처리되는 것을 방지한다. 네트워크 문제로 재전송되더라도 같은 키의 메시지는 한 번만 처리된다.


4. 에이전트 이름 규칙

에이전트 이름에는 규칙이 필요하다. API가 자동으로 정규화(normalize)하기는 하지만, 원칙적으로 공백을 포함한 한글 이름을 사용한다.

  • 올바른 예: "글 작성 디렉터", "주식 정보 관리 디렉터"
  • 피할 예: "글-작성-디렉터", "write_score_agent"

이유는 단순하다. 사용자가 에이전트를 부르는 방식과 시스템 내부의 이름이 일치해야 혼란이 없다. 기술적 편의를 위해 하이픈이나 언더스코어를 쓰면 "어떤 이름이 맞지?" 하는 상황이 반복된다.


5. 실제 사례: 에이전트 간 블로그 소재 전달

이 구조가 작동하는 흐름을 하나 살펴보자.

  1. 생활 정리 에이전트가 사용자의 경험/결정을 정리하면서 "이건 블로그 소재가 될 수 있겠다"고 판단
  2. 웹훅으로 허브에 인사이트 전달: POST /api/webhooks — 태그에 "블로그소재" 포함
  3. 글 작성 에이전트가 세션 시작 시 허브에서 관련 인사이트를 확인
  4. 소재가 유효하다고 판단되면, 메시지 경로로 작업 시작: 리서치 → 소스 파일 작성 → 검증 → 블로그 글 작성
  5. 블로그 글 초안이 완성되면 사용자에게 컨펌 요청

핵심은 1~2단계가 웹훅(fire-and-forget)이고 나머지가 에이전트 자체의 파이프라인이라는 점이다. 인사이트 전달은 가볍게, 실제 작업은 각 에이전트가 자율적으로.

생활 정리 에이전트가 글 작성 에이전트에게 "이 주제로 글을 써줘"라고 직접 요청할 경우에는 메시지 경로(POST /api/agent-messages)를 사용한다. 작업 완료 후 ACK로 결과를 돌려준다.


설계 함정과 대응 패턴

단일 경로 설계의 문제: 모든 통신을 하나의 API로 처리하면 인사이트 알림에도 ACK가 요구되어 에이전트 처리 부담이 늘어난다. 중요한 작업 요청이 알림 사이에 묻히는 우선순위 역전 현상도 발생한다. 경로를 분리하면 각 통신 유형이 필요한 만큼의 오버헤드만 부담한다.

이름 불일치 문제: 에이전트 이름을 시스템 내부와 외부에서 다르게 쓰면 인박스 조회 시 매칭 실패가 발생한다. API 레벨에서 정규화를 적용하되, 사용자 가시 이름과 내부 식별자를 동일하게 유지하는 것이 원칙이다.

dedupeKey 누락의 위험: 네트워크 타임아웃으로 메시지가 재전송될 때 dedupeKey가 없으면 같은 작업이 중복 실행된다. 모든 작업 요청 메시지에 결정론적(deterministic) 고유 키를 포함하는 것이 필수다. 키 생성 전략: {fromAgent}:{toAgent}:{intent}:{timestamp} 패턴으로 중복 가능성을 최소화한다.


마무리

에이전트가 하나일 때는 통신 구조가 필요 없다. 두 개가 되면 "있으면 좋겠다" 수준이다. 세 개 이상이 되면 없으면 안 된다.

핵심 설계 원칙은 단순하다: 가벼운 전달은 웹훅, 추적이 필요한 작업은 메시지. 이 구분 하나로 통신 구조의 복잡성이 관리 가능한 수준으로 내려온다.

에이전트 간 통신에서 어려운 것은 기술 구현이 아니다. "어떤 통신에 어떤 수준의 안정성이 필요한가"를 판단하는 것이 설계의 본질이다. 그 판단을 구조로 굳히는 것이 메시지 허브의 역할이다.

시리즈 전체 안내: 시리즈 목차

댓글

이 블로그의 인기 게시물

"LLM 핵심 학습 (1/6) — 기본: 토큰화·임베딩·어텐션·위치 인코딩"

"LLM 핵심 학습 (2/6) — 파인튜닝: LoRA·QLoRA·증류·Adapter"

"ML 기초 학습 (1/9) — 머신러닝과 sklearn: 학습의 좌표계"