A2A 프로토콜 v1.0의 새로운 점

A2A 프로토콜 v1.0의 새로운 점 (What's New)

이 문서는 A2A 프로토콜 v0.3.0에서 v1.0으로의 변경 사항을 포괄적으로 개괄해요. v1.0 릴리스는 향상된 명확성, 더 강력한 스펙, 중요한 구조적 개선과 함께 프로토콜의 상당한 성숙을 나타내요.

출처: 문서

본문

주요 테마 개요

v1.0 릴리스는 네 가지 주요 테마에 집중해요.

1. 프로토콜 성숙과 표준화

  • a2a.proto를 gRPC 특화 구현 파일에서 보편적이고 규범적인 소스 오브 트루스로 승격
  • 공식 스펙 표준(RFC 8785, RFC 7515)과 가능한 곳에서 google.rpc.Status 활용
  • REST, gRPC, JSON-RPC 바인딩에 업계 표준 패턴을 더 엄격히 준수
  • 명시적 역호환 규칙을 가진 향상된 버전 관리 전략
  • 프로토콜별 매핑을 가진 포괄적 오류 분류(taxonomy)

2. 강화된 타입 안전성과 명확성

  • 판별자(discriminator) kind 필드 제거, JSON 멤버 기반 다형성으로 대체
  • 파괴적 변경: ProtoJSON 스펙 준수를 위해 Enum 값이 kebab-case에서 SCREAMING_SNAKE_CASE로 변경
  • 더 엄격한 필드 명명 규칙(JSON의 camelCase)
  • 더 정밀한 타임스탬프 스펙(밀리초 정밀도의 ISO 8601)
  • Optional vs Required 의미를 더 명확히 하는 더 잘 정의된 데이터 타입

3. 개선된 개발자 경험

  • 일관성과 명확성을 위해 연산 이름 변경
  • 논리적 그룹화를 위해 Agent Card 구조 재구성
  • 버전 관리와 요구사항 선언을 가진 향상된 확장 메커니즘
  • 더 명시적인 서비스 매개변수 처리(A2A-Version, A2A-Extensions 헤더)
  • ID 형식 단순화 — 복잡한 복합 ID(예: tasks/{id})를 제거하고 단순 UUID로 대체
  • 인터페이스별 프로토콜 버전 관리 — 각 AgentInterface가 자체 프로토콜 버전을 지정해 더 나은 역호환성 제공
  • 멀티테넌시 지원 — gRPC 요청에서 네이티브 테넌트 스코핑

4. 엔터프라이즈 대비 기능

  • JWS와 JSON 정규화(JSON Canonicalization)를 사용한 Agent Card 서명 검증
  • 동등성 보장을 가진 세 가지 프로토콜 바인딩의 공식 스펙
  • 상호 TLS 지원을 가진 향상된 보안 스킴 선언
  • 현대 OAuth 2.0 플로우 — Device Code 플로우(RFC 8628) 추가, 폐지된 implicit/password 플로우 제거
  • PKCE 지원 — 보안 강화를 위해 Authorization Code 플로우에 pkce_required 필드 추가
  • 확장 가능한 태스크 목록을 위한 커서 기반 페이지네이션

핵심 연산의 동작 변경

Send Message (message/send → SendMessage)

v0.3.0 동작:

  • 연산 이름이 message/send
  • Task 대 Message가 언제 반환되는지에 대한 공식성이 덜함

v1.0 변경:

  • ✅ 이름 변경: 연산이 이제 SendMessage
  • ✅ 명확화: Task vs Message 반환 의미에 대한 더 정밀한 스펙

Send Streaming Message (message/stream → SendStreamingMessage)

v0.3.0 동작:

  • 연산 이름이 message/stream
  • 스트림 이벤트에 kind 판별자 필드가 있었음

v1.0 변경:

  • ✅ 이름 변경: 연산이 이제 SendStreamingMessage
  • ✅ 파괴적 변경: 스트림 이벤트에 더 이상 kind 필드 없음. TaskStatusUpdateEvent와 TaskArtifactUpdateEvent를 구분하려면 JSON 멤버 이름을 사용
  • ✅ 제거: TaskStatusUpdateEvent에서 final boolean 필드 제거. 대신 프로토콜 바인딩 특화 스트림 종료 메커니즘 활용
  • ✅ 명확화: 여러 동시 스트림 허용, 모두 동일한 순서의 이벤트 수신

Get Task (tasks/get → GetTask)

v0.3.0 동작:

  • 연산 이름이 tasks/get
  • 상태, 아티팩트, 선택적으로 이력을 포함한 태스크 반환
  • "include history"가 무엇을 의미하는지에 대한 공식성이 덜함

v1.0 변경:

  • ✅ 이름 변경: 연산이 이제 GetTask
  • ✅ 명확화: 이력 포함 동작에 대한 더 정밀한 스펙
  • ✅ 새로움: Task 객체가 이제 메시지와 아티팩트에 extensions[] 배열 포함
  • ✅ 명확화: 인증/권한 스코핑 — 서버는 호출자에게 보이는 태스크만 반환해야 함

List Tasks (tasks/list → ListTasks)

v0.3.0 동작:

  • 연산 사용 불가.

v1.0 변경:

  • ✅ 새로움: 필터링 기능을 가진 새 연산 ListTasks
  • ✅ 명확화: 태스크 가시성이 인증된 호출자로 스코프됨

Cancel Task (tasks/cancel → CancelTask)

v0.3.0 동작:

  • 연산 이름이 tasks/cancel
  • taskId로 요청, Task 반환

v1.0 변경:

  • ✅ 이름 변경: 연산이 이제 CancelTask
  • ✅ 명확화: 취소가 허용되는 시점에 대한 더 정밀한 스펙
  • ✅ 명확화: 취소 시나리오의 태스크 상태 전이

Get Agent Card (잘 알려진 URI와 GetExtendedAgentCard)

v0.3.0 동작:

  • /.well-known/agent-card.json을 통한 발견
  • agent/getAuthenticatedExtendedCard를 통한 확장 카드
  • 최상위의 supportsAuthenticatedExtendedCard boolean

v1.0 변경:

  • ✅ 이름 변경: agent/getAuthenticatedExtendedCard → GetExtendedAgentCard
  • ✅ 파괴적 변경: supportsAuthenticatedExtendedCard가 capabilities.extendedAgentCard로 이동
  • ✅ 새로움: Agent Card 서명에 대한 정규화(RFC 8785) 명확화
  • ✅ 파괴적 변경: protocolVersion이 AgentCard에서 개별 AgentInterface 객체로 이동
  • ✅ 파괴적 변경: preferredTransport와 additionalInterfaces가 supportedInterfaces[]로 통합. 각 인터페이스는 url, protocolBinding, protocolVersion을 가짐

Subscribe to task (tasks/resubscribe → SubscribeToTask)

v0.3.0 동작:

  • 끊긴 SSE 스트림을 다시 연결하는 데 tasks/resubscribe 사용
  • 백필 동작이 구현에 따라 다름

v1.0 변경:

  • ✅ 이름 변경: 연산이 이제 SubscribeToTask
  • ✅ 명확화: 스트리밍 구독 수명주기의 공식 스펙
  • ✅ 명확화: 태스크가 종료 상태에 도달할 때의 스트림 종료 동작
  • ✅ 명확화: 태스크당 여러 동시 구독 지원

푸시 알림 연산

v0.3.0 연산:

  • tasks/pushNotificationConfig/set
  • tasks/pushNotificationConfig/get
  • tasks/pushNotificationConfig/list
  • tasks/pushNotificationConfig/delete

v1.0 변경:

  • ✅ 이름 변경: 이제 CreateTaskPushNotificationConfig, GetTaskPushNotificationConfig, ListTaskPushNotificationConfigs, DeleteTaskPushNotificationConfig
  • ✅ 명확화: 푸시 알림 페이로드가 이제 StreamResponse 형식 사용
  • ✅ 파괴적 변경: 모든 메서드의 모델이 변경되고 TaskPushNotificationConfig가 평탄화됨

새로움: 멀티테넌시 지원

v0.3.0:

  • 프로토콜에 네이티브 멀티테넌시 지원 없음
  • 테넌트가 인증이나 URL 경로로 암시적으로 처리됨

v1.0 변경:

  • ✅ 새로움: 모든 요청 메시지에 tenant 필드 추가
  • ✅ 새로움: 기본 테넌트를 지정하도록 AgentInterface에 tenant 필드 추가
  • ✅ 명확화: 테넌트가 요청별로 제공되고 AgentInterface에서 상속됨
  • ✅ 사용 사례: 단일 엔드포인트에서 여러 에이전트를 서빙할 수 있게 함

프로토콜 단순화

ID 형식 단순화 (#1389)

v0.3.0:

  • 일부 연산이 tasks/{taskId} 같은 복합 복합 ID를 사용
  • 클라이언트/서버가 리소스 이름을 구성/분해해야 했음

v1.0 변경:

  • ✅ 파괴적 변경: 모든 ID가 이제 단순 리터럴
  • ✅ 파괴적 변경: 이전에 복합 ID를 사용한 연산이 이제 상위(super)와 리소스 ID를 분리. 예: tasks/{taskId}/pushNotificationConfigs/{configId} → 별도의 task_id와 id 필드
  • ✅ 이점: 구현이 더 단순 — ID가 데이터베이스 키에 직접 매핑
HTTP URL 경로 단순화 (#1269)

v0.3.0:

  • HTTP+JSON 바인딩이 URL에서 /v1/ 접두사 사용
  • 예: POST /v1/message:send

v1.0 변경:

  • ✅ 파괴적 변경: HTTP+JSON URL 경로에서 /v1 접두사 제거
  • ✅ 새로움: 예: POST /message:send, GET /tasks/{id}
  • ✅ 근거: 에이전트 소유자가 원하면 버전이 기본 url의 일부가 될 수 있음
  • ✅ 이점: 더 깔끔한 URL, 인터페이스 수준에서 버전 관리

핵심 모델 객체의 구조 변경

TaskStatus 객체

수정된 필드:

  • ✅ state: 파괴적 변경 — Enum 값이 소문자에서 TASK_STATE_ 접두사를 가진 SCREAMING_SNAKE_CASE로 변경
    • v0.3.0: "submitted", "working", "completed", "failed", "canceled", "rejected", "input-required", "auth-required"
    • v1.0: "TASK_STATE_SUBMITTED", "TASK_STATE_WORKING", "TASK_STATE_COMPLETED", "TASK_STATE_FAILED", "TASK_STATE_CANCELED", "TASK_STATE_REJECTED", "TASK_STATE_INPUT_REQUIRED", "TASK_STATE_AUTH_REQUIRED"
  • ✅ timestamp: 이제 명시적으로 밀리초 정밀도의 ISO 8601 UTC(YYYY-MM-DDTHH:mm:ss.sssZ)

제거된 필드: 없음

마이그레이션 예시:

// v0.3.0
{
  "status": {
    "state": "completed",
    "timestamp": "2024-03-15T10:15:00Z"
  }
}

// v1.0
{
  "status": {
    "state": "TASK_STATE_COMPLETED",
    "timestamp": "2024-03-15T10:15:00.000Z"
  }
}

Message 객체

추가된 필드:

  • ✅ extensions[]: 이 메시지에 적용되는 확장 URI 배열

수정된 필드:

  • ✅ role: 파괴적 변경 — Enum 값이 소문자에서 ROLE_ 접두사를 가진 SCREAMING_SNAKE_CASE로 변경
    • v0.3.0: "user", "agent"
    • v1.0: "ROLE_USER", "ROLE_AGENT"

마이그레이션 예시:

// v0.3.0
{
  "role": "user",
  "parts": [{"kind": "text", "text": "Hello"}]
}

// v1.0
{
  "role": "ROLE_USER",
  "parts": [{"text": "Hello"}],
}

동작 변경:

  • Parts 배열이 이제 kind 필드 대신 멤버 기반 판별을 사용

Part 객체

파괴적 변경 — 완전 재설계: Part 구조는 v1.0에서 완전히 재설계됐어요. 별도의 TextPart, FilePart, DataPart 메시지 타입 대신 이제 단일 통합 Part 메시지가 있어요.

v0.3.0 구조(별도 타입):

// Text example
{
  "kind": "text",
  "text": "Hello world"
}

// File example
{
  "kind": "file",
  "file": {
    "fileWithUri": "https://example.com/doc.pdf",
    "mimeType": "application/pdf"
  }
}

// Data example
{
  "kind": "data",
  "data": {"key": "value"}
}

v1.0 구조(통합 Part):

// Text example
{
  "text": "Hello world",
  "mediaType": "text/plain"
}

// File with URL example
{
  "url": "https://example.com/doc.pdf",
  "filename": "doc.pdf",
  "mediaType": "application/pdf"
}

// File with raw bytes example
{
  "raw": "base64encodedcontent==",
  "filename": "image.png",
  "mediaType": "image/png"
}

// Data example
{
  "data": {"key": "value"},
  "mediaType": "application/json"
}

변경 사항:

  • ⛔ 제거: 별도의 TextPart, FilePart, DataPart 타입
  • ⛔ 제거: kind 판별자 필드
  • ⛔ 제거: 중첩 file 객체 구조
  • ✅ 새로움: oneof content 필드를 가진 단일 통합 Part 메시지
  • ✅ 새로움: 콘텐츠 타입은 어떤 필드가 존재하는지에 따라 결정됨: text, raw, url, 또는 data
  • ✅ 새로움: mediaType 필드(mimeType 대체) — 모든 part 유형에 사용 가능
  • ✅ 새로움: filename 필드 — (파일뿐 아니라) 모든 part 유형에 사용 가능
  • ✅ 새로움: 인라인 이진 콘텐츠용 raw 필드(JSON에서 base64)
  • ✅ 새로움: 파일 참조용 url 필드(file.fileWithUri 대체)

마이그레이션 예시:

// v0.3.0
const textPart = { kind: "text", text: "Hello" };
const filePart = { kind: "file", file: { fileWithUri: "https://...", mimeType: "image/png" } };
const dataPart = { kind: "data", data: { key: "value" } };

// v1.0
const textPart = { text: "Hello", mediaType: "text/plain" };
const filePart = { url: "https://...", mediaType: "image/png", filename: "image.png" };
const dataPart = { data: { key: "value" }, mediaType: "application/json" };

// Discrimination changed from kind field to member presence
if (part.kind === "text") { ... }  // v0.3.0
if ("text" in part) { ... }        // v1.0

Artifact 객체

추가된 필드:

  • ✅ extensions[]: 확장 URI 배열

수정된 필드:

  • ✅ parts[]: 이제 멤버 기반 Part 판별 사용(위 Part 변경 참고)

AgentCard 객체

추가된 필드:

  • ✅ supportedInterfaces[]: AgentInterface 객체 배열

제거된 필드:

  • ⛔ protocolVersion: AgentCard에서 제거(이제 각 AgentInterface에 있음)
  • ⛔ preferredTransport: supportedInterfaces로 통합
  • ⛔ additionalInterfaces: supportedInterfaces로 통합
  • ⛔ supportsAuthenticatedExtendedCard: capabilities.extendedAgentCard로 이동
  • ⛔ url: 기본 엔드포인트가 이제 supportedInterfaces[0].url에 있음

구조 예시:

v0.3.0:

{
  "protocolVersion": "0.3",
  "url": "https://agent.example.com/a2a",
  "preferredTransport": "JSONRPC",
  "supportsAuthenticatedExtendedCard": true,
  "additionalInterfaces": [...]
}

v1.0:

{
  "supportedInterfaces": [
    {
      "url": "https://agent.example.com/a2a",
      "protocolBinding": "JSONRPC",
      "protocolVersion": "1.0"
    }
  ],
  "capabilities": {
    "extendedAgentCard": true
  },
  "signatures": [...]
}

AgentCapabilities 객체

수정된 필드:

  • ✅ extendedAgentCard: 최상위 supportsAuthenticatedExtendedCard 필드에서 이동

PushNotificationConfig 객체

수정된 필드:

  • ✅ authentication: 향상된 PushNotificationAuthenticationInfo 구조

스트림 이벤트 객체

TaskStatusUpdateEvent:

v0.3.0:

{
  "kind": "status-update",
  "taskId": "...",
  "contextId": "...",
  "status": {...},
  "final": true
}

v1.0:

{
  "statusUpdate": {
    "taskId": "...",
    "contextId": "...",
    "status": {...}
  }
}

변경 사항:

  • ⛔ 제거: kind 판별자
  • ⛔ 제거: final boolean 필드(스트림 종료가 완료를 나타냄)
  • ✅ 새 패턴: 이벤트 타입이 JSON 멤버 이름(statusUpdate 또는 artifactUpdate)으로 결정됨
  • ✅ 명확화: 종료 상태가 프로토콜 특화 스트림 종료 메커니즘으로 표시됨

TaskArtifactUpdateEvent:

v0.3.0:

{
  "kind": "artifact-update",
  "taskId": "...",
  "contextId": "...",
  "artifact": {...}
}

v1.0:

{
  "artifactUpdate": {
    "taskId": "...",
    "contextId": "...",
    "artifact": {...},
    "index": 0
  }
}

변경 사항:

  • ⛔ 제거: kind 판별자
  • ✅ 새 패턴: artifactUpdate 객체로 감싸짐
  • ✅ 새로움: index 필드가 태스크의 artifacts 배열에서 아티팩트 위치를 나타냄

OAuth 2.0 보안 업데이트 (#1303)

v1.0은 OAuth 2.0 Security Best Current Practice(BCP)에 맞춰 OAuth 2.0 지원을 현대화해요.

제거된 플로우(OAuth BCP가 폐지):

  • ⛔ ImplicitOAuthFlow — 브라우저 이력/로그의 토큰 유출 위험으로 폐지
  • ⛔ PasswordOAuthFlow — 자격 증명 노출 위험으로 폐지

추가된 플로우:

  • ✅ DeviceCodeOAuthFlow (RFC 8628) — CLI 도구, IoT 기기, 입력 제한 시나리오용
    • device_authorization_url 엔드포인트 제공
    • verification_uri, user_code 패턴 지원
    • 헤드리스 환경에 이상적

강화된 보안:

  • ✅ AuthorizationCodeOAuthFlow(RFC 7636)에 pkce_required 필드 추가
    • PKCE(Proof Key for Code Exchange)가 필수인지 나타냄
    • 인증 코드 가로채기 공격으로부터 보호
    • 모든 OAuth 클라이언트에 권장, 공개 클라이언트에 필수

마이그레이션 가이드:

// v0.3.0 - Implicit Flow (now removed)
{
  "implicitFlow": {
    "authorizationUrl": "https://auth.example.com/authorize",
    "scopes": {"read": "Read access"}
  }
}

// v1.0 - Use Authorization Code + PKCE instead
{
  "authorizationCodeFlow": {
    "authorizationUrl": "https://auth.example.com/authorize",
    "tokenUrl": "https://auth.example.com/token",
    "pkceRequired": true,
    "scopes": {"read": "Read access"}
  }
}

다른 스펙에 대한 새로운 의존성

v1.0은 업계 표준 스펙에 대한 여러 새로운 공식 의존성을 도입해요.

추가된 스펙

✅ google.rpc.Status / google.rpc.ErrorInfo
  • 목적: ProtoJSON 표현을 가진 표준화된 오류 응답 모델
  • 사용: HTTP+JSON과 JSON-RPC 바인딩의 오류 응답
  • 영향: HTTP 오류에 RFC 9457을 대체. A2A 특화 오류에 대해 reason과 domain을 가진 구조화된 ErrorInfo를 시행
✅ RFC 8785 - JSON 정규화 스킴(JCS)
  • 목적: 서명을 위한 결정적 JSON 직렬화
  • 사용: Agent Card 서명 검증
  • 영향: Agent Card 무결성의 암호화 검증 가능
  • 세부사항: JWS 서명 전에 사용되는 정규 형식(signatures 필드 제외)
✅ RFC 7515 - JSON 웹 서명(JWS)
  • 목적: 암호화 서명 표준
  • 사용: Agent Card 서명 필드
  • 영향: 신뢰 검증을 위한 업계 표준 서명 형식
  • 세부사항: jku 또는 신뢰 키 저장소를 통한 공개 키 검색을 가진 분리 서명 지원
✅ Google API 설계 지침
  • 목적: gRPC 모범 관행과 규칙
  • 사용: gRPC 바인딩 설계 패턴
  • 영향: gRPC 생태계 기대와 더 나은 정렬
✅ ISO 8601
  • 목적: 타임스탬프 형식 표준
  • 사용: TaskStatus.timestamp 같은 타임스탬프 필드
  • 영향: 명시적 형식 요구사항: 밀리초 정밀도의 UTC(YYYY-MM-DDTHH:mm:ss.sssZ)

기존 의존성(v0.3.0에서 유지)

  • JSON-RPC 2.0
  • gRPC / Protocol Buffers 3
  • HTTP/HTTPS(다양한 RFC)
  • Server-Sent Events(SSE) — W3C 스펙
  • RFC 8615 — 잘 알려진 URI
  • OAuth 2.0, OpenID Connect(인증용)
  • TLS(RFC 8446 권장)

보완적 프로토콜

Model Context Protocol(MCP):

  • 관계 명확화: MCP는 도구/리소스 통합을, A2A는 에이전트 간 조정을 담당
  • 두 프로토콜은 경쟁이 아니라 보완적
  • 에이전트는 서로 다른 사용 사례에 두 프로토콜을 모두 지원할 수 있음

개발자에 대한 영향

코드 업데이트가 필요한 파괴적 변경

1. Part 타입 통합(치명적 영향)

가장 중요한 파괴적 변경: TextPart, FilePart, DataPart 타입이 제거되고 단일 통합 Part 구조로 대체됐어요.

이전(v0.3.0):

// Separate types with kind discriminator
if (part.kind === "text") {
  return part.text;
} else if (part.kind === "file") {
  if (part.file.fileWithUri) {
    return fetchFile(part.file.fileWithUri);
  } else {
    return part.file.fileWithBytes;
  }
} else if (part.kind === "data") {
  return part.data;
}

이후(v1.0):

// Unified Part with oneof content
if ("text" in part) {
  return part.text;
} else if ("url" in part) {
  return fetchFile(part.url);
} else if ("raw" in part) {
  return decodeBase64(part.raw);
} else if ("data" in part) {
  return part.data;
}
2. 스트림 이벤트 판별자 패턴(높은 영향)

스트림 이벤트가 kind 기반에서 래퍼 기반 판별로 변경됐어요.

이전(v0.3.0):

if (event.kind === "status-update") {
  handleStatusUpdate(event);
} else if (event.kind === "artifact-update") {
  handleArtifactUpdate(event);
}

이후(v1.0):

if ("statusUpdate" in event) {
  handleStatusUpdate(event.statusUpdate);
} else if ("artifactUpdate" in event) {
  handleArtifactUpdate(event.artifactUpdate);
}
3. Agent Card 구조(높은 영향)

에이전트 발견과 역량 확인에 업데이트가 필요해요.

이전(v0.3.0):

const endpoint = agentCard.url;
const transport = agentCard.preferredTransport;
const supportsExtended = agentCard.supportsAuthenticatedExtendedCard;

이후(v1.0):

const primaryInterface = agentCard.supportedInterfaces[0];
const endpoint = primaryInterface.url;
const transport = primaryInterface.protocolBinding;
const supportsExtended = agentCard.capabilities.extendedAgentCard;
4. 페이지네이션(중간 영향)

List Tasks 구현이 페이지 기반에서 커서 기반으로 전환해야 해요.

이전(v0.3.0):

const response = await listTasks({ page: 1, perPage: 50 });

이후(v1.0):

let pageToken = undefined;
do {
  const response = await listTasks({ pageToken, pageSize: 50 });
  // process response.tasks
  pageToken = response.nextPageToken;
} while (pageToken);
5. Enum 값 변경(높은 영향)

모든 enum 값이 이제 타입 접두사를 가진 SCREAMING_SNAKE_CASE를 사용해요.

TaskState:

// v0.3.0
if (task.status.state === "completed") { ... }
if (task.status.state === "input-required") { ... }

// v1.0
if (task.status.state === "TASK_STATE_COMPLETED") { ... }
if (task.status.state === "TASK_STATE_INPUT_REQUIRED") { ... }

MessageRole:

// v0.3.0
const message = { role: "user", parts: [...] };

// v1.0
const message = { role: "ROLE_USER", parts: [...] };

전체 매핑:

  • "submitted" → "TASK_STATE_SUBMITTED"
  • "working" → "TASK_STATE_WORKING"
  • "completed" → "TASK_STATE_COMPLETED"
  • "failed" → "TASK_STATE_FAILED"
  • "canceled" → "TASK_STATE_CANCELED"
  • "rejected" → "TASK_STATE_REJECTED"
  • "input-required" → "TASK_STATE_INPUT_REQUIRED"
  • "auth-required" → "TASK_STATE_AUTH_REQUIRED"
  • "user" → "ROLE_USER"
  • "agent" → "ROLE_AGENT"
6. 필드 이름 변경(낮은 영향)
  • file.mimeType → mediaType
  • 연산 이름(전환 기간 동안 별칭 제공)
7. google.rpc.Status를 통한 표준화된 오류 처리(높은 영향)

HTTP+JSON 오류 응답이 RFC 9457(Problem Details) 대신 google.rpc.Status의 ProtoJSON 표현을 사용하도록 업데이트됐어요. JSON-RPC와 HTTP+JSON 바인딩은 이제 data/details 배열 안의 google.rpc.ErrorInfo를 사용해 A2A 특화 오류 컨텍스트를 제공해요.

변경 사항:

  • HTTP+JSON Content-Type: application/problem+json에서 application/json으로 변경
  • 오류 모델: google.rpc.Status 필드(code, message, details) 사용
  • A2A 오류 정보: details에 reason(A2A 오류 타입의 UPPER_SNAKE_CASE)과 domain: "a2a-protocol.org"를 가진 google.rpc.ErrorInfo 객체를 반드시 포함해야 함

JSON-RPC 마이그레이션 예시:

// v0.3.0
"error": {
  "code": -32001,
  "message": "Task not found",
  "data": { "taskId": "123" }
}

// v1.0
"error": {
  "code": -32001,
  "message": "Task not found",
  "data": [
    {
      "@type": "type.googleapis.com/google.rpc.ErrorInfo",
      "reason": "TASK_NOT_FOUND",
      "domain": "a2a-protocol.org",
      "metadata": { "taskId": "123" }
    }
  ]
}

HTTP+JSON 마이그레이션 예시:

// v0.3.0 (Draft using RFC 9457)
HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{
  "type": "https://a2a-protocol.org/errors/task-not-found",
  "title": "Task Not Found",
  "status": 404,
  "detail": "The specified task ID does not exist"
}

// v1.0
HTTP/1.1 404 Not Found
Content-Type: application/json

{
  "error": {
    "code": 404,
    "status": "NOT_FOUND",
    "message": "The specified task ID does not exist",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "TASK_NOT_FOUND",
        "domain": "a2a-protocol.org"
      }
    ]
  }
}

활용할 새 역량

1. 실행 모드 제어
// Wait for task completion (Default)
const result = await sendMessage(message, { returnImmediately: false });

// Return immediately, poll later
const task = await sendMessage(message, { returnImmediately: true });
2. Agent Card 서명 검증
if (agentCard.signatures && agentCard.signatures.length > 0) {
  const verified = await verifyAgentCardSignature(agentCard);
  if (!verified) {
    throw new Error("Agent Card signature verification failed");
  }
}
3. 확장 요구사항
const requiredExtensions = agentCard.extensions
  .filter(ext => ext.required)
  .map(ext => ext.uri);

// Check if client supports required extensions
if (!clientSupportsAll(requiredExtensions)) {
  throw new Error("Missing required extension support");
}
4. 버전 협상
// Client sends A2A-Version header
headers["A2A-Version"] = "1.0";

// Server validates and rejects if unsupported
if (!supportedVersions.includes(requestedVersion)) {
  throw new VersionNotSupportedError();
}

마이그레이션 전략 권장사항

1단계: 호환성 계층
  • 이전 및 새 판별자 패턴을 모두 파싱하는 지원 추가
  • 프로토콜 버전 기반 버전 감지 구현
  • 전환 동안 두 Agent Card 구조 모두 지원
2단계: 이중 지원
  • 모든 API를 v1.0 형식으로 업데이트
  • v0.3.0용 역호환 리더 유지
  • A2A-Version 헤더 처리 추가
  • 레거시 페이지 기반 옆에 커서 기반 페이지네이션 구현
3단계: v1.0 전용
  • v0.3.0 호환성 코드 폐지
  • 레거시 판별자 파싱 제거
  • 페이지 기반 페이지네이션 제거
  • 이중 형식 지원 코드 정리

역호환 전략(#1401)

v1.0은 SDK 역호환을 가능하게 하는 프로토콜 버전 관리에 대한 공식 접근을 도입해요.

인터페이스별 프로토콜 버전:

  • 각 AgentInterface가 이제 자체 protocolVersion 필드를 지정
  • 에이전트는 여러 인터페이스를 노출해 여러 프로토콜 버전을 동시에 지원할 수 있음
  • 클라이언트는 Agent Card에서 적절한 인터페이스를 선택해 버전을 협상

SDK 구현 패턴:

// SDK can support multiple protocol versions
class A2AClient {
  async connect(agentCardUrl: string) {
    const card = await this.getAgentCard(agentCardUrl);

    // Find best matching interface
    const interface = card.supportedInterfaces.find(i =>
      this.supportedVersions.includes(i.protocolVersion)
    );

    if (!interface) {
      throw new Error("No compatible protocol version");
    }

    // Use version-specific adapter
    return this.createAdapter(interface.protocolVersion, interface);
  }
}

이점:

  • SDK가 여러 프로토콜 버전 지원을 유지할 수 있음
  • 에이전트가 이전·새 버전을 모두 지원해 점진적으로 마이그레이션할 수 있음
  • 클라이언트가 최고의 호환 버전을 자동 선택
  • 이전 프로토콜 버전의 우아한 폐지 가능

테스트 고려사항

  • v0.3.0과 v1.0 형식 데이터 모두로 테스트
  • Agent Card 서명 검증 검증
  • 커서 기반 페이지네이션 엣지 케이스 테스트(빈 결과, 단일 페이지 등)
  • 새 오류 타입의 올바른 처리 검증
  • 확장 요구사항 검증 테스트

권장 우선순위

치명(즉시 수행):

  • Part 및 스트리밍 이벤트 파싱 업데이트(판별자 패턴)
  • Agent Card 파싱 업데이트(구조 변경)
  • 모든 요청에 A2A-Version 헤더 추가

높음(1개월 이내):

  • 커서 기반 페이지네이션 구현
  • enum 값 처리 업데이트(state 필드)
  • return_immediately 매개변수 지원 추가

중간(3개월 이내):

  • Agent Card 서명 검증 구현
  • 확장 요구사항 확인 추가
  • 타임스탬프 처리를 ISO 8601 형식으로 업데이트
  • 새 오류 타입 구현

낮음(있으면 좋음):

  • 향상된 메타데이터 역량 활용
  • 상호 TLS 인증 지원 구현

결론

A2A 프로토콜 v1.0은 v0.3.0의 핵심 아키텍처 원칙을 유지하면서 프로토콜 성숙에서 상당한 진전을 나타내요. 변경 사항은 표준화, 타입 안전성, 엔터프라이즈 준비에 집중하며, 개발자가 구현을 업데이트하도록 요구하지만 그 대가로 더 명확한 스펙과 더 나은 개발자 경험을 제공해요. 파괴적 변경은 코드 업데이트가 필요하지만 구현이 간단하고 코드 명확성을 개선해요. 버전 관리, 서명, 향상된 확장을 둘러싼 새 역량은 v1.x 계열 내에서 미래 프로토콜 진화를 위한 견고한 기반을 제공해요. 개발자는 단계적 마이그레이션 접근을 계획하고, 치명적 파괴 변경을 우선시하면서 시간이 지나며 새 역량을 점진적으로 채택해야 해요.

더 알아보기 (Learn more)