콘텐츠로 이동
Study NoteAgent 배포 플랫폼

A2A 프로토콜 — Agent Card·message/send·Task

결론부터
A2A는 Agent를 tool이 아니라 대화 상대로 부르는 계약이다 — Agent Card로 발견하고 message/send로 Message를 보내면 Task가 돌아오며, 긴 작업은 상태 전이와 SSE event로 따라간다
이 장에서 처음 나오는 말6개
A2AAgent2Agent Protocol
서로 다른 framework로 만든 Agent끼리, 또는 client가 Agent를 부를 때 쓰는 프로토콜이다. Google이 2025년 4월 제안했고 지금은 Agentic AI Foundation 소속이다.
Agent Card
Agent가 이름·endpoint·지원 기능·skill·인증 방식을 적어 잘 알려진 URL에 두는 JSON 문서다. client는 이것부터 읽는다.
Message
한 턴의 발화다. role(user 또는 agent)과 parts(text·file·data)로 구성된다.
Task
Agent가 Message를 받아 만드는 작업 단위다. id·상태·artifacts·history를 갖고, 끝날 때까지 여러 Message가 오갈 수 있다.
Artifact
Task가 만들어 낸 결과물이다. 답변 text, 생성 파일, 구조화 데이터가 parts로 담긴다.
opaque agent
A2A의 전제다. 부르는 쪽은 Agent의 내부 model·tool·memory를 모르고 Message와 Task만 본다.

MCP 장이 Agent와 tool 사이의 wire를 읽었다면, 이 장은 client와 Agent 사이의 wire를 읽는다. 이 덱의 백엔드가 kagent controller의 8083에 보내는 것, 10장의 adapter가 검증하는 Agent Card, 12장 비교 실습이 호출하는 message/send가 모두 이 프로토콜이다. 읽고 나면 Task가 왜 Message가 아닌지, working에서 멈춘 응답을 어떻게 이어 받는지, 그리고 kagent 0.9.x의 method 이름이 왜 최신 스펙과 다른지 답할 수 있다.

두 프로토콜은 같은 JSON-RPC 2.0 위에 있고 둘 다 HTTP로 나른다. 다른 것은 상대가 누구인가다.

MCPA2A
부르는 대상tool·resource·prompt를 제공하는 server자율적으로 일하는 Agent
호출 단위tools/call 한 번에 결과 한 번Message를 보내면 Task가 생기고 여러 턴이 오갈 수 있음
상대의 내부schema로 드러난 함수opaque. model·tool·memory를 모름
발견initialize 뒤 tools/list잘 알려진 URL의 Agent Card
긴 작업progress notification, tasks(실험)Task 상태 전이 + SSE stream + push notification
전송stdio·Streamable HTTPHTTP만. JSON-RPC 외에 gRPC·REST binding도 정의

한 Agent가 tool은 MCP로 쓰고 다른 Agent는 A2A로 부르는 것이 공식 그림이다. 이 덱의 kagent Agent가 정확히 그 모양이라 두 프로토콜을 같은 층으로 보면 kagent 실습 5장이 경고하듯 연결 방향과 권한 주체가 뒤섞인다.

A2A client는 Agent Card를 먼저 읽고 message/send로 Message를 보내며, Agent는 Task를 만들어 상태를 바꾸다가 artifacts를 채워 돌려준다. 긴 작업은 message/stream의 SSE event나 push notification으로 따라간다

Agent Card — 부르기 전에 읽는 문서

섹션 제목: “Agent Card — 부르기 전에 읽는 문서”

client는 endpoint URL을 미리 알아도 Agent Card부터 읽는다. 스펙이 권하는 위치는 https://{host}/.well-known/agent-card.json이다(0.3.0부터. 0.2.x는 agent.json이었고 kagent 0.9.9는 아직 agent.json으로 서빙한다 — 실습 5장이 그 경로다).

{
"protocolVersion": "0.3.0",
"name": "lab-reader",
"description": "Read-only Kubernetes inspector",
"url": "http://kagent-controller.kagent.svc:8083/api/a2a/kagent/lab-reader/",
"version": "1.0.0",
"preferredTransport": "JSONRPC",
"capabilities": { "streaming": true, "pushNotifications": false },
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"],
"securitySchemes": { "bearer": { "type": "http", "scheme": "bearer" } },
"security": [{ "bearer": [] }],
"skills": [{ "id": "inspect-kubernetes-resources", "name": "Inspect resources",
"description": "List and describe resources in allowed namespaces" }]
}
필드client가 하는 일
url·preferredTransport·additionalInterfaces어디로, 어떤 binding(JSONRPC·GRPC·HTTP+JSON)으로 보낼지 결정
capabilities.streaming·pushNotificationsmessage/stream을 써도 되는지, webhook 등록이 되는지
securitySchemes·security어떤 인증 header를 붙여야 하는지. OpenAPI security scheme 형식
skills사람과 라우팅 로직이 읽는 능력 설명. 실행 권한이 아니다
defaultInputModes·defaultOutputModes주고받을 MIME type

포트는 Card의 url에 적힌 것이 전부다. 스펙은 포트를 정하지 않고, kagent BYO Agent가 8080에서 듣는 것과 controller가 8083으로 route를 내주는 것은 kagent의 선택이다. 인증이 필요한 Agent는 인증 뒤에 더 자세한 Card를 주는 agent/getAuthenticatedExtendedCard도 제공할 수 있다.

Message·Task·Artifact — 세 객체만 알면 된다

섹션 제목: “Message·Task·Artifact — 세 객체만 알면 된다”
객체핵심 필드뜻
Messagerole(user·agent), parts[], messageId, taskId?, contextId?한 턴의 발화. 첫 Message에는 taskId가 없고 Agent가 만들어 준다
Partkind: text·file·datatext는 text, file은 file.uri 또는 file.bytes, data는 임의 JSON
Taskid, contextId, status.state, status.message?, artifacts[], history[]작업 단위. contextId로 여러 Task를 한 대화 맥락에 묶는다
ArtifactartifactId, parts[], name?Task의 산출물. 답변도 artifact의 text part다

Task가 Message와 별개인 이유는 작업이 한 턴에 끝나지 않기 때문이다. Agent가 추가 정보를 물으면 Task는 input-required로 멈추고 client는 같은 taskId로 다음 Message를 보낸다. 이 덱의 5장이 말한 session 소유권이 A2A에서는 contextId와 taskId를 누가 발급·보관하느냐의 문제로 나타난다.

Task는 submitted에서 working으로 가고, input-required나 auth-required에서 client의 다음 Message를 기다렸다가 다시 working으로 돌아오며, completed·failed·canceled·rejected 중 하나로 끝난다

completed·failed·canceled·rejected는 종료 상태라 그 뒤로 Message를 보내면 새 Task가 필요하다. input-required·auth-required는 중단이지 종료가 아니다. client가 상태를 보고 다음 행동을 고르는 것이 A2A client 구현의 핵심이고, 12장 실습 코드가 result.status.state만 뽑아 보는 이유다.

0.3.0의 JSON-RPC method는 일곱 개다.

method하는 일
message/sendMessage를 보내고 Task(또는 짧은 답이면 Message)를 받는다. 응답이 올 때까지 기다림
message/stream같은 요청을 SSE로. 상태·artifact event가 흘러오고 종료 상태에서 끝난다
tasks/getTask id로 현재 상태·artifacts·history를 조회
tasks/cancel진행 중인 Task 취소 요청
tasks/pushNotificationConfig/set긴 Task의 상태 변화를 받을 webhook 등록
tasks/resubscribe끊긴 stream을 Task id로 다시 구독
agent/getAuthenticatedExtendedCard인증된 client용 확장 Agent Card

message/send — 가장 흔한 호출이다. 12장 실습이 보내는 것과 같다.

{"jsonrpc":"2.0","id":"req-1","method":"message/send","params":{
"message":{"role":"user","messageId":"m-1",
"parts":[{"kind":"text","text":"kagent 네임스페이스의 Service 수를 tool로 확인해 줘"}]}}}
{"jsonrpc":"2.0","id":"req-1","result":{
"kind":"task","id":"t-42","contextId":"c-7",
"status":{"state":"completed","timestamp":"2026-09-23T09:00:12Z"},
"artifacts":[{"artifactId":"a-1","parts":[{"kind":"text","text":"Service는 3개다: kagent-controller, kagent-tools, kagent-ui"}]}],
"history":[{"role":"user","messageId":"m-1","parts":[{"kind":"text","text":"kagent 네임스페이스의 …"}]}]}}

message/stream — 같은 params를 보내되 응답이 text/event-stream이다. 각 data는 완전한 JSON-RPC response 하나이고 result.kind로 종류를 구분한다. final: true인 status-update가 오면 stream이 끝난다.

data: {"jsonrpc":"2.0","id":"req-2","result":{"kind":"task","id":"t-43","contextId":"c-7","status":{"state":"submitted"}}}
data: {"jsonrpc":"2.0","id":"req-2","result":{"kind":"status-update","taskId":"t-43","contextId":"c-7","status":{"state":"working"},"final":false}}
data: {"jsonrpc":"2.0","id":"req-2","result":{"kind":"artifact-update","taskId":"t-43","contextId":"c-7","artifact":{"artifactId":"a-1","parts":[{"kind":"text","text":"Service는 3개다: …"}]}}}
data: {"jsonrpc":"2.0","id":"req-2","result":{"kind":"status-update","taskId":"t-43","contextId":"c-7","status":{"state":"completed"},"final":true}}

긴 작업을 따라가는 방법은 셋이고 Agent Card의 capabilities가 어느 것이 가능한지 말해 준다.

방법언제
message/send 뒤 tasks/get pollingstreaming 미지원 Agent, 또는 backend가 연결을 오래 못 잡을 때
message/stream SSE사용자 화면에 진행을 보여줄 때. Next.js 채팅 포팅이 이 경로
push notification webhook분 단위 이상 걸리고 client가 떠 있지 않아도 될 때

A2A는 OAuth 흐름을 정의하지 않는다. Agent Card의 securitySchemes가 OpenAPI와 같은 형식으로 “bearer token을 Authorization header에 넣어라” 같은 요구를 말하고, token을 어떻게 얻는지는 배포 환경의 일이다. 그래서 이 덱에서 Grant 검사는 A2A 호출 앞에 있다 — 포털 backend가 5장의 invocation 권한을 판정한 뒤 controller 8083으로 message/send를 보내고, 사용자가 controller에 직접 붙어 Grant를 우회하지 못하게 network에서 막는다. kagent v0.9의 oauth2-proxy 기반 인증도 dashboard 입구의 것이지 A2A route의 per-Agent authorization이 아니다.

A2A는 2025년 7월 30일 0.3.0, 2026년 3월 12일 1.0.0을 냈다. 1.0은 JSON-RPC·gRPC·HTTP+JSON 세 binding을 대등하게 두면서 이름을 통일했다. kagent 0.9.x와 이 덱의 실습 코드는 0.2·0.3 형식이다.

항목0.2·0.31.0
메서드message/send, message/stream, tasks/get, tasks/cancelSendMessage, SendStreamingMessage, GetTask, CancelTask, ListTasks, SubscribeToTask
roleuser·agentROLE_USER·ROLE_AGENT
상태working·input-requiredTASK_STATE_WORKING·TASK_STATE_INPUT_REQUIRED
객체 구분kind: "task" 같은 discriminator제거. binding별 구조로 구분
binding 선언preferredTransport·additionalInterfacessupportedInterfaces[].protocolBinding

새 SDK로 client를 만들 때는 Agent Card의 protocolVersion을 먼저 읽고 어느 이름을 쓸지 정한다. 0.x server에 SendMessage를 보내면 method not found다.

  • A2A는 client와 Agent 사이의 계약이다. MCP와 같은 JSON-RPC 2.0이지만 상대가 opaque한 Agent이고 호출 단위가 Task다.
  • Agent Card(/.well-known/agent-card.json, kagent 0.9.9는 agent.json)를 먼저 읽어 endpoint·binding·인증·능력을 안다. 포트는 Card의 url이 전부다.
  • Message(role·parts)를 보내면 Task(id·status·artifacts)가 돌아온다. input-required는 중단이라 같은 taskId로 이어 보낸다.
  • 긴 작업은 tasks/get polling, message/stream SSE, push notification 셋 중 Card가 허용하는 것으로 따라간다.
  • 인증은 프로토콜 밖이다. 이 덱의 Grant 검사는 A2A 호출 앞에 있다.
  • 1.0은 method·role·state 이름을 바꿨다. kagent 0.9.x는 0.x 이름을 쓰므로 Card의 protocolVersion으로 구분한다.