A2A 프로토콜 및 클라우드 에이전트
인바운드/아웃바운드 A2A, Cloud Run에서 실행되는 클라우드 Coder 및 Reviewer 에이전트, A2A 호환성 계약에 대해 설명합니다.
Revka는 A2A(Agent-to-Agent) 프로토콜을 양방향으로 지원합니다. 외부 오케스트레이터가 호출하는 A2A 에이전트로 자신을 노출할 수 있으며, 외부 A2A 에이전트를 직접 호출할 수도 있습니다. 여기에는 GitHub 이슈를 수정하고 풀 리퀘스트를 리뷰하기 위해 Revka가 Google Cloud Run에 배포하여 A2A로 연결하는 두 개의 사전 구축된 Google ADK / Gemini 에이전트(Coder와 Reviewer)가 포함됩니다.
이 페이지는 Revka를 다른 A2A 호환 시스템과 통합하거나, 워크플로에서 클라우드 Coder/Reviewer 에이전트를 구동하거나, Revka가 호출할 수 있는 자체 A2A 에이전트를 구축하고자 할 때 참조하세요. A2A 도구는 Operator MCP 도구 인터페이스에 있으며, a2a 스텝 타입을 통해 워크플로에서 원격 에이전트를 직접 호출할 수 있습니다.
A2A 프로토콜 지원
섹션 제목: “A2A 프로토콜 지원”Revka는 A2A 스펙을 따릅니다. /.well-known/agent-card.json에서 에이전트 카드를 제공하며, 기본 URL로 JSON-RPC 2.0 메서드인 message/send, tasks/get, tasks/cancel, tasks/list를 POST 방식으로 처리합니다.
인바운드 — A2A 서버로서의 Revka
섹션 제목: “인바운드 — A2A 서버로서의 Revka”A2A 서버로 동작할 때, 수신된 message/send는 Revka 서브 에이전트를 생성하며, 에이전트의 생명주기는 A2A 태스크 상태에 매핑됩니다. 인바운드 인터페이스는 세 가지 Operator MCP 도구로 제공됩니다.
| 도구 | 설명 |
|---|---|
a2a_get_card | 현재 Revka 인스턴스의 A2A 에이전트 카드를 반환합니다. template_name을 지정하면 특정 템플릿의 카드를, 생략하면 에이전트 템플릿별로 하나의 스킬을 나열하는 복합 카드를 반환합니다. |
a2a_handle_request | 수신된 A2A JSON-RPC 요청(request 객체 전체)을 처리합니다. message/send, tasks/get, tasks/cancel, tasks/list로 디스패치합니다. |
a2a_list_tasks | 외부 에이전트가 생성한 A2A 태스크 목록을 반환합니다. 선택적 context_id 필터와 limit(기본값 50)을 지원합니다. |
Revka의 복합 카드는 스킬(코드 생성, 리뷰, 리서치, 테스트, 아키텍처)을 capabilities: {"streaming": true, "pushNotifications": false}로 광고합니다. message/send 수신 시, 메시지 파트에서 프롬프트를 추출하고 에이전트를 생성(대상 스킬이 에이전트 타입에 매핑)하며, tasks/get으로 폴링할 수 있는 태스크를 반환합니다.
아웃바운드 — 외부 A2A 에이전트 호출
섹션 제목: “아웃바운드 — 외부 A2A 에이전트 호출”클라이언트로 동작할 때, Revka는 원격 카드를 탐색하고 태스크를 전송한 후 결과를 폴링합니다. 아웃바운드 인터페이스는 세 가지 도구로 제공됩니다.
| 도구 | 필수 인자 | 주요 선택 인자 |
|---|---|---|
a2a_discover | url | timeout(기본값 30), Cloud Run 인증 인자, auth_token |
a2a_send_task | url, message | skill_id, wait(기본값 false), timeout(기본값 60), Cloud Run 인증 인자, auth_token |
a2a_get_remote_task | url, task_id | Cloud Run 인증 인자, auth_token |
a2a_discover는 /.well-known/agent-card.json을 먼저 시도한 후 /agent-card.json을 시도하며, 카드를 캐시합니다. name이 없는 카드는 거부됩니다.
a2a_send_task는 message/send를 POST하고 즉시 태스크를 반환합니다. wait: true를 설정하면 태스크가 종료 상태(completed, failed, 또는 canceled)에 도달할 때까지 tasks/get을 폴링한 후 응답 아티팩트에서 텍스트를 추출합니다. 출력 텍스트는 type == "text"인 아티팩트 파트에서만 읽습니다.
// a2a_send_task arguments{ "url": "https://coder-agent-xxxxx-uc.a.run.app", "message": "{\"repo_name\": \"acme/api\", \"issue_number\": 42, \"issue_title\": \"Fix crash\", \"issue_body\": \"...\", \"strategy\": \"guard against None in handler\"}", "wait": true, "cloud_run_auth": "gcloud"}Cloud Run 신원 인증
섹션 제목: “Cloud Run 신원 인증”비공개 Cloud Run 서비스는 인증되지 않은 요청을 거부하므로, 아웃바운드 도구와 a2a 워크플로 스텝은 신원 토큰 옵션을 허용합니다. 발급된 토큰은 X-Serverless-Authorization: Bearer <token>으로 전송됩니다(A2A 애플리케이션 auth_token이 사용하는 Authorization 헤더와는 별도로 유지됩니다).
| 옵션 | 타입 | 설명 |
|---|---|---|
cloud_run_auth | string | "gcloud"(또는 "google" / "auto")로 설정하면 Cloud Run 신원 토큰을 자동으로 발급합니다. |
cloud_run_identity_token | string | 토큰을 직접 발급받아 제공할 때 사용합니다. |
cloud_run_audience | string | 토큰 대상. 기본값은 서비스 오리진 URL입니다. |
cloud_run_config | string | CLI로 토큰을 발급할 때 사용할 gcloud 구성 이름입니다. |
cloud_run_auth_timeout | number | 토큰 발급 최대 대기 시간(초). 기본값 20. |
auth_token | string | A2A 애플리케이션 베어러 토큰. Authorization: Bearer <token>으로 전송됩니다. |
토큰 발급 시 GCP 메타데이터 서버(Cloud Run / GCE에서 사용 가능, gcloud 바이너리 불필요)를 우선 사용하며, 로컬 개발 환경에서는 gcloud CLI(gcloud auth print-identity-token --audiences=<url>)로 폴백합니다. 둘 다 사용할 수 없는 경우 명확한 오류와 함께 호출이 실패합니다.
a2a 워크플로 스텝
섹션 제목: “a2a 워크플로 스텝”워크플로에서 type: a2a 스텝을 사용하면 아웃바운드 클라이언트를 래핑하여 원격 A2A 에이전트를 호출할 수 있습니다.
- id: review type: a2a a2a: url: https://reviewer-agent-xxxxx-uc.a.run.app message: '{"repo_name": "${inputs.repo}", "pr_number": ${steps.open_pr.output.number}}' skill_id: reviewer cloud_run_auth: gcloud # mint identity token for private Cloud Run cloud_run_audience: https://reviewer-agent-xxxxx-uc.a.run.app timeout: 300 auth: "<provider>:<profile_name>" # optional A2A bearer from an auth profile클라우드 Coder 에이전트
섹션 제목: “클라우드 Coder 에이전트”Coder 에이전트는 사전 구축된 Google ADK 에이전트(API 키 없이 Vertex AI와 Application Default Credentials를 통한 Gemini 2.5 Pro)로, 저장소를 클론하고 수정 사항을 구현하며 테스트를 실행한 후 풀 리퀘스트를 엽니다. Cloud Run에서 실행되며 a2a_send_task 또는 a2a 워크플로 스텝을 통해 A2A로 호출됩니다.
에이전트 도구: run_shell, read_file, write_file, github_open_pr, github_merge_pr, github_comment_and_close_issue. GITHUB_TOKEN은 run_shell에서 사용 가능하며(예: git clone https://x-access-token:[email protected]/owner/repo.git), 모델이나 로그에 절대 노출되지 않습니다 — _redact() 처리를 통해 모든 도구 출력에서 제거됩니다.
에이전트는 태스크 JSON에 따라 두 가지 동작 모드를 지원합니다.
{ "repo_name": "owner/repo", "issue_number": 42, "issue_title": "Fix crash on empty input", "issue_body": "...", "strategy": "how to implement the fix"}repo/ 서브디렉터리에 클론하고, fix/issue-<issue_number> 브랜치를 생성한 후 변경 사항을 구현합니다. 테스트 감지 시(pytest.ini, pytest가 포함된 pyproject.toml, 또는 tests/ 디렉터리가 있는 경우) python3 -m pytest -x -q를 실행하고, 커밋, 푸시, PR을 엽니다.
출력 아티팩트(JSON 텍스트):
{ "pr_url": "...", "branch": "fix/issue-42", "summary": "...", "test_status": "passed|failed|skipped" }{ "action": "merge", "repo_name": "owner/repo", "pr_number": 7, "issue_number": 123, "run_id": "..."}PR을 스쿼시 머지하고, 성공 시 연결된 이슈에 댓글을 달고 닫습니다. 이 모드에서는 클론, 편집, PR 생성을 수행하지 않습니다.
출력 아티팩트(JSON 텍스트):
{ "merge_status": "merged|failed", "issue_closed": true, "merged_pr_url": "...", "audit_summary": "..." }Coder 에이전트 배포
섹션 제목: “Coder 에이전트 배포”gcloud run deploy coder-agent \ --project construct-498201 --region us-central1 \ --source cloud-agents/coder \ --no-allow-unauthenticated \ --set-secrets GITHUB_TOKEN=revka-GITHUB_TOKEN:latest \ --set-env-vars GOOGLE_CLOUD_PROJECT=construct-498201,GOOGLE_CLOUD_LOCATION=us-central1,GOOGLE_GENAI_USE_VERTEXAI=True \ --memory 1Gi --timeout 900 --max-instances 1 --no-cpu-throttling| 설정 | 이유 |
|---|---|
--no-allow-unauthenticated | 비공개 서비스 — 호출자는 Cloud Run 신원 토큰(cloud_run_auth: gcloud)을 제시해야 합니다. |
--max-instances 1 + --no-cpu-throttling | 태스크는 인메모리로 실행되며 message/send 반환 후에도 계속 실행되어야 하므로, 인스턴스가 스로틀되거나 태스크 중간에 스케일 아웃되어서는 안 됩니다. |
--set-secrets GITHUB_TOKEN=revka-GITHUB_TOKEN:latest | Secret Manager에서 GitHub 토큰을 주입합니다. |
| 서비스 계정 | Vertex AI를 통해 Gemini를 호출하려면 roles/aiplatform.user가 필요합니다. |
조정 가능한 환경 변수: MODEL_NAME(기본값 gemini-2.5-pro), TASK_TIMEOUT_SECONDS(기본값 1500), MAX_TASKS(기본값 100, 인메모리 태스크 링 버퍼). CI는 .github/workflows/deploy-cloud-agents.yml을 통해 두 에이전트를 모두 배포합니다.
클라우드 Reviewer 에이전트
섹션 제목: “클라우드 Reviewer 에이전트”Reviewer 에이전트는 사전 구축된 Google ADK 에이전트(Vertex AI와 ADC를 통한 Gemini 2.5 Pro)로, GitHub PR diff를 가져와 Vertex AI Search를 통해 저장소 코딩 컨벤션에 기반하여 정확성, 안전성, 테스트 커버리지를 리뷰합니다.
에이전트 도구: github_get_pr, github_get_pr_diff(diff는 60,000자로 잘림), retrieve_conventions. 태스크 입력 및 출력:
// input{ "repo_name": "owner/repo", "pr_number": 57 }
// output artifact (JSON text){ "review_status": "approved" | "needs_changes", "findings": ["..."], "standards_checked": ["Rule 1: monetary values are integer cents", "..."], "summary": "..."}Vertex AI Search를 통한 그라운딩
섹션 제목: “Vertex AI Search를 통한 그라운딩”retrieve_conventions는 저장소의 코딩 컨벤션이 담긴 Discovery Engine(Vertex AI Search) 데이터 스토어를 쿼리한 후, 번들 코퍼스를 추가하여 리뷰 시 항상 특정 번호의 규칙을 인용할 수 있도록 합니다.
- 데이터 스토어:
REVIEWER_DATASTORE_ID환경 변수. 기본값:projects/construct-498201/locations/us/collections/default_collection/dataStores/reviewer-conventions. - 번들 코퍼스:
cloud-agents/reviewer/grounding/CONVENTIONS.md(번호가 매겨진 코딩 규칙 세트), 폴백으로 에이전트에 함께 포함됩니다.
Reviewer 에이전트 배포
섹션 제목: “Reviewer 에이전트 배포”Reviewer는 순수 추론 및 그라운딩 에이전트이므로 두 가지 런타임 중 하나를 선택할 수 있습니다. Cloud Run(A2A 서버, Coder와 동일한 방언)이나 Vertex AI Agent Engine(추론 집약적 워크로드를 위한 관리형 호스팅) 모두 사용 가능합니다.
gcloud run deploy reviewer-agent \ --project construct-498201 --region us-central1 \ --source cloud-agents/reviewer \ --no-allow-unauthenticated \ --set-secrets GITHUB_TOKEN=revka-GITHUB_TOKEN:latest \ --set-env-vars GOOGLE_CLOUD_PROJECT=construct-498201,GOOGLE_CLOUD_LOCATION=us-central1,GOOGLE_GENAI_USE_VERTEXAI=True \ --memory 1Gi --timeout 900 --max-instances 1 --no-cpu-throttlingReviewer는 PR을 읽기만 하므로 공개 저장소의 경우 GitHub 토큰이 불필요합니다 — 인증되지 않은 GitHub REST 요청도 충분합니다. 비공개 저장소에만 Secret Manager 토큰을 연결하세요.
cd cloud-agents/reviewer && python deploy_agent_engine.py이 스크립트는 멱등성을 보장합니다. 동일한 DISPLAY_NAME(기본값 revka-reviewer)의 Agent Engine이 이미 존재하면 업데이트하고, 없으면 새로 생성하며, 호출자가 캡처할 수 있도록 reasoning-engine 리소스 이름을 stdout에 출력합니다. 환경 변수로 구성합니다: PROJECT, LOCATION, STAGING_BUCKET, RUNTIME_SERVICE_ACCOUNT, REVIEWER_DATASTORE_ID, GITHUB_TOKEN_SECRET, DISPLAY_NAME.
비공개 저장소용 GITHUB_TOKEN 시크릿을 연결하려면 ATTACH_GITHUB_SECRET=true를 설정하세요. 이 경우 Agent Engine 런타임 서비스 에이전트가 해당 시크릿에 대한 secretAccessor 권한을 가져야 하며, 그렇지 않으면 인스턴스 준비 상태 확인에 실패합니다.
A2A 서버 호환성 계약
섹션 제목: “A2A 서버 호환성 계약”두 클라우드 에이전트는 모두 Revka의 클라이언트 방언에 맞추기 위해 ADK의 내장 adk api_server --a2a 대신 커스텀 A2A 서버(FastAPI)를 구현합니다. Revka가 호출할 자체 A2A 에이전트를 구축하는 경우 이 계약을 따르세요. 완전한 참조 구현은 cloud-agents/tests/test_a2a_compat.py에 있습니다.
엔드포인트
| 메서드 + 경로 | 설명 |
|---|---|
GET /.well-known/agent-card.json | 에이전트 카드(1차 탐색 경로). |
GET /agent-card.json | 에이전트 카드(폴백 탐색 경로). |
POST / (또는 POST /a2a) | JSON-RPC 2.0 엔드포인트. |
GET /healthz | 헬스 체크. |
JSON-RPC 메서드: message/send, tasks/get, tasks/cancel, tasks/list. JSON-RPC result는 태스크 객체 자체입니다.
에이전트 카드 — a2a_discover가 읽는 형태. 필수 필드: name, description, url, 그리고 각 스킬에 id, name, description, tags가 포함된 비어 있지 않은 skills[] 배열. 추가로 capabilities:
{ "name": "Revka Coder Agent", "description": "ADK/Gemini coding executor: ...", "url": "https://coder-agent-xxxxx-uc.a.run.app", "version": "1.0.0", "capabilities": { "streaming": false, "pushNotifications": false }, "skills": [ { "id": "coder-implement-fix", "name": "Implement GitHub issue fix", "description": "Input JSON: {repo_name, issue_number, ...}. Output JSON: {pr_url, branch, summary, test_status}.", "tags": ["coding", "github", "pull-request", "adk", "gemini", "a2a"] } ]}태스크 객체 — a2a_send_task / a2a_get_remote_task가 읽는 형태:
{ "id": "task-...", "contextId": "ctx-...", "status": { "state": "completed", "timestamp": "2026-06-19T..." }, "artifacts": [ { "name": "coder-result", "parts": [{ "type": "text", "text": "{...result JSON...}" }] } ], "history": []}준수해야 할 계약 규칙:
- 아티팩트 파트는
{"type": "text", "text": ...}를 사용합니다. Revka의 클라이언트는type == "text"인 파트에서만 출력을 추출합니다 — a2a-sdk의kind키 방식은 인식되지 않습니다. (인바운드 메시지 파싱은 유연하게 처리됩니다. 클라우드 서버는 수신 메시지 파트에서type과kind모두를 허용하지만, 출력 시에는 항상type을 사용합니다.) - 종료 태스크 상태:
completed,failed,canceled. message/send는 즉시 반환합니다. 비종료 상태(submitted→working)로 반환하며, 태스크는 비동기로 실행되고 호출자는tasks/get으로 폴링합니다.wait: true를 설정한a2a_send_task가 이 폴링을 대신 수행합니다.- 태스크 입력은 메시지 텍스트에 포함된 JSON입니다. 서버는 순수 JSON 객체 또는 JSON이 포함된 텍스트를 허용하고, 필수 필드를 검증하며, 필드가 누락된 경우
agent-error아티팩트와 함께 태스크를 실패 처리합니다.
클라우드 에이전트 테스트
섹션 제목: “클라우드 에이전트 테스트”A2A 인터페이스는 google-adk 없이 실행되는 pytest 단위 테스트로 커버됩니다(서버 내부의 ADK 임포트는 지연 로딩됩니다).
cd cloud-agents && pytest tests/cloud-agents/tests/test_a2a_compat.py는 coder와 reviewer 서버를 매개변수화하여 전체 호환성 매트릭스를 검증합니다. 에이전트 카드의 필수 필드와 스킬 형태, 태스크 입력 파싱(유효한 JSON, 텍스트에 포함된 JSON, 객체가 아닌 JSON, 필수 필드 누락), 메시지 텍스트 추출(type 및 kind 키 방식 모두), 태스크 상태 머신(submitted → completed, 출력은 type == "text" 파트에서 읽음)을 모두 검증합니다. Revka를 위해 구축하는 새로운 클라우드 에이전트도 동일한 매트릭스를 통과해야 합니다.
Google Agents CLI 통합
섹션 제목: “Google Agents CLI 통합”오퍼레이터는 google_agents_cli MCP 도구를 통해 Google ADK / Agent Engine 생명주기 명령을 직접 실행할 수 있습니다. 이는 ADK 에이전트의 설정, 스캐폴딩, 린트, 실행, 평가, 배포, 게시에 사용되는 동일한 agents-cli입니다. Revka 에이전트가 Google ADK 태스크(예: 클라우드 에이전트 재배포)를 수행할 때 이 방식으로 생명주기 단계를 실행합니다.
// google_agents_cli arguments{ "command": ["deploy", "--no-wait"] }{ "command": ["eval", "run"] }{ "prompt": "research rust error handling" }명령은 argv 토큰 형식으로 전달(셸 문자열이 아님)되므로 셸 인젝션으로부터 안전합니다. 기본값: timeout 600초, max_output_bytes 2MB, allow_interactive: false(인터랙티브 플래그는 차단됨). revka-agentops-a2a 내장 워크플로는 이 도구를 배포 단계에서 사용합니다.