Skip to main content

Gemini Agent Plugin

The Gemini Agent plugin provides AI-powered coding sessions via the Gemini CLI A2A (Agent-to-Agent) protocol. Each session runs in an isolated container with full developer tooling.

Architecture

How It Works

  1. User creates a session via the launch form (or shareable URL)
  2. Backend deploys a container (Cloud Run or Podman) running the Gemini CLI A2A server
  3. Initial prompt is enqueued as messages in the database
  4. QueueProcessor (background service) polls for pending messages every 3 seconds
  5. When the container is ready, QueueProcessor sends messages via A2A JSON-RPC and stores responses
  6. Frontend polls /chat-history every 2 seconds to display new messages

Backend Modes

The backend supports two container runtime backends via the ContainerBackend interface:

ModeConfigBackendUse Case
Cloud RunlocalMode: false (default)GeminiAgentServiceProduction, shared environments
Local PodmanlocalMode: trueLocalContainerServiceLocal development, debugging

Usage

Creating a Session

Navigate to Gemini Agent in the sidebar and click Launch New Session.

FieldRequiredDescription
RepositoryNoGitHub repo in org/name format. Leave empty for no repo.
BranchYesGit branch to check out (default: main)
Task DescriptionNoPrompt for the agent. Sent as the initial message.
Access GroupNoRestrict session visibility to a team
Interactive ModeNoKeep container alive for follow-up messages (default: on)
Session TTLNoHow long to keep the container after last activity (15m–1d, default: 1h)
Keep LogsNoPrevent automatic cleanup of chat history

When a repository is specified, the backend automatically prepends commands to clone the repo, cd into it, and configure git identity.

Shareable Launch URLs

Sessions can be pre-configured via URL parameters:

/gemini-agent/new?repository=org/repo&branch=main&prompt=Fix+the+bug&auto_launch=true
ParameterDescription
repositoryGitHub repository
branchBranch name
promptTask description
accessGroupAccess group
interactivefalse to disable interactive mode
ttlSession TTL (15m, 30m, 1h, 2h, 4h, 8h, 1d)
keepLogstrue to keep logs indefinitely
GITHUB_TOKENtrue to include GitHub token
GOOGLE_TOKENtrue to include Google token
auto_launchtrue to skip the form and launch immediately

Session Lifecycle

StatusMeaning
creatingContainer is being deployed, A2A server starting
runningContainer healthy, accepting messages
stoppedUser stopped the session (container deleted)
failedContainer failed to start or crashed
deletedCleaned up by backend (displays as "STOPPED" in UI)

Sending Messages

In the console view, type messages in the input field and press Send (or Enter). Messages are:

  1. Enqueued in the database (HTTP 202 response)
  2. Picked up by QueueProcessor within ~3 seconds
  3. Sent to the A2A container via JSON-RPC
  4. Response streamed back and stored in database
  5. Displayed in the chat panel on next poll (~2 seconds)

Messages sent while the agent is processing are queued and processed in order.

Viewing Logs

Click the Logs icon (document icon) in the console header to open the logs drawer. Logs are fetched every 2 seconds. Use the filter input to search log entries.

In local mode, logs come from podman logs. In Cloud Run mode, logs come from Google Cloud Logging.

Credentials

The plugin supports injecting credentials into agent containers:

CredentialSourceEnvironment Variable
GitHub TokenUser OAuth (X-GitHub-Token header)GITHUB_TOKEN
Google TokenUser OAuth (X-Gcp-Access-Token header)GOOGLE_ACCESS_TOKEN
Gemini API KeyBackstage config or VaultGEMINI_API_KEY
TFC TokenVaultTFC_TOKEN
File Search AuthVault override, then user Google OAuth, then backend Gemini API keyFILE_SEARCH_API_KEY or FILE_SEARCH_GOOGLE_ACCESS_TOKEN

The credentials section in the launch form shows availability status. Credentials are checked via the /sessions/credentials endpoint.

File Search Auth Order

The Gemini agent now injects File Search-specific auth independently of the core Gemini model auth. The runtime resolves File Search auth in this order:

  1. Vault override secret (gemini-api-key)
  2. User Google OAuth token
  3. Backend geminiAgent.geminiApiKey

When a repository-backed session is launched, the backend also injects:

  • FILE_SEARCH_STORE_NAME
  • FILE_SEARCH_REPO
  • FILE_SEARCH_SOURCE_REPOSITORY
  • FILE_SEARCH_AUTH_MODE

API Key vs OAuth Mode

The container supports two authentication modes for the Gemini API:

ModeWhen UsedBehavior
OAuthNo GEMINI_API_KEY providedMounts ~/.gemini/ credential files into container
API KeyGEMINI_API_KEY setSkips credential file mounts, patches entrypoint to disable USE_CCPA

Container Image

Registry: ghcr.io/badal-io/gemini-cli/gemini-a2a Source: badal-io/gemini-clideploy/cloudrun/ Architectures: linux/amd64, linux/arm64

Image Contents

ToolVersionPurpose
Node.js22 (bookworm)Runtime for Gemini CLI
Gemini CLImonorepo latestA2A server + CLI
gcloud CLIlatest + GKE auth, Cloud Run proxyGCP operations
TerraformlatestInfrastructure provisioning
VaultlatestSecret management
GitHub CLI (gh)latestRepository operations
Python 3system (bookworm)Scripting
bunlatestFast JS/TS runtime
socatsystemPort forwarding proxy
ai-plugin-translatorlatestGemini extension
file-search-extensionworkspace packageRepository-scoped Gemini File Search retrieval

Entrypoint

The entrypoint.sh script:

  1. Sets environment variables (USE_CCPA, GEMINI_FOLDER_TRUST, GEMINI_YOLO_MODE)
  2. Starts the A2A server via node a2a-server.mjs on port 8081
  3. Waits for the /.well-known/agent-card.json endpoint to respond
  4. Forwards external traffic from 0.0.0.0:8080 to [::1]:8081 via socat (IPv6, since Node.js binds to ::1)

File Search Retrieval

The bundled File Search extension gives the agent a repository-scoped retrieval path for documents uploaded by gemini-packager.

Retrieval behavior is intentionally narrow:

  • search first to identify likely files or concepts
  • fetch targeted file or symbol context second
  • expand to adjacent files only when the first retrieval reports partial context
  • avoid broad fan-out retrieval on every turn

The extension exposes retrieval tools for:

  • searching the configured File Search store
  • fetching focused file context
  • fetching focused symbol context
  • expanding to related files when the current context is incomplete

Configuration

app-config.yaml / app-config.local.yaml

geminiAgent:
localMode: false # true for podman, false for Cloud Run
containerImage: ghcr.io/badal-io/gemini-cli/gemini-a2a:latest
gcpProject: prj-np-devex-backstage-z3brs
gcpRegion: northamerica-northeast1
serviceAccountEmail: gemini-agent@${GCP_PROJECT_ID}.iam.gserviceaccount.com
geminiApiKey: ${GEMINI_API_KEY} # Optional, for API key mode

Local Mode Prerequisites

  • podman installed and podman machine running
  • ~/.gemini/settings.json and ~/.gemini/oauth_creds.json for Gemini auth (auto-mounted into containers)
  • On Apple Silicon: image runs under amd64 emulation via --platform linux/amd64

API Endpoints

EndpointMethodAuthPurpose
/healthGETNoHealth check ({ status, localMode })
/sessionsPOSTYesCreate new session
/sessionsGETYesList sessions for current user
/sessions/credentialsGETYesCheck available credentials
/sessions/:idGETYesGet session status
/sessions/:idDELETEYesStop and delete session
/sessions/:idPATCHYesUpdate session settings
/sessions/:id/chat-historyGETYesGet chat messages + activity events
/sessions/:id/messagePOSTYesEnqueue a message (HTTP 202)
/sessions/:id/logsGETYesBatch logs or SSE stream
/sessions/:id/streamGETYesSSE keepalive (legacy)
/sessions/:id/agent-cardGETYesProxy A2A agent capabilities

A2A Protocol Integration

The backend communicates with the container's A2A server using JSON-RPC v2.0:

{
"jsonrpc": "2.0",
"method": "message/stream",
"id": "msg-<timestamp>",
"params": {
"message": {
"role": "user",
"parts": [{ "kind": "text", "text": "..." }],
"messageId": "<unique-id>"
}
}
}

Message Queue

The QueueProcessor is a background service that manages async message delivery:

  • Poll interval: 3 seconds
  • Max retries: 10 per message
  • Retry cooldown: 10 seconds after failure
  • Readiness check: Probes /.well-known/agent-card.json before sending
  • Atomicity: Uses CAS (compare-and-swap) to prevent duplicate processing

Testing

Unit E2E Tests (Mocked)

Located in plugins/gemini-agent/e2e-tests/gemini-agent.spec.ts. These use Playwright with mocked API responses to test UI components without a running backend.

cd backstage
npx playwright test plugins/gemini-agent/e2e-tests/gemini-agent.spec.ts

Integration E2E Tests (Live)

Located in plugins/gemini-agent/e2e-tests/gemini-agent-e2e.spec.ts. These run against a live Backstage instance with real container orchestration.

# Prerequisites: Backstage frontend + backend running (yarn start)
cd backstage
E2E_TEST=true npx playwright test plugins/gemini-agent/e2e-tests/gemini-agent-e2e.spec.ts

The integration test:

  1. Authenticates via the guest auth provider
  2. Creates a no-repo session with a random math prompt
  3. Waits for the container to start and become RUNNING
  4. Waits for the agent to respond with the correct answer
  5. Cleans up the session

Environment variables:

VariableDefaultDescription
E2E_TEST(unset)Set to true to enable integration tests
BACKEND_URLhttp://localhost:7007Backend API base URL
PLAYWRIGHT_URLhttp://localhost:3001Frontend URL for browser tests

Troubleshooting

Common Errors

ErrorCauseFix
"Error: Failed to fetch" on page loadBackend not runningStart both frontend + backend: yarn start
"Error: [object Object]" on page loadAuth token rejected by backendEnsure guest auth provider is enabled in app-config.local.yaml
PERMISSION_DENIED: run.services.createService account lacks IAM roleGrant roles/run.admin to the service account
UNAUTHENTICATEDNo Application Default CredentialsRun gcloud auth application-default login
"A2A not ready" (debug log)Container still startingNormal — QueueProcessor retries every 3s automatically
"No response body from A2A"Container crashed or network errorCheck container logs via the Logs drawer
Session stuck in CREATINGContainer failed health checkCheck podman/Cloud Run logs for startup errors
Messages stuck in "queued"A2A server not respondingVerify container is running; check network connectivity

Log Messages Reference

Session Creation:

Creating local container gemini-agent-abc123... for user john
Local container gemini-agent-abc123... started, mapped to http://localhost:38920
Enqueued 3 initial message(s) for session abc123-uuid

Message Processing (normal):

Container gemini-agent-abc123... is ready

Message Processing (waiting for container):

QueueProcessor: A2A not ready for session abc123 (probe=503)

This is normal during container startup — the processor retries automatically.

Errors:

QueueProcessor: failed to process message 42: fetch failed
QueueProcessor tick error: ECONNREFUSED

These indicate connectivity problems with the container. Check if the container is still running.

Debugging Local Mode

# Check running containers
podman ps --filter name=gemini-agent

# View container logs
podman logs -f gemini-agent-<session-id-prefix>

# Check container health
curl http://localhost:<mapped-port>/.well-known/agent-card.json

# Restart podman machine (if containers won't start)
podman machine stop && podman machine start

Debugging Cloud Run Mode

# List gemini-agent services
gcloud run services list --filter="metadata.labels.managed-by=backstage" \
--project=<gcp-project> --region=<gcp-region>

# View service logs
gcloud run services logs read gemini-agent-<session-id-prefix> \
--project=<gcp-project> --region=<gcp-region>

# Check service status
gcloud run services describe gemini-agent-<session-id-prefix> \
--project=<gcp-project> --region=<gcp-region>

SSE Streaming Note

Backstage's root HTTP router adds compression() middleware globally, which buffers res.write() chunks. The backend explicitly calls res.flush() after every write in SSE endpoints. If you're extending the plugin with new streaming endpoints, always call (res as any).flush() after each res.write().

Key Files

Backend Plugin (plugins/gemini-agent-backend/src/)

FilePurpose
router.tsExpress router — endpoint handlers, auth middleware
services/types.tsContainerBackend interface, Zod schemas, session types
services/GeminiAgentService.tsCloud Run backend (production)
services/LocalContainerService.tsPodman backend (local dev)
services/QueueProcessor.tsBackground message delivery engine
services/ChatLogStore.tsDatabase operations for sessions, messages, events
services/CredentialService.tsCredential collection from OAuth, Vault, config

Frontend Plugin (plugins/gemini-agent/src/)

FilePurpose
components/GeminiAgentPage/Main page with routing (list, form, console)
components/LaunchForm/Session creation form with credential selection
components/AgentConsole/Console view: chat panel, activity bar, logs
components/SessionList/Session table with status, actions
api/GeminiAgentClient.tsREST API client
api/GeminiAgentApi.tsAPI interface and type definitions

E2E Tests (plugins/gemini-agent/e2e-tests/)

FilePurpose
gemini-agent.spec.tsUnit e2e tests with mocked APIs
gemini-agent-e2e.spec.tsIntegration e2e tests against live instance

Database Schema

Sessions Table (gemini_agent_sessions)

ColumnTypeDescription
idUUIDSession identifier
user_idstringBackstage user entity ref
repositorystringGitHub repository
branchstringGit branch
prompttextFull prompt (with auto-commands)
statusstringcreating, running, stopped, failed, deleted
keep_indefinitelybooleanPrevent automatic cleanup
interactive_modebooleanKeep container alive for follow-ups
interactive_ttl_minutesintTTL after last activity
last_activity_attimestampUpdated on each message

Message Queue (gemini_agent_message_queue)

ColumnTypeDescription
idintAuto-increment ID
session_idUUIDParent session
texttextMessage content
statusstringpending, processing, completed, failed
retry_countintNumber of retry attempts

Chat Messages (gemini_agent_chat_messages)

ColumnTypeDescription
rolestringuser or agent
texttextMessage content
timestampbigintUnix timestamp

Activity Events (gemini_agent_activity_events)

ColumnTypeDescription
kindstringthinking-step, status-change, artifact-update
statestringsubmitted, working, thinking, completed, failed
subjectstringBrief description
descriptiontextDetailed description