왜 지금 A2A를 다시 보는가
사내에 에이전트가 하나뿐일 때는 통신 표준이 필요 없습니다. 문제는 두 번째, 세 번째 에이전트가 생기는 순간부터입니다. 검색 에이전트, 승인 에이전트, 리포트 에이전트가 각자 다른 팀에서 다른 프레임워크(LangChain, 사내 자체 오케스트레이터, Claude Agent SDK 등)로 만들어지면, 서로를 부르기 위한 글루 코드가 에이전트 수의 제곱에 가깝게 늘어납니다. N개가 서로 붙으면 최악의 경우 N×(N-1)/2개의 커스텀 연동이 생깁니다. Google이 2025년 4월 공개하고 이후 Linux Foundation으로 거버넌스를 이관한 A2A(Agent2Agent) 프로토콜은 이 문제를 “표준 인터페이스로 에이전트를 찾고, 능력을 확인하고, 작업을 맡긴다”는 좁은 범위로 풉니다. 이 글은 프로토콜 소개 번역이 아니라, 사내 PoC에서 A2A를 검토할 때 제가 실제로 확인하는 지점을 정리한 것입니다.
MCP와 헷갈리면 설계가 꼬입니다
MCP(Model Context Protocol)와 A2A는 자주 같이 언급되지만 역할이 다릅니다. MCP는 하나의 에이전트가 도구·데이터에 접근하는 수직 관계를 표준화합니다. A2A는 서로 다른 에이전트가 수평으로 협력하는 관계를 표준화합니다. 이 둘을 같은 층으로 취급해서 “MCP 서버를 여러 개 붙이면 멀티에이전트다”라고 설계하면, 각 에이전트의 상태 관리와 권한 경계가 도구 호출 로그에 섞여 버립니다.
| 구분 | MCP | A2A |
|---|---|---|
| 통신 방향 | 에이전트 → 도구/데이터 (단방향) | 에이전트 ↔ 에이전트 (양방향) |
| 상태 | 대체로 무상태 호출 | Task 상태 기계(submitted→working→completed 등) |
| 핵심 객체 | Tool, Resource, Prompt | AgentCard, Task, Message, Artifact |
| 발견 방식 | 서버 설정에 직접 등록 | /.well-known/agent.json 공개 발견 |
| 적합한 질문 | “이 에이전트가 어떤 도구를 쓸 수 있는가” | “이 작업을 누구에게 맡길 것인가” |
구현이 다루는 세 가지 객체
실무에서 먼저 이해해야 하는 것은 슬로건이 아니라 세 가지 객체입니다.
AgentCard는 에이전트의 명함입니다. HTTP GET으로 /.well-known/agent.json을 조회하면 이름, 엔드포인트, 지원 스킬, 인증 방식이 나옵니다. 오케스트레이터는 이 카드만 보고 “이 작업을 이 에이전트에게 맡길 수 있는가”를 판단합니다.
{
"name": "yield-analysis-agent",
"url": "https://internal.example.com/a2a/yield",
"capabilities": { "streaming": true, "pushNotifications": false },
"skills": [
{ "id": "yield-detect", "description": "공정 데이터 이상치 탐지" }
],
"authentication": { "schemes": ["Bearer"] }
}
Task는 A2A 서버가 소유하는 상태 기계입니다. 클라이언트가 임의로 상태를 바꾸지 못하고, 서버가 생성한 taskId로만 추적합니다. 동기(즉시 완료), SSE 스트리밍(진행률 전송), Webhook 비동기(장시간 작업 후 콜백) 세 가지 통신 모드를 상황에 맞춰 고릅니다. 짧은 조회는 동기로, 대량 문서 분석처럼 분 단위가 걸리는 작업은 Webhook으로 처리하는 것이 현실적입니다.
Artifact는 Task 완료 후 반환되는 최종 산출물입니다. 중간 상태는 history에, 결과만 artifact에 담기는 구조라서, 오케스트레이터가 중간 로그와 최종 결과를 혼동하지 않게 설계할 수 있습니다.
멀티 에이전트 장애는 한 곳에서 시작해 전체로 번진다
A2A로 여러 에이전트를 연결하면 얻는 것은 유연성이지만, 동시에 장애 전파 경로도 함께 늘어납니다. 리포트 에이전트가 검색 에이전트를 호출하고, 검색 에이전트가 다시 데이터 에이전트를 호출하는 체인 구조에서 가장 안쪽 에이전트의 응답이 느려지면, 타임아웃이 설정되어 있지 않은 한 이 지연이 체인 전체로 누적됩니다. 실무에서 최소한 지켜야 할 세 가지 원칙이 있습니다.
- 타임아웃은 매 홉마다 명시: 상위 에이전트의 타임아웃이 하위 체인 전체 지연보다 짧으면, 하위 작업이 끝나기 전에 상위가 먼저 포기하고 자원만 낭비합니다. 체인 깊이를 고려해 타임아웃을 역산합니다.
- 서킷 브레이커: 특정 에이전트가 연속으로 실패하면, 호출을 일정 시간 차단하고 대체 경로(캐시된 결과, 대체 에이전트, 사람 개입)로 전환합니다.
- 분산 트레이싱: taskId를 체인 전체에 전파해, 어느 에이전트에서 지연이나 오류가 발생했는지 한 번에 추적할 수 있게 합니다. OpenTelemetry 같은 표준 트레이싱 도구에 taskId를 상관관계 ID로 넣는 것이 실무에서 흔한 패턴입니다.
사내 PoC에서 실제로 걸리는 지점
프로토콜 문서를 읽는 것과 사내 망에 붙이는 것은 다른 일입니다. 제가 PoC 설계에서 먼저 확인하는 항목은 다음과 같습니다.
- 발견 범위를 좁힙니다. 공인 인터넷에 공개하는 것이 아니라면, /.well-known/agent.json을 사내망 안에서만 노출하고, 게이트웨이에서 접근 IP 대역을 제한합니다.
- Task ID 재사용을 막습니다. 클라이언트가 taskId를 추측해 다른 사용자의 작업 상태를 조회하지 못하도록, 서버가 생성한 ID에 소유자 클레임을 같이 검증합니다.
- 스트리밍과 배치를 분리합니다. SSE 연결을 오래 열어 두는 에이전트가 늘어나면 게이트웨이의 커넥션 풀이 먼저 바닥납니다. 장시간 작업은 처음부터 Webhook 모드로 설계합니다.
- 버전 롤아웃 계획을 세웁니다. A2A 스펙은 아직 Linux Foundation과 AAIF 거버넌스 아래 계속 개정되고 있습니다. AgentCard의 version 필드를 무시하지 말고, 호환되지 않는 카드는 명시적으로 거부하게 만듭니다.
보안에서 먼저 잠가야 하는 것
A2A의 설계 원칙 중 “에이전트 불투명성(Agent Opacity)”은 내부 추론 과정과 도구 목록을 외부에 노출하지 않는다는 뜻입니다. 이 원칙을 지키는 것과 별개로, 실무에서 흔히 빠뜨리는 두 가지가 있습니다.
# 위험한 패턴: 외부 AgentCard의 값을 검증 없이 프롬프트에 삽입
name = agent_card["name"] # 신뢰할 수 없는 입력
prompt = f"{name} 에이전트를 호출합니다: {user_query}"
# 개선: 동적 필드를 프롬프트 본문과 분리하고 이스케이프
safe_name = sanitize(agent_card["name"])
prompt = build_prompt(template="agent_call", name=safe_name, query=user_query)
Webhook 콜백을 받는 경우, 발신 에이전트를 자처하는 요청이 위조되지 않았는지 JWT 서명과 발신 에이전트 ID를 함께 검증해야 합니다. 서명 검증만 하고 발신 ID를 대조하지 않으면, 유효한 서명을 가진 다른 에이전트가 남의 Webhook URL로 결과를 흘려보낼 수 있습니다.
한계와 제언
A2A는 아직 안정화 중인 표준입니다. 조직 전체를 이 프로토콜에 맞춰 재설계하기보다는, 실제로 여러 팀·여러 프레임워크의 에이전트가 서로를 불러야 하는 지점에서만 도입하는 것이 안전합니다. 에이전트가 하나거나, 모두 같은 프레임워크 안에 있다면 A2A는 불필요한 추상화 비용입니다. 도입을 결정했다면, MCP는 도구 계층에 그대로 두고 A2A는 에이전트 간 계층에만 올리는 Two-Layer 구조를 권장합니다. 두 계층을 섞으면 권한 경계와 로그가 같이 흐려집니다.
최소 권한은 스킬 단위로 끊는다
AgentCard에 스킬이 다섯 개 있다고 해서 호출자가 다섯 개를 다 쓸 수 있게 두면, 검색 에이전트가 전표 승인을 부릅니다. 위임 토큰에 허용 스킬 ID와 만료, 호출자 ID를 넣고, 서버는 카드가 아니라 토큰을 봅니다. 카드는 발견용 광고입니다.
| 로그 필드 | 남기는 이유 | 남기지 말 것 |
|---|---|---|
| caller_id, callee_id | 누가 누구를 불렀는지 | 프롬프트 전문 |
| skill_id, task_id | 재현과 권한 감사 | 내부 도구 목록 전체 |
| 상태 전이 시각 | 멈춘 작업 추적 | 중간 추론 토큰 |
에이전트가 하나면 A2A를 깔지 않는다
같은 저장소의 같은 오케스트레이터 안에서 함수를 나누는 일은 프로토콜이 아닙니다. A2A 비용은 카드 배포, 인증, 태스크 저장, 웹훅 검증입니다. 이 네 개가 팀 두 개 이상에서 반복될 때만 표준을 올립니다. 그 전에는 MCP로 도구를 붙이고, 호출 그래프를 로그로만 남깁니다.
장시간 작업의 웹훅 URL을 카드에 공개하면, 다른 에이전트가 그 주소로 완료를 위조합니다. 콜백은 구독 시에만 발급하고, 수신 측은 서명과 발신 ID와 만료를 같이 봅니다. 스펙 버전이 다른 카드를 관대하게 수락하면, 필드 하나가 빠진 산출물이 다음 에이전트의 입력이 됩니다. 거부 목록을 게이트웨이에 두십시오.
Takeaway
- A2A는 모델 API가 아니라 에이전트가 서로를 발견하고 작업을 맡기는 계약입니다.
- MCP(도구 계층)와 A2A(에이전트 계층)를 같은 층으로 섞지 마십시오. 권한 경계가 흐려집니다.
- AgentCard의 값은 신뢰할 수 없는 입력입니다. 프롬프트에 그대로 삽입하지 말고 이스케이프하십시오.
- 체인이 깊어질수록 타임아웃·서킷 브레이커·분산 트레이싱 없이는 한 에이전트의 지연이 전체로 번집니다.
- 표준이 아직 이동 중입니다. 벤더 SDK에 잠기지 말고, 메시지 스키마를 자체 로그로 남겨 두십시오.