A2A protocol & cloud agents
Inbound/outbound A2A, the Cloud Coder and Reviewer agents on Cloud Run, and the A2A compatibility contract.
Revka speaks the A2A (Agent-to-Agent) protocol in both directions. It can expose itself as an A2A agent that external orchestrators call, and it can call out to external A2A agents — including two prebuilt Google ADK / Gemini agents (a Coder and a Reviewer) that Revka deploys to Google Cloud Run and reaches over A2A to fix GitHub issues and review pull requests.
Use this page when you want to integrate Revka with another A2A-compatible system, drive the cloud Coder/Reviewer agents from a workflow, or build your own A2A agent that Revka can call. The A2A tools live in the Operator MCP tool surface; the a2a step type lets workflows call remote agents directly.
A2A protocol support
Section titled “A2A protocol support”Revka follows the A2A spec: an agent card served at /.well-known/agent-card.json, and JSON-RPC 2.0 methods message/send, tasks/get, tasks/cancel, and tasks/list posted to the base URL.
Inbound — Revka as an A2A server
Section titled “Inbound — Revka as an A2A server”When acting as an A2A server, an incoming message/send spawns a Revka sub-agent; the agent’s lifecycle maps onto A2A task states. Three Operator MCP tools cover the inbound surface:
| Tool | Purpose |
|---|---|
a2a_get_card | Return the A2A agent card for this Revka instance. Pass template_name for a single template’s card, or omit it for a composite card listing one skill per agent template. |
a2a_handle_request | Handle an incoming A2A JSON-RPC request (the full request object). Dispatches to message/send, tasks/get, tasks/cancel, tasks/list. |
a2a_list_tasks | List A2A tasks created by external agents. Optional context_id filter and limit (default 50). |
Revka’s composite card advertises its skills (code generation, review, research, testing, architecture) with capabilities: {"streaming": true, "pushNotifications": false}. On message/send, Revka extracts the prompt from the message parts, spawns an agent (the target skill maps to an agent type), and returns a task you can poll with tasks/get.
Outbound — calling external A2A agents
Section titled “Outbound — calling external A2A agents”When acting as a client, Revka discovers a remote card, sends a task, and polls for the result. Three tools cover the outbound surface:
| Tool | Required args | Key optional args |
|---|---|---|
a2a_discover | url | timeout (default 30), Cloud Run auth args, auth_token |
a2a_send_task | url, message | skill_id, wait (default false), timeout (default 60), Cloud Run auth args, auth_token |
a2a_get_remote_task | url, task_id | Cloud Run auth args, auth_token |
a2a_discover tries /.well-known/agent-card.json first, then /agent-card.json, and caches the card. A card with no name is rejected.
a2a_send_task POSTs message/send and returns immediately with the task. When wait: true, it polls tasks/get until the task reaches a terminal state (completed, failed, or canceled), then extracts text from the response artifacts. Output text is read only from artifact parts where 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 identity auth
Section titled “Cloud Run identity auth”Private Cloud Run services reject unauthenticated requests, so the outbound tools and the a2a workflow step accept identity-token options. The minted token is sent as X-Serverless-Authorization: Bearer <token> (kept separate from any A2A application auth_token, which uses the Authorization header).
| Option | Type | Meaning |
|---|---|---|
cloud_run_auth | string | Set to "gcloud" (also "google" / "auto") to mint a Cloud Run identity token automatically. |
cloud_run_identity_token | string | Supply a pre-minted identity token directly instead of minting one. |
cloud_run_audience | string | Token audience. Defaults to the service origin URL. |
cloud_run_config | string | gcloud configuration name to use when minting via the CLI. |
cloud_run_auth_timeout | number | Max seconds to wait for token minting. Default 20. |
auth_token | string | A2A application bearer token, sent as Authorization: Bearer <token>. |
Token minting prefers the GCP metadata server (available on Cloud Run / GCE, no gcloud binary required) and falls back to the gcloud CLI (gcloud auth print-identity-token --audiences=<url>) for local development. If neither is available, the call fails with a clear error.
The a2a workflow step
Section titled “The a2a workflow step”Workflows can call a remote A2A agent with a type: a2a step, which wraps the outbound client:
- 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 profileCloud Coder agent
Section titled “Cloud Coder agent”The Coder agent is a prebuilt Google ADK agent (Gemini 2.5 Pro via Vertex AI with Application Default Credentials — no API keys) that clones a repository, implements a fix, runs tests, and opens a pull request. It runs on Cloud Run and is called over A2A by a2a_send_task or the a2a workflow step.
Agent tools: run_shell, read_file, write_file, github_open_pr, github_merge_pr, github_comment_and_close_issue. The GITHUB_TOKEN is available to run_shell (for git clone https://x-access-token:[email protected]/owner/repo.git) and is never echoed to the model or logs — a _redact() pass strips it from all tool output.
The agent has two operation modes, selected by the task JSON:
{ "repo_name": "owner/repo", "issue_number": 42, "issue_title": "Fix crash on empty input", "issue_body": "...", "strategy": "how to implement the fix"}It clones into a repo/ subdirectory, creates a branch fix/issue-<issue_number>, implements the change, runs python3 -m pytest -x -q when tests are detectable (a pytest.ini, a pyproject.toml with pytest, or a tests/ dir), commits, pushes, and opens a PR.
Output artifact (JSON text):
{ "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": "..."}It squash-merges the PR and, on success, comments on and closes the linked issue. It does not clone, edit, or open a PR in this mode.
Output artifact (JSON text):
{ "merge_status": "merged|failed", "issue_closed": true, "merged_pr_url": "...", "audit_summary": "..." }Deploy the Coder agent
Section titled “Deploy the Coder agent”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| Setting | Why |
|---|---|
--no-allow-unauthenticated | Private service — callers must present a Cloud Run identity token (cloud_run_auth: gcloud). |
--max-instances 1 + --no-cpu-throttling | Tasks run in-memory and must keep executing after message/send returns, so the instance can’t be throttled or scaled mid-task. |
--set-secrets GITHUB_TOKEN=revka-GITHUB_TOKEN:latest | Injects the GitHub token from Secret Manager. |
| Service account | Needs roles/aiplatform.user to call Gemini via Vertex AI. |
Tunable env vars: MODEL_NAME (default gemini-2.5-pro), TASK_TIMEOUT_SECONDS (default 1500), MAX_TASKS (default 100, the in-memory task ring buffer). CI deploys both agents via .github/workflows/deploy-cloud-agents.yml.
Cloud Reviewer agent
Section titled “Cloud Reviewer agent”The Reviewer agent is a prebuilt Google ADK agent (Gemini 2.5 Pro via Vertex AI, ADC) that fetches a GitHub PR diff and reviews it for correctness, safety, and test coverage, grounded in the repository’s coding conventions via Vertex AI Search.
Agent tools: github_get_pr, github_get_pr_diff (diff truncated at 60,000 chars), and retrieve_conventions. Task input and output:
// 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": "..."}Grounding via Vertex AI Search
Section titled “Grounding via Vertex AI Search”retrieve_conventions queries a Discovery Engine (Vertex AI Search) data store holding the repo’s coding conventions, then layers in a bundled corpus so the review can always cite specific numbered rules:
- Data store:
REVIEWER_DATASTORE_IDenv var. Default:projects/construct-498201/locations/us/collections/default_collection/dataStores/reviewer-conventions. - Bundled corpus:
cloud-agents/reviewer/grounding/CONVENTIONS.md(a numbered set of coding rules), shipped with the agent as a fallback.
Deploy the Reviewer agent
Section titled “Deploy the Reviewer agent”The Reviewer is a pure reasoning + grounding agent, so it can run on either runtime — Cloud Run (A2A server, same dialect as the Coder) or Vertex AI Agent Engine (managed hosting for reasoning-heavy workloads).
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-throttlingThe Reviewer only reads PRs, so against a public repo it needs no GitHub token — unauthenticated GitHub REST is fine. Attach the Secret Manager token only for private repos.
cd cloud-agents/reviewer && python deploy_agent_engine.pyThis is idempotent: it updates the existing Agent Engine with the same DISPLAY_NAME (default revka-reviewer) if one exists, otherwise creates it, and prints the reasoning-engine resource name on stdout for callers to capture. Configure via env: PROJECT, LOCATION, STAGING_BUCKET, RUNTIME_SERVICE_ACCOUNT, REVIEWER_DATASTORE_ID, GITHUB_TOKEN_SECRET, DISPLAY_NAME.
Set ATTACH_GITHUB_SECRET=true to attach the GITHUB_TOKEN secret for private repos — then the Agent Engine runtime service agent must hold secretAccessor on the secret, or instances fail readiness.
A2A server compatibility contract
Section titled “A2A server compatibility contract”Both cloud agents implement a custom A2A server (FastAPI) rather than ADK’s built-in adk api_server --a2a, specifically to match Revka’s client dialect. If you build your own A2A agent for Revka to call, match this contract — the exhaustive reference is cloud-agents/tests/test_a2a_compat.py.
Endpoints
| Method + path | Purpose |
|---|---|
GET /.well-known/agent-card.json | Agent card (primary discovery path). |
GET /agent-card.json | Agent card (fallback discovery path). |
POST / (also POST /a2a) | JSON-RPC 2.0 endpoint. |
GET /healthz | Health check. |
JSON-RPC methods: message/send, tasks/get, tasks/cancel, tasks/list. The JSON-RPC result is the Task object itself.
Agent card — the shape a2a_discover reads. Required: name, description, url, and a non-empty skills[] array where each skill has id, name, description, and tags. Plus 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"] } ]}Task object — the shape a2a_send_task / a2a_get_remote_task read:
{ "id": "task-...", "contextId": "ctx-...", "status": { "state": "completed", "timestamp": "2026-06-19T..." }, "artifacts": [ { "name": "coder-result", "parts": [{ "type": "text", "text": "{...result JSON...}" }] } ], "history": []}Contract rules to honor:
- Artifact parts use
{"type": "text", "text": ...}. Revka’s client extracts output only from parts wheretype == "text"— the a2a-sdk’skindkeying is not understood. (Inbound message parsing is lenient: the cloud servers accept bothtypeandkindon incoming message parts, but always emittype.) - Terminal task states:
completed,failed,canceled. message/sendreturns immediately with a non-terminal state (submitted→working); the task executes asynchronously and the caller pollstasks/get.a2a_send_taskwithwait: truedoes this polling for you.- Task input is JSON embedded in the message text. The server accepts a bare JSON object or text containing one, validates required fields, and fails the task with an
agent-errorartifact when fields are missing.
Cloud agent tests
Section titled “Cloud agent tests”The A2A surface is covered by pytest unit tests that run without google-adk (the ADK imports inside the servers are lazy):
cd cloud-agents && pytest tests/cloud-agents/tests/test_a2a_compat.py parametrizes both the coder and reviewer servers and asserts the full compatibility matrix: agent-card required fields and skill shape, task-input parsing (valid JSON, JSON embedded in prose, non-object JSON, missing required fields), message-text extraction (both type- and kind-keyed parts), and the task state machine (submitted → completed, with output read from type == "text" parts). Any new cloud agent you build for Revka should pass the same matrix.
Google Agents CLI integration
Section titled “Google Agents CLI integration”The operator can drive Google ADK / Agent Engine lifecycle commands directly via the google_agents_cli MCP tool — the same agents-cli used to set up, scaffold, lint, run, eval, deploy, and publish ADK agents. This is how a Revka agent assigned a Google ADK task (for example, redeploying the cloud agents) performs the lifecycle steps.
// google_agents_cli arguments{ "command": ["deploy", "--no-wait"] }{ "command": ["eval", "run"] }{ "prompt": "research rust error handling" }Commands are passed as argv tokens (not a shell string), so they are safe from shell injection. Defaults: timeout 600s, max_output_bytes 2 MB, and allow_interactive: false (interactive flags are blocked). The revka-agentops-a2a builtin workflow uses this tool for its deploy steps.