Files
gemini-cli/packages/a2a-server

Gemini CLI A2A Server

Experimental - This package is under active development.

An A2A (Agent-to-Agent) server that wraps the Gemini CLI agent, enabling remote interaction via the A2A protocol. Includes a Google Chat bridge for using the agent directly from Google Chat.

Architecture

Google Chat  ──webhook──>  Chat Bridge  ──A2A──>  A2A Server  ──>  Gemini CLI Agent
                               │
                               └── Chat REST API (push responses back to Chat)

This package contains two independently deployable services:

  1. A2A Server (src/http/server.ts) - Standard A2A protocol endpoint (JSON-RPC + SSE streaming) that wraps the Gemini CLI agent. Heavy workload — deploy with concurrency=1.
  2. Chat Bridge (src/chat-bridge/server.ts) - Lightweight proxy that translates Google Chat webhooks into A2A protocol calls. Connects to the A2A server over HTTP. Deploy with high concurrency (concurrency=80).

The Chat Bridge responds immediately to webhooks with "Processing..." (avoiding Google Chat's 30s timeout), then streams results from the A2A agent and pushes them to Chat via the REST API.

Prerequisites

  • GCP project with the following APIs enabled:
    • Cloud Run API
    • Cloud Build API
    • Artifact Registry API
    • Google Chat API
    • Cloud Storage API (for session persistence)
  • gcloud CLI authenticated with your project
  • Node.js 20+ for local development
  • Gemini API key from Google AI Studio

Environment Variables

A2A Server

Variable Required Description
GEMINI_API_KEY Yes Gemini API key for the agent
CODER_AGENT_PORT No Server port (default: 8080)
CODER_AGENT_HOST No Bind host (default: localhost, set 0.0.0.0 for containers)
CODER_AGENT_WORKSPACE_PATH No Agent workspace directory (default: /workspace)
GCS_BUCKET_NAME No GCS bucket for task persistence
GEMINI_YOLO_MODE No Set true to auto-approve all tool calls
GIT_TERMINAL_PROMPT No Set 0 to prevent git credential prompts in headless env

Chat Bridge

Variable Required Description
A2A_SERVER_URL Yes URL of the A2A agent server (e.g. http://localhost:8080)
PORT No Server port (default: 8080)
CHAT_PROJECT_NUMBER No Google Chat project number for JWT verification
CHAT_SA_KEY_PATH No Path to service account key for Chat API (uses ADC if not set)
GCS_BUCKET_NAME No GCS bucket for session persistence
CHAT_BRIDGE_DEBUG No Set true for verbose bridge logging

Local Development

Build

From the repo root:

npm install
npm run build

Run the A2A Server

export GEMINI_API_KEY="your-api-key"
export CODER_AGENT_PORT=8080

node packages/a2a-server/dist/src/http/server.js

Run the Chat Bridge (separate terminal)

export A2A_SERVER_URL=http://localhost:8080
export PORT=8090

node packages/a2a-server/dist/src/chat-bridge/server.js

Test the A2A endpoint

# Check the agent card
curl http://localhost:8080/.well-known/agent-card.json | jq .

# Send a message (JSON-RPC)
curl -X POST http://localhost:8080 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "message/send",
    "params": {
      "message": {
        "kind": "message",
        "role": "user",
        "messageId": "test-1",
        "parts": [{"kind": "text", "text": "Hello, what can you do?"}]
      },
      "configuration": {"blocking": true}
    }
  }'

Test the Chat Bridge

# Health check
curl http://localhost:8080/chat/health | jq .

# Simulate a Google Chat MESSAGE event
curl -X POST http://localhost:8080/chat/webhook \
  -H "Content-Type: application/json" \
  -d '{
    "type": "MESSAGE",
    "eventTime": "2026-01-01T00:00:00Z",
    "message": {
      "name": "spaces/test/messages/1",
      "text": "Hello agent",
      "thread": {"name": "spaces/test/threads/abc"},
      "sender": {"name": "users/1", "displayName": "Test User"},
      "space": {"name": "spaces/test", "type": "DM"}
    },
    "space": {"name": "spaces/test", "type": "DM"},
    "user": {"name": "users/1", "displayName": "Test User"}
  }'

Cloud Run Deployment

1. Create Artifact Registry repository

export PROJECT_ID=your-project-id
export REGION=us-central1

gcloud artifacts repositories create gemini-a2a \
  --repository-format=docker \
  --location=$REGION \
  --project=$PROJECT_ID

2. Create GCS bucket (optional, for session persistence)

gsutil mb -l $REGION gs://gemini-a2a-sessions-$PROJECT_ID

3. Build both images

# Build A2A agent server
gcloud builds submit \
  --config=packages/a2a-server/cloudbuild.yaml \
  --project=$PROJECT_ID

# Build Chat bridge
gcloud builds submit \
  --config=packages/a2a-server/cloudbuild-chat-bridge.yaml \
  --project=$PROJECT_ID

4. Deploy A2A agent server

export AGENT_IMAGE=us-central1-docker.pkg.dev/$PROJECT_ID/gemini-a2a/a2a-server:latest

gcloud run deploy gemini-a2a-server \
  --image=$AGENT_IMAGE \
  --region=$REGION \
  --project=$PROJECT_ID \
  --platform=managed \
  --allow-unauthenticated \
  --memory=2Gi \
  --cpu=2 \
  --timeout=300 \
  --concurrency=1 \
  --max-instances=1 \
  --set-env-vars="GEMINI_YOLO_MODE=true,GCS_BUCKET_NAME=gemini-a2a-sessions-$PROJECT_ID" \
  --set-secrets="GEMINI_API_KEY=gemini-api-key:latest"

5. Deploy Chat bridge

Get the A2A server URL first:

export A2A_URL=$(gcloud run services describe gemini-a2a-server \
  --region=$REGION --project=$PROJECT_ID --format='value(status.url)')

export BRIDGE_IMAGE=us-central1-docker.pkg.dev/$PROJECT_ID/gemini-a2a/chat-bridge:latest

gcloud run deploy gemini-chat-bridge \
  --image=$BRIDGE_IMAGE \
  --region=$REGION \
  --project=$PROJECT_ID \
  --platform=managed \
  --allow-unauthenticated \
  --memory=512Mi \
  --cpu=1 \
  --timeout=60 \
  --concurrency=80 \
  --max-instances=1 \
  --set-env-vars="A2A_SERVER_URL=$A2A_URL,GCS_BUCKET_NAME=gemini-a2a-sessions-$PROJECT_ID"

Important

: After initial deployment, always use --update-env-vars instead of --set-env-vars to avoid wiping existing environment variables.

6. Update an existing deployment

# Update env vars without replacing existing ones
gcloud run services update gemini-a2a-server \
  --region=$REGION \
  --project=$PROJECT_ID \
  --update-env-vars="NEW_VAR=value"

# Deploy a new image
gcloud run services update gemini-a2a-server \
  --region=$REGION \
  --project=$PROJECT_ID \
  --image=$IMAGE

Google Chat App Configuration

1. Create a service account for Chat API

The Chat bridge needs a service account with the Chat API scope to push messages proactively.

# Create service account
gcloud iam service-accounts create gemini-chat-bot \
  --display-name="Gemini Chat Bot" \
  --project=$PROJECT_ID

# Download key (for local dev)
gcloud iam service-accounts keys create chat-sa-key.json \
  --iam-account=gemini-chat-bot@$PROJECT_ID.iam.gserviceaccount.com

On Cloud Run, use Application Default Credentials (ADC) instead of a key file. Grant the Cloud Run service account the chat.bot scope by configuring it as the Chat app's service account.

2. Configure the Google Chat app

  1. Go to Google Cloud Console > APIs & Services > Google Chat API > Configuration
  2. Set App name and Description
  3. Under Connection settings, select HTTP endpoint URL
  4. Set the URL to your Chat bridge Cloud Run service URL + /chat/webhook:
    https://gemini-chat-bridge-HASH-uc.a.run.app/chat/webhook
    
  5. Under Authentication Audience, select HTTP endpoint URL
  6. Under Visibility, choose who can use the app
  7. Under Permissions, configure who can install it
  8. Click Save

3. Grant Cloud Run invoker permission

If your Cloud Run service requires authentication (recommended):

# Get the Chat service account
# It's usually chat@system.gserviceaccount.com

gcloud run services add-iam-policy-binding gemini-chat-bridge \
  --region=$REGION \
  --project=$PROJECT_ID \
  --member="serviceAccount:chat@system.gserviceaccount.com" \
  --role="roles/run.invoker"

Chat Bridge Commands

When messaging the bot in Google Chat:

Command Description
/reset or reset Clear the current session and start fresh
/yolo Enable YOLO mode - auto-approve all tool calls
/safe Disable YOLO mode - require approval for tool calls
approve / yes / y Approve a pending tool call
reject / no / n Reject a pending tool call
always allow Approve and always allow this tool

Troubleshooting

"Gemini CLI Agent is not responding" in Google Chat

This usually means the bridge couldn't return "Processing..." within Google Chat's 30-second timeout. Check Cloud Run logs for both services:

# Chat bridge logs
gcloud run services logs read gemini-chat-bridge \
  --region=$REGION --project=$PROJECT_ID --limit=50

# A2A agent logs
gcloud run services logs read gemini-a2a-server \
  --region=$REGION --project=$PROJECT_ID --limit=50

Tool approvals appearing in YOLO mode

Ensure GEMINI_YOLO_MODE=true is set. If you used --set-env-vars during a deployment, it may have wiped this variable. Use --update-env-vars instead.

Agent hangs on git operations

The GIT_TERMINAL_PROMPT=0 env var (set in the Dockerfile) prevents git from prompting for credentials. If git operations require authentication, configure a credential helper or use gh auth with a token.

Session state lost after restart

Enable GCS persistence by setting GCS_BUCKET_NAME. Sessions are automatically flushed to GCS every 30 seconds and restored on startup.

Chat responses appear as top-level messages instead of thread replies

The Chat bridge includes thread.name in all responses. If replies still appear at the top level, ensure the webhook event includes thread information. DM conversations always thread correctly; spaces may need threading enabled.

Known Limitations

  • Google Chat 4096 character limit: Long agent responses are automatically split into multiple messages at paragraph/line boundaries.
  • Single bridge instance: The bridge uses max-instances=1 so that the in-memory async processing guard works correctly. This means no redundancy during deploys (brief downtime during revision rollover).
  • Tool confirmation in streaming mode: When the A2A server has GEMINI_YOLO_MODE=false, tool confirmations via streaming may not return text due to an SDK-level issue (executor aborts on SSE disconnect). Server YOLO mode works correctly.