Skip to content

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.

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.

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:

ToolPurpose
a2a_get_cardReturn 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_requestHandle an incoming A2A JSON-RPC request (the full request object). Dispatches to message/send, tasks/get, tasks/cancel, tasks/list.
a2a_list_tasksList 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.

When acting as a client, Revka discovers a remote card, sends a task, and polls for the result. Three tools cover the outbound surface:

ToolRequired argsKey optional args
a2a_discoverurltimeout (default 30), Cloud Run auth args, auth_token
a2a_send_taskurl, messageskill_id, wait (default false), timeout (default 60), Cloud Run auth args, auth_token
a2a_get_remote_taskurl, task_idCloud 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"
}

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).

OptionTypeMeaning
cloud_run_authstringSet to "gcloud" (also "google" / "auto") to mint a Cloud Run identity token automatically.
cloud_run_identity_tokenstringSupply a pre-minted identity token directly instead of minting one.
cloud_run_audiencestringToken audience. Defaults to the service origin URL.
cloud_run_configstringgcloud configuration name to use when minting via the CLI.
cloud_run_auth_timeoutnumberMax seconds to wait for token minting. Default 20.
auth_tokenstringA2A 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.

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 profile

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" }
Terminal window
gcloud run deploy coder-agent \
--project construct-498201 --region us-central1 \
--source cloud-agents/coder \
--no-allow-unauthenticated \
--service-account [email protected] \
--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
SettingWhy
--no-allow-unauthenticatedPrivate service — callers must present a Cloud Run identity token (cloud_run_auth: gcloud).
--max-instances 1 + --no-cpu-throttlingTasks 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:latestInjects the GitHub token from Secret Manager.
Service accountNeeds 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.

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": "..."
}

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_ID env 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.

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).

Terminal window
gcloud run deploy reviewer-agent \
--project construct-498201 --region us-central1 \
--source cloud-agents/reviewer \
--no-allow-unauthenticated \
--service-account [email protected] \
--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

The 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.

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 + pathPurpose
GET /.well-known/agent-card.jsonAgent card (primary discovery path).
GET /agent-card.jsonAgent card (fallback discovery path).
POST / (also POST /a2a)JSON-RPC 2.0 endpoint.
GET /healthzHealth 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 where type == "text" — the a2a-sdk’s kind keying is not understood. (Inbound message parsing is lenient: the cloud servers accept both type and kind on incoming message parts, but always emit type.)
  • Terminal task states: completed, failed, canceled.
  • message/send returns immediately with a non-terminal state (submittedworking); the task executes asynchronously and the caller polls tasks/get. a2a_send_task with wait: true does 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-error artifact when fields are missing.

The A2A surface is covered by pytest unit tests that run without google-adk (the ADK imports inside the servers are lazy):

Terminal window
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 (submittedcompleted, with output read from type == "text" parts). Any new cloud agent you build for Revka should pass the same matrix.

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.