Back to Skills
google/agents-cliCheck passed

SKILL DETAIL

google-agents-cli-deploy

google/agents-cli/google-agents-cli-deploy

This skill should be used when the user wants to deploy an agent, set up CI/CD, configure secrets, or troubleshoot a deployment, and needs guidance on Agent Runtime, Cloud Run, or GKE deployment targets, or binding an agent to an Agent Gateway. It covers deployment workflows, service accounts, rollback, and production infrastructure, and applies to any framework agents-cli deploys (ADK, LangChain, and others). Part of the agents-cli skills suite, it is not intended for agent API code patterns (for ADK use google-agents-cli-adk-code), evaluation (use google-agents-cli-eval), or project scaffolding (use google-agents-cli-scaffold).

Installs · 1,696View source

Installation

npx skills add https://github.com/google/agents-cli --skill google-agents-cli-deploy

Skill files

SKILL.md

Last synced · Oct 22, 2026

references/agent-runtime.md›
# Agent Runtime infrastructure

> **Assumes `/google-agents-cli-scaffold` scaffolding.** If your project isn't scaffolded yet, see `/google-agents-cli-scaffold` first.

## Deployment Architecture

Agent Runtime in Gemini platform uses **container-based deployment**: `agents-cli deploy` packages your project and Agent Runtime builds a container image from your project's `Dockerfile` (required at the project root; scaffolded projects ship one).

File selection honors the project-root `.gcloudignore`, else the project-root `.gitignore` (nested `.gitignore` files are not consulted).

Each template builds the served application its own way:

- **Python** — the container runs `uvicorn app.fast_api_app:app`, the same entrypoint as Cloud
  Run and GKE. Which routes that app exposes depends on the framework; check
  `app/fast_api_app.py`. There is no top-level `AgentEngineApp`/`AdkApp` deployment
  entrypoint — the container serves HTTP directly.
- **Go** — the container runs the binary compiled from `main.go`, which composes ADK Go's
  launcher sub-commands (`web`, `api`, `a2a`, `appinfo`). Agent Runtime is supported natively,
  so there is no adapter layer.

`agents-cli deploy` labels the deployment with the `framework` recorded in
`agents-cli-manifest.yaml`, and `--framework` overrides it. Terraform has its own copy, the
`agent_framework` default in `deployment/terraform/*/variables.tf`, so editing the manifest
alone leaves the tfvar stale and the next `terraform apply` puts the old label back. Edit
both. The label picks the console playground and decides whether ADK class methods are
declared; it does not constrain the container.

> **ADK Python projects.** `fast_api_app.py` builds the FastAPI `app` via
> `get_fast_api_app(web=True, lifespan=...)`. The lifespan builds one `Runner`
> from the shared session/artifact services (`app_utils/services.py`) and mounts
> A2A routes (`attach_a2a_routes`); `attach_reasoning_engine_routes(app)` adds
> the reasoning_engine contract routes (the adapter constructs an `AdkApp`
> internally to dispatch the native `:streamQuery`/`:query` contract). So the
> container serves the ADK HTTP surface (`/run_sse`, `/apps/...`), the A2A routes
> under `/a2a/{app_name}` (JSON-RPC + agent card), and the reasoning_engine
> adapter routes `/api/reasoning_engine` + `/api/stream_reasoning_engine` (used
> by the console playground and ADK registration with Gemini).

> **ADK Go projects.** `main.go` composes launcher sub-commands instead of building a web
> app. The `agentengine` sub-launcher (`google.golang.org/adk/v2/cmd/launcher/web/agentengine`,
> constructed as `agentengine.NewLauncher(rootAgent.Name())`) is the counterpart
> to Python's reasoning_engine adapter — it is what makes the console playground
> work. The container serves the ADK HTTP surface at the root (hence
> `-path_prefix /`), the agent card at `/.well-known/agent-card.json` and A2A
> JSON-RPC at `/a2a/v1/invoke`. There is no `webui` sub-launcher on this target.

### The `/api` HTTP passthrough

Agent Runtime exposes the container's HTTP routes externally under an
`/api` prefix, so deployed agents are reachable without a public Cloud Run URL:

```
https://{location}-aiplatform.googleapis.com/reasoningEngines/v1/{resource}/api/{container_path}
```

where `{resource}` is the full `projects/.../reasoningEngines/...` name. For
example, the A2A agent card (container route `/a2a/{agent_directory}/.well-known/agent-card.json`)
is reachable at:

```
https://{location}-aiplatform.googleapis.com/reasoningEngines/v1/{resource}/api/a2a/{agent_directory}/.well-known/agent-card.json
```

`{agent_directory}` is the app name (the project's `agent_directory`, recorded in
`deployment_metadata.json`). This is the exact URL `deploy` advertises on
success and `run` constructs for `--mode a2a` against an Agent Runtime URL — both
authenticate with your Google credentials. On Agent Runtime, `publish` defaults
to **ADK** registration (`:streamQuery` against the reasoning-engine resource
name) rather than this card URL; pass `--registration-type a2a` if your container
serves only A2A.

## Deploying

Deploy with `agents-cli deploy` (run `agents-cli deploy --help` for the full flag reference). CI/CD pipelines invoke the same command.

**Deployment flow:**
1. `agents-cli deploy` packages the project files (honoring `.gcloudignore`/`.gitignore`)
2. Agent Runtime builds the container image and creates/updates the Agent Runtime instance
3. Writes `deployment_metadata.json` with the engine resource ID

## Terraform Resource

Agent Runtime uses `google_vertex_ai_reasoning_engine` in `deployment/terraform/single-project/service.tf` (and the `cicd/service.tf` variant for CI/CD-managed deployments). Check those files for current scaling, concurrency, and resource limit settings.

Key difference from Cloud Run: the `lifecycle.ignore_changes` (covering `container_spec`, `source_code_spec`, and `deployment_spec`) is critical — the image and source are updated by `agents-cli deploy` / CI/CD, not Terraform.

## deployment_metadata.json

Written by `agents-cli deploy` after a successful deployment:

```json
{
  "remote_agent_runtime_id": "projects/PROJECT/locations/LOCATION/reasoningEngines/ENGINE_ID",
  "deployment_target": "agent_runtime",
  "is_a2a": true,
  "agent_directory": "app",
  "deployment_timestamp": "2025-02-25T10:30:00.000+00:00"
}
```

Used by: subsequent deploys (update vs create), `agents-cli run --url`, and `agents-cli publish` (reads the runtime ID for the default ADK registration on Agent Runtime, and constructs the A2A card URL only when A2A registration is explicitly chosen). Cloud Run does not use this file.

If deployment times out but the engine was created, manually populate this file with the engine resource ID.

## CI/CD Differences from Cloud Run

| Aspect | Agent Runtime | Cloud Run |
|--------|-------------|-----------|
| **Build** | Dockerfile → image (built by Agent Runtime) | Dockerfile → image (`gcloud builds`) |
| **Deploy command** | `agents-cli deploy` | `gcloud run deploy --image ...` |
| **Artifact** | Container image | Container image in Artifact Registry |
| **Language toolchain version** | Configurable in Dockerfile | Configurable in Dockerfile |
| **Load testing** | Scaffolded load test against the Agent Runtime endpoint | Scaffolded load test, direct HTTP to the Cloud Run URL |

## Playground & Remote Testing

```bash
# Local mode (uses local agent instance)
agents-cli playground

# Query your deployed Agent Runtime remotely (ADK projects; use --mode a2a otherwise)
agents-cli run --url https://LOCATION-aiplatform.googleapis.com/v1/projects/PROJECT/locations/LOCATION/reasoningEngines/ID --mode adk "Hello, what can you do?"
```

`--mode` is required with `--url`: use `adk` for the ADK streaming API (`:streamQuery`) or `a2a` for the A2A protocol. Add `-v` for full JSON event payloads. Auth is auto-detected via Google Cloud credentials.

To query Agent Runtime programmatically:

```python
import agentplatform

client = agentplatform.Client(location="us-east1")
agent = client.runtimes.get(name="projects/PROJECT/locations/LOCATION/reasoningEngines/ENGINE_ID")

async for event in agent.async_stream_query(message="Hello!", user_id="test"):
    print(event)
```

## Session & Artifact Services

> **ADK projects.** The two paragraphs below are the ADK scaffold's session and
> artifact wiring. The environment-variable sources at the end of this section
> apply to any framework.

Agent Runtime always uses in-memory sessions at scaffold time; at runtime `app_utils/services.py` upgrades to `VertexAiSessionService` when Agent Runtime injects `GOOGLE_CLOUD_AGENT_ENGINE_ID`. `get_fast_api_app` receives the `shared://session` URI, resolved by `services.py`.

Artifacts use `GcsArtifactService` when `LOGS_BUCKET_NAME` is set, otherwise `InMemoryArtifactService`.

Environment variables set during deployment come from `agents-cli deploy` (the CLI's `deploy/agent_runtime.py`) for SDK deploys, and from `deployment/terraform/single-project/service.tf` (or the `cicd/` variant) for Terraform-managed deploys. Check those for current values.

### Memory Bank

To enable cross-session memory on Agent Runtime, configure `memory_bank_config` via `context_spec`. See the ADK [`cross-session-memory` recipe](https://github.com/google/adk-samples/tree/main/core/python/cross-session-memory) for the full pattern.

## Networking (PSC Interface)

Agent Runtime cannot reach your VPC by default. To enable private connectivity, create a [network attachment](https://cloud.google.com/vpc/docs/create-manage-network-attachments) and deploy with `--network-attachment`. Add `--dns-peering-domain`, `--dns-peering-project`, and `--dns-peering-network` if you need private DNS resolution. PSC config is immutable after deployment — delete and redeploy to change it. See the [GCP docs](https://cloud.google.com/gemini-enterprise-agent-platform/scale/runtime/private-service-connect-interface) for prerequisites and setup.
references/batch-inference.md›
# Batch Inference (Cloud Run)

Invoke your agent as a BigQuery Remote Function for batch inference over table rows. This requires a custom `POST /` endpoint since BQ cannot use URL paths.

> **ADK projects.** The BigQuery request/response contract and the Terraform below apply to any framework; both handlers invoke the agent through the ADK `Runner`, so swap in your framework's invocation. For the `Runner` API, see `/google-agents-cli-adk-code`.

> For event-driven triggers (Pub/Sub, Eventarc): ADK Python has native `trigger_sources`. ADK Go has `pubsub` and `eventarc` sub-launchers, which the scaffolded entrypoint does not start. See: `/google-agents-cli-adk-code`

## BigQuery Remote Function

### Python handler

BQ sends `{"calls": [["row1"], ...], "caller": "..."}`, expects `{"replies": ["...", ...]}` in same order. BQ **cannot use URL paths** — register at `POST /`.

```python
import asyncio, json, uuid
from fastapi import Request
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
from my_agent.agent import root_agent

APP_NAME = "my_agent"
_trigger_session_service = InMemorySessionService()
_trigger_runner = Runner(
    agent=root_agent, app_name=APP_NAME, session_service=_trigger_session_service,
)

async def _run_agent(message_text: str, user_id: str = "trigger") -> list:
    session = await _trigger_session_service.create_session(
        app_name=APP_NAME, user_id=user_id, session_id=str(uuid.uuid4())
    )
    events = []
    async for event in _trigger_runner.run_async(
        user_id=user_id, session_id=session.id,
        new_message=types.Content(role="user", parts=[types.Part(text=message_text)]),
    ):
        events.append(event)
    return events

@app.post("/")
async def trigger_bq(request: Request):
    body = await request.json()
    calls: list = body.get("calls", [])
    user_id = body.get("caller") or body.get("sessionUser") or "bq"

    async def _process_row(row_args: list) -> str:
        text = row_args[0] if (len(row_args) == 1 and isinstance(row_args[0], str)) \
               else json.dumps(row_args)
        try:
            events = await _run_agent(text, user_id=user_id)
            return json.dumps([e.model_dump(mode="json") for e in events])
        except Exception as e:
            return f"Error: {e}"

    replies = await asyncio.gather(*[_process_row(row) for row in calls])
    return {"replies": list(replies)}
```

### Go handler

A Go project has no FastAPI app to hang a route on. Serve the BigQuery endpoint from your own mux
with `adkrest.Server` mounted beside it — the composition upstream's
[`examples/rest`](https://github.com/google/adk-go/tree/main/examples/rest) uses, and the one
`/google-agents-cli-adk-code` (`references/adk-go.md`) covers in full.

```go
restServer, _ := adkrest.NewServer(adkrest.ServerConfig{
	AgentLoader:     agent.NewSingleLoader(rootAgent),
	SessionService:  session.InMemoryService(),
	SSEWriteTimeout: 120 * time.Second,
})

mux := http.NewServeMux()

// "POST /{$}" matches the root path exactly, so it does not shadow the ADK
// routes mounted at "/" below, and registration order does not matter.
mux.HandleFunc("POST /{$}", func(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Calls [][]any `json:"calls"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	replies := make([]string, 0, len(req.Calls))
	for _, call := range req.Calls {
		prompt := ""
		if len(call) > 0 {
			prompt = fmt.Sprint(call[0])
		}
		// Invoke the agent here via runner.Runner; see /google-agents-cli-adk-code.
		replies = append(replies, prompt)
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(map[string]any{"replies": replies})
})

mux.Handle("/", restServer)
http.ListenAndServe(addr, mux)
```

This replaces the launcher, so `webui` and the keyword CLI go away; mount `server/adka2a` and
`server/agentengine` on the same mux if you need them.

Add the package to the image too — the generated `Dockerfile` copies **named** directories, so a new
one is absent from the build context and the build fails with `package <mod>/<pkg> is not in std`:

```dockerfile
COPY bqremote/ ./bqremote/
```

### BQ remote function Terraform:

```hcl
resource "google_bigquery_routine" "my_fn" {
  routine_type    = "SCALAR_FUNCTION"
  language        = "SQL"
  definition_body = ""
  arguments {
    name          = "message"
    argument_kind = "FIXED_TYPE"
    data_type     = jsonencode({ typeKind = "STRING" })
  }
  return_type = jsonencode({ typeKind = "STRING" })
  remote_function_options {
    endpoint   = google_cloud_run_v2_service.app.uri  # root URL only
    connection = google_bigquery_connection.my_conn.name
  }
}
```
references/cicd-pipeline.md›
# Production Deployment — CI/CD Pipeline

**Best for:** Production applications, teams requiring staging → production promotion.

**Prerequisites:**
1. Project must NOT be in a gitignored folder
2. User must provide staging and production GCP project IDs
3. GitHub repository name and owner

**Steps:**
1. If prototype, first add Terraform/CI-CD files using the Agents CLI (see `/google-agents-cli-scaffold` for full options):
   ```bash
   agents-cli scaffold enhance . --cicd-runner github_actions
   ```

2. Ensure you're logged in to GitHub CLI:
   ```bash
   gh auth login  # (skip if already authenticated)
   ```

3. Run infra cicd:
   ```bash
   agents-cli infra cicd \
     --staging-project YOUR_STAGING_PROJECT \
     --prod-project YOUR_PROD_PROJECT \
     --repository-name YOUR_REPO_NAME \
     --create
   ```

4. Push code to trigger deployments

## Key `infra cicd` Flags

| Flag | Required | Description |
|------|----------|-------------|
| `--staging-project` | Yes | GCP project ID for staging environment |
| `--prod-project` | Yes | GCP project ID for production environment |
| `--repository-name` | Yes | GitHub repository name |
| `--create` | No | Create a new GitHub repository. Omit to use an existing one (the command verifies the repository exists either way) |
| `--repository-owner` | No | GitHub repo owner. Defaults to your `gh` CLI user — set this when creating under (or pointing to) a GitHub organization or another user's account |
| `--cicd-project` | No | Separate GCP project for CI/CD infrastructure. Defaults to prod project |
| `--region` | No | GCP region. Auto-detected or defaults to `us-east1` |
| `--local-state` | No | Store Terraform state locally instead of in GCS (see `references/terraform-patterns.md`) |

Run `agents-cli infra cicd --help` for the full flag reference (Cloud Build options, dev project, region, etc.).

## Choosing a CI/CD Runner

| Runner | Pros | Cons |
|--------|------|------|
| **github_actions** (Default) | No PAT needed, uses `gh auth`, WIF-based, fully automated | Requires GitHub CLI authentication |
| **google_cloud_build** | Native GCP integration | Requires `--github-pat` and `--github-app-installation-id` in programmatic mode (or `-i` for interactive OAuth flow) |

### Cloud Build Example

```bash
agents-cli infra cicd \
  --staging-project YOUR_STAGING_PROJECT \
  --prod-project YOUR_PROD_PROJECT \
  --repository-name YOUR_REPO_NAME \
  --create \
  --github-pat YOUR_PAT \
  --github-app-installation-id YOUR_APP_ID
```

## How Authentication Works (WIF)

Both runners use **Workload Identity Federation (WIF)** — GitHub/Cloud Build OIDC tokens are trusted by a GCP Workload Identity Pool, which grants `cicd_runner_sa` impersonation. No long-lived service account keys needed. Terraform in `infra cicd` creates the pool, provider, and SA bindings automatically. If auth fails, re-run `terraform apply` in the CI/CD Terraform directory.

## CI/CD Pipeline Stages

The pipeline has three stages:

1. **CI (PR checks)** — Triggered on pull request. Runs unit and integration tests.
2. **Staging CD** — Triggered on merge to `main`. Builds container, deploys to staging, runs load tests.
   > **Path filter:** Staging CD only triggers when relevant paths change. The filter differs by language:
   * Python: the agent directory (`app/**` by default), `tests/**`, `deployment/**`, `uv.lock`.
   * Go: the agent directory, `e2e/**`, `deployment/**`, `go.mod`, `go.sum`, `main.go`, `Dockerfile`.
   The first push after `infra cicd` won't trigger staging CD unless one of these changes. If nothing happens after pushing, this is why.
3. **Production CD** — Triggered after successful staging deploy via `workflow_run`. Might require **manual approval** before deploying to production.
   > **Approving:** Go to GitHub Actions → the production workflow run → click "Review deployments" → approve the pending `production` environment. This is GitHub's environment protection rules, not a custom mechanism.

**IMPORTANT**: `infra cicd` creates infrastructure but doesn't deploy automatically. Terraform configures all required GitHub secrets and variables (WIF credentials, project IDs, service accounts). Push code to trigger the pipeline:

```bash
git add . && git commit -m "Initial agent implementation"
git push origin main
```

To approve production deployment:

```bash
# GitHub Actions: Approve via repository Actions tab (environment protection rules)

# Cloud Build: Find pending build and approve
gcloud builds list --project=PROD_PROJECT --region=REGION --filter="status=PENDING"
gcloud builds approve BUILD_ID --project=PROD_PROJECT
```

## Non-GitHub Providers (GitLab, Bitbucket, etc.)

The `agents-cli infra cicd` command only supports GitHub. It requires the `gh` CLI, uses the Terraform `github` provider, and both CI/CD runners (GitHub Actions, Cloud Build) assume a GitHub source repo.

For other git providers, use the scaffolded Terraform as a starting point:
1. Run `agents-cli scaffold enhance` to generate the Terraform and CI/CD files
2. Replace the `github` provider and resources in `deployment/terraform/cicd/` with your provider's equivalents
3. Adapt the CI/CD pipeline files (e.g., replace `.github/workflows/` with `.gitlab-ci.yml`)
4. Run `terraform apply` directly instead of `agents-cli infra cicd`
references/cloud-run.md›
# Cloud Run Infrastructure

> **Assumes `/google-agents-cli-scaffold` scaffolding.** If your project isn't scaffolded yet, see `/google-agents-cli-scaffold` first.

## Scaling & Resource Defaults

Agents CLI scaffolds Cloud Run infrastructure in `deployment/terraform/single-project/service.tf` (and the `cicd/` variant). Check that file for current resource limits, scaling configuration, concurrency, and session affinity settings.

Key settings to be aware of: `cpu_idle` (CPU allocation strategy), `min_instance_count` (cold start avoidance), `max_instance_request_concurrency` (concurrency per instance), and `session_affinity` (sticky routing).

For how to size cpu/memory/workers/concurrency together (and avoid OOM), see **Sizing a deployment** in the `/google-agents-cli-deploy` skill.

## Dockerfile

Scaffolded projects include a `Dockerfile`:
* Python uses a single-stage build with `uv` for dependency management.
* Go uses a multi-stage build — a `golang` builder image compiles the binary, which is copied into a slim runtime image.

Check the project root `Dockerfile` for the exact configuration.

## Served Endpoints

A scaffolded Python project serves `uvicorn app.fast_api_app:app` on port 8080; a Go project serves the binary built from `main.go`. Which routes that app exposes depends on the framework, so check `app/fast_api_app.py` or `main.go`.

> **ADK projects.** The app serves the ADK HTTP surface (`/run_sse`, `/apps/...`) plus A2A (JSON-RPC + agent card — A2A is built into every ADK agent). A2A differs between languages — Python serves both under `/a2a/{app_name}`; Go serves the agent card at the root (`/.well-known/agent-card.json`) and JSON-RPC under `/a2a/` (`/a2a/v1/invoke`, plus `/a2a/invoke` for the v0 protocol).

## Session Types

> **ADK projects.** The session wiring below (`shared://session`, `app_utils/services.py`) is ADK scaffold behavior. The Cloud SQL infrastructure it uses is framework-agnostic.

| Type | Configuration | Use Case |
|------|--------------|----------|
| **In-memory** | Default (`shared://session` resolved by `app_utils/services.py` (in-memory)) | Local dev only; lost on instance restart |
| **Cloud SQL** | `--session-type cloud_sql` at scaffold time | Production persistent sessions (Postgres 15, IAM auth) |
| **Agent Runtime in Gemini platform** | Managed sessions (`agentengine://{resource_name}`) | When using Agent Runtime as session backend |

The concrete session URI for `cloud_sql` / `agent_platform_sessions` is now built inside `app_utils/services.py`, not `fast_api_app.py`.

Cloud SQL session infrastructure (instance, database, Cloud SQL Unix socket volume mount) is configured in `deployment/terraform/single-project/service.tf`.

> **Manual Deployment Warning:** When using Cloud SQL without Terraform (e.g., direct `gcloud run deploy` with `--add-cloudsql-instances`), you MUST manually grant `roles/cloudsql.client` to the runtime service account, otherwise the connection will fail with authorization errors.

## Network & Ingress

Default ingress is `INGRESS_TRAFFIC_ALL` (public). To restrict, change the `ingress` setting in `service.tf` to `INGRESS_TRAFFIC_INTERNAL_ONLY` (VPC only) or `INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER` (internal + GCLB).

IAP (Identity-Aware Proxy) can be enabled by running `agents-cli deploy --iap` (Cloud Run only), which adds Google identity authentication without code changes. IAP is configured by the deploy flag, not by a generated Terraform variable.

VPC connectors are not configured by default. Add them in custom Terraform if needed for private resource access (see `references/terraform-patterns.md`).
references/cmek.md›
# Customer-managed encryption keys (CMEK)

An agent on Agent Runtime in Gemini platform can be encrypted with your Cloud KMS key instead of a Google-managed one.
The key is set at creation and can't be added, changed, or removed later.

## Where to set the key

| Agent created by | Set the key in |
|---|---|
| `agents-cli deploy` (dev deployments) | `deploy --key` |
| `infra single-project` | `deployment/terraform/single-project/service.tf` |
| `infra cicd` (staging/prod) | `deployment/terraform/cicd/service.tf` |

`--key` only works when `deploy` creates the agent. Terraform-created agents are only updated by
`deploy`, so add the key to the Terraform yourself ([below](#terraform)).

## Key and grants

Use a single-region key in the agent's region, by full name:
`projects/KEY_PROJECT/locations/REGION/keyRings/RING/cryptoKeys/KEY`. The key may live in another project.

Before creation, grant `roles/cloudkms.cryptoKeyEncrypterDecrypter` **on the key** to both
service agents of the project running the agent:

- `[email protected]`
- `service-PROJECT_NUMBER@gcp-sa-aiplatform-re.iam.gserviceaccount.com`

```bash
for sa in gcp-sa-aiplatform gcp-sa-aiplatform-re; do
  gcloud kms keys add-iam-policy-binding KEY \
    --keyring RING --location REGION --project KEY_PROJECT \
    --member "serviceAccount:service-PROJECT_NUMBER@${sa}.iam.gserviceaccount.com" \
    --role roles/cloudkms.cryptoKeyEncrypterDecrypter
done
```

Granting needs `roles/cloudkms.admin` (Editor isn't enough). Grants take up to 10 minutes to apply.

## Dev deployments

```bash
agents-cli deploy --key projects/KEY_PROJECT/locations/REGION/keyRings/RING/cryptoKeys/KEY
```

On redeploy, omit `--key` or pass the same key; anything else fails. To change encryption,
delete and re-create the agent. `deploy --list` shows each agent's key.

## Terraform

Add `encryption_spec` to `google_vertex_ai_reasoning_engine.app` and make it depend on the key
grants. If the grants are managed outside Terraform, skip the IAM resource.

**single-project** (`service.tf`):

```hcl
locals {
  kms_key = "projects/KEY_PROJECT/locations/REGION/keyRings/RING/cryptoKeys/KEY"
}

resource "google_kms_crypto_key_iam_member" "agent_platform_cmek" {
  for_each      = toset(["gcp-sa-aiplatform", "gcp-sa-aiplatform-re"])
  crypto_key_id = local.kms_key
  role          = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
  member        = "serviceAccount:service-${data.google_project.project.number}@${each.key}.iam.gserviceaccount.com"
}

resource "google_vertex_ai_reasoning_engine" "app" {
  # ...existing arguments...
  encryption_spec {
    kms_key_name = local.kms_key
  }
  depends_on = [
    google_project_service.services,
    google_kms_crypto_key_iam_member.agent_platform_cmek,
  ]
}
```

**cicd** (`service.tf`), one key per environment, granted to that environment's project:

```hcl
locals {
  kms_keys = {
    staging = "projects/KEY_PROJECT/locations/REGION/keyRings/RING/cryptoKeys/STAGING_KEY"
    prod    = "projects/KEY_PROJECT/locations/REGION/keyRings/RING/cryptoKeys/PROD_KEY"
  }
}

resource "google_kms_crypto_key_iam_member" "agent_platform_cmek" {
  for_each = {
    for pair in setproduct(keys(local.deploy_project_ids), ["gcp-sa-aiplatform", "gcp-sa-aiplatform-re"]) :
    "${pair[0]}-${pair[1]}" => { env = pair[0], agent = pair[1] }
  }
  crypto_key_id = local.kms_keys[each.value.env]
  role          = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
  member        = "serviceAccount:service-${data.google_project.projects[each.value.env].number}@${each.value.agent}.iam.gserviceaccount.com"
}

resource "google_vertex_ai_reasoning_engine" "app" {
  # ...existing arguments...
  encryption_spec {
    kms_key_name = local.kms_keys[each.key]
  }
  depends_on = [
    google_project_service.deploy_project_services,
    google_kms_crypto_key_iam_member.agent_platform_cmek,
  ]
}
```

Adding a key to an already deployed agent re-creates it (new resource ID, placeholder source
until the next deploy). Check `terraform plan` first.
references/gke.md›
# GKE Infrastructure

> **Assumes `agents-cli` scaffolding.** If your project isn't scaffolded yet, see `/google-agents-cli-scaffold` first.

## Deployment Architecture

GKE uses **container-based deployment** to a managed GKE Autopilot cluster. Your agent is packaged as a Docker container (same Dockerfile as Cloud Run), pushed to Artifact Registry, and deployed via Terraform-managed Kubernetes resources.

## Dockerfile

Scaffolded projects include a `Dockerfile` at the project root — same as Cloud Run. Python uses a single-stage build with `uv` for dependency management; Go uses a multi-stage build that compiles the binary in a `golang` image and copies it into a slim runtime image. Check the project root `Dockerfile` for the exact configuration.

## Kubernetes Resources (Terraform-Managed)

All Kubernetes resources are managed by Terraform in `deployment/terraform/cicd/service.tf` (staging/prod) and `deployment/terraform/single-project/service.tf` (single-project). CI/CD pipelines only update the container image via `kubectl set image`.

| Resource | Purpose |
|----------|---------|
| **`kubernetes_deployment_v1`** | Pod spec, container config, resource requests/limits, startup/readiness/liveness probes, env vars, optional Cloud SQL proxy sidecar |
| **`kubernetes_service_v1`** | LoadBalancer service exposing port 8080 |
| **`kubernetes_horizontal_pod_autoscaler_v2`** | HorizontalPodAutoscaler (2-10 replicas, 70% CPU target) |
| **`kubernetes_pod_disruption_budget_v1`** | PodDisruptionBudget (minAvailable: 1) |
| **`kubernetes_service_account_v1`** | Kubernetes ServiceAccount for Workload Identity |
| **`kubernetes_namespace_v1`** | Namespace for the application |
| **`kubernetes_secret_v1`** | DB password secret (Cloud SQL only) |

## Terraform Infrastructure

GKE infrastructure is provisioned in `deployment/terraform/single-project/service.tf`. Check that file for current configuration.

Key differences from Cloud Run: Terraform provisions a full networking stack (VPC, subnet, Cloud NAT for private node internet access) and a GKE Autopilot cluster with private nodes. Cloud SQL (optional, when `session_type == "cloud_sql"`) uses a proxy sidecar in the pod rather than Cloud Run's Unix socket volume mount.

## Workload Identity

GKE uses Workload Identity to map Kubernetes service accounts to GCP service accounts. The Kubernetes SA is annotated with the GCP `app_sa` email and bound via an `iam.workloadIdentityUser` IAM binding in Terraform.

This lets pods authenticate as `app_sa` without service account keys — same security model as Cloud Run's service identity, but configured through Kubernetes.

## Session Types

> **ADK projects.** The session wiring below (`shared://session`, `app_utils/services.py`) is ADK scaffold behavior. The Cloud SQL infrastructure it uses is framework-agnostic.

| Type | Configuration | Use Case |
|------|--------------|----------|
| **In-memory** | Default (`shared://session` resolved by `app_utils/services.py` (in-memory)) | Local dev only; lost on pod restart |
| **Cloud SQL** | `--session-type cloud_sql` at scaffold time | Production persistent sessions (Cloud SQL proxy sidecar in pod) |
| **Agent Runtime in Gemini platform** | Managed sessions (`agentengine://{resource_name}`) | When using Agent Runtime as session backend |

The concrete session URI for `cloud_sql` / `agent_platform_sessions` is now built inside `app_utils/services.py`, not `fast_api_app.py`.

Cloud SQL in GKE uses a **proxy sidecar container** in the pod (unlike Cloud Run which uses a Unix socket volume mount). The sidecar is configured in the `kubernetes_deployment_v1` Terraform resource.

## Served Endpoints

A scaffolded Python project serves `uvicorn app.fast_api_app:app` on port 8080; a Go project serves the binary built from `main.go`. Which routes that app exposes depends on the framework, so check `app/fast_api_app.py` or `main.go`.

> **ADK projects.** The app serves the ADK HTTP surface (`/run_sse`, `/apps/...`) plus A2A (JSON-RPC + agent card — A2A is built into every ADK agent). A2A differs between languages — Python serves both under `/a2a/{app_name}`; Go serves the agent card at the root (`/.well-known/agent-card.json`) and JSON-RPC under `/a2a/` (`/a2a/v1/invoke`, plus `/a2a/invoke` for the v0 protocol).

## Testing Your Deployed Agent

> **ADK projects.** The session + `/run_sse` calls below are the ADK HTTP surface. On other frameworks, port-forward the same way and call your app's own routes (e.g. the A2A endpoint).

GKE LoadBalancer services are **internal by default** — they are not accessible from outside the VPC. Use `kubectl port-forward` to access the service locally:

```bash
# Start port-forward (runs in background)
kubectl port-forward svc/SERVICE_NAME 8080:8080 -n NAMESPACE &

# Test health endpoint
curl "http://127.0.0.1:8080/"

# Create a session
curl -X POST "http://127.0.0.1:8080/apps/app/users/test-user/sessions" \
  -H "Content-Type: application/json" \
  -d '{}'

# Send a message via SSE streaming
curl -X POST "http://127.0.0.1:8080/run_sse" \
  -H "Content-Type: application/json" \
  -d '{
    "app_name": "app",
    "user_id": "test-user",
    "session_id": "SESSION_ID",
    "new_message": {"role": "user", "parts": [{"text": "Hello!"}]}
  }'

# Live agents: same forward, WebSocket surface
agents-cli run "Hello!" --mode adk_live --url http://127.0.0.1:8080
```

## Network & Ingress

GKE LoadBalancer services are **internal by default** (the `cloud.google.com/load-balancer-type: "Internal"` annotation is set in Terraform). The internal IP is used for pod-to-pod A2A communication within the cluster. Use `kubectl port-forward` for local access.
references/terraform-patterns.md›
# Custom Infrastructure (Terraform)

> **Assumes `/google-agents-cli-scaffold` scaffolding.** These patterns apply to projects with `deployment/terraform/` directories.

## Where to Put Custom Terraform

| Scenario | Location |
|----------|----------|
| Single-project infrastructure | `deployment/terraform/single-project/` |
| CI/CD environments (staging/prod) | `deployment/terraform/cicd/` |

## Example: Custom Resources

```hcl
# deployment/terraform/single-project/custom_resources.tf

resource "google_pubsub_topic" "events" {
  name    = "${var.project_name}-events"
  project = var.project_id
}

resource "google_bigquery_dataset" "analytics" {
  dataset_id = "${replace(var.project_name, "-", "_")}_analytics"
  project    = var.project_id
  location   = var.region
}

# Eventarc trigger for Cloud Storage
resource "google_eventarc_trigger" "storage_trigger" {
  name     = "${var.project_name}-storage-trigger"
  location = var.region
  project  = var.project_id

  matching_criteria {
    attribute = "type"
    value     = "google.cloud.storage.object.v1.finalized"
  }
  matching_criteria {
    attribute = "bucket"
    value     = google_storage_bucket.uploads.name
  }

  destination {
    cloud_run_service {
      service = google_cloud_run_v2_service.app.name
      region  = var.region
      # ADK trigger-source route; on other frameworks point this at your app's own path
      path    = "/apps/${var.project_name}/trigger/eventarc"
    }
  }

  service_account = google_service_account.app_sa.email
}
```

**For CI/CD environments (staging/prod):**

Add resources to `deployment/terraform/cicd/` (applies to staging and prod):

```hcl
# deployment/terraform/cicd/custom_resources.tf
# Resources here are created in BOTH staging and prod projects
# Use for_each with local.deploy_project_ids for multi-environment

resource "google_pubsub_topic" "events" {
  for_each = local.deploy_project_ids
  name     = "${var.project_name}-events"
  project  = each.value
}
```

## IAM for Custom Resources

**Single-project** (`deployment/terraform/single-project/`):

```hcl
resource "google_pubsub_topic_iam_member" "app_publisher" {
  topic   = google_pubsub_topic.events.name
  project = var.project_id
  role    = "roles/pubsub.publisher"
  member  = "serviceAccount:${google_service_account.app_sa.email}"
}

# Grant BigQuery data editor
resource "google_bigquery_dataset_iam_member" "app_editor" {
  dataset_id = google_bigquery_dataset.analytics.dataset_id
  project    = var.project_id
  role       = "roles/bigquery.dataEditor"
  member     = "serviceAccount:${google_service_account.app_sa.email}"
}
```

**CI/CD** (`deployment/terraform/cicd/`) — use `for_each` to apply across environments:

```hcl
resource "google_pubsub_topic_iam_member" "app_publisher" {
  for_each = local.deploy_project_ids
  topic    = google_pubsub_topic.events[each.key].name
  project  = each.value
  role     = "roles/pubsub.publisher"
  member   = "serviceAccount:${google_service_account.app_sa[each.key].email}"
}
```

## Applying Custom Infrastructure

```bash
# For single-project infrastructure
agents-cli infra single-project --apply  # Runs terraform apply in deployment/terraform/single-project/

# For CI/CD, infrastructure is applied automatically on push
```

## Common Patterns

> The `/apps/{app_name}/trigger/*` paths below are ADK's trigger-source routes. The Terraform is the same on any framework — point it at whatever path your app serves.

**Cloud Storage trigger (Eventarc):**
- Create bucket in Terraform
- Create Eventarc trigger pointing to `/apps/{app_name}/trigger/eventarc` endpoint
- Grant `eventarc.eventReceiver` role to app service account

**Pub/Sub processing:**
- Create topic and push subscription in Terraform
- Point subscription to `/apps/{app_name}/trigger/pubsub` endpoint
- Grant `iam.serviceAccountTokenCreator` role for push auth

> **Go trigger endpoints are not enabled by default, and sit under `/api`.** The scaffolded `main.go`
> already registers the `pubsub` and `eventarc` sub-launchers, but the container `ENTRYPOINT` omits
> their keywords, so the routes are not served. Each trigger launcher keeps its own `-path_prefix`.
> The launcher prints both URLs at startup; point the subscription at those.

**BigQuery Remote Function:**
- Create BigQuery connection in Terraform
- Grant connection service account permission to invoke Cloud Run
- Create the remote function via SQL after deployment

**Cloud SQL sessions:**
- Already configured when using `--session-type cloud_sql` via the Agents CLI (see `/google-agents-cli-scaffold`)
- Additional tables/schemas can be added via migration scripts

## Terraform State Management

### Remote State (Default)

By default, `infra cicd` creates a GCS bucket for remote Terraform state:

```hcl
# Auto-configured backend in deployment/terraform/cicd/backend.tf
terraform {
  backend "gcs" {
    bucket = "{cicd_project}-terraform-state"
    prefix = "{repository_name}/{prod|single-project}"
  }
}
```

The state bucket is named `{cicd_project}-terraform-state` and uses the repository name + environment as the prefix to isolate state per project and environment.

### Local State

Use the `--local-state` flag with `infra cicd` to skip remote backend setup and store state locally:

```bash
agents-cli infra cicd \
  --staging-project STAGING_PROJECT \
  --prod-project PROD_PROJECT \
  --repository-name REPO_NAME \
  --create \
  --local-state
```

Local state is stored in `deployment/terraform/cicd/terraform.tfstate`. This is suitable for single-developer projects but not recommended for teams (state conflicts).

### Importing Existing Resources

If resources already exist (e.g., created manually or by a previous deployment), import them into Terraform state:

```bash
# Import a Cloud Run service
cd deployment/terraform/single-project
terraform import google_cloud_run_v2_service.app \
  projects/PROJECT_ID/locations/REGION/services/SERVICE_NAME

# Import a service account
terraform import google_service_account.app_sa \
  projects/PROJECT_ID/serviceAccounts/SA_EMAIL

# Import a secret
terraform import google_secret_manager_secret.my_secret \
  projects/PROJECT_ID/secrets/SECRET_NAME
```

After importing, run `terraform plan` to verify the imported state matches the configuration. Fix any drift before applying.
references/testing-deployed-agents.md›
# Testing Your Deployed Agent

## Quick Test (Recommended)

The fastest way to test any deployed agent is the `run --url` command — it handles authentication, session creation, and streaming automatically:

```bash
# A2A protocol
agents-cli run --url https://my-agent-abc123.run.app --mode a2a "Hello, what can you do?"

# ADK streaming API (ADK projects only)
agents-cli run --url https://my-agent-abc123.run.app --mode adk "Hello, what can you do?"

# Agent Runtime in Gemini platform (auto-detected from URL — works with either mode)
agents-cli run --url https://LOCATION-aiplatform.googleapis.com/v1/projects/PROJECT/locations/LOCATION/reasoningEngines/ID --mode adk "Hello!"

# Custom auth header (overrides auto-detected credentials)
agents-cli run --url https://my-agent.run.app --mode a2a -H "Authorization: Bearer my-token" "Hello!"
```

The `--mode` flag is required with `--url`: use `a2a` for the A2A protocol, or `adk` for the ADK streaming API (`/run_sse`, or `:streamQuery` for Agent Runtime) if the deployed container serves it. Agent Runtime URLs are detected automatically. Add `-v` for full JSON event payloads.

> Agent Runtime exposes the whole container under an `/api/...` HTTP passthrough (`https://{location}-aiplatform.googleapis.com/reasoningEngines/v1/{resource}/api/<path>`), so the container's own routes — `/run_sse`, `/a2a/{app_name}`, etc. — are also reachable there. (This is separate from the reasoning_engine adapter, which serves only `/api/reasoning_engine` + `/api/stream_reasoning_engine` for the native `:streamQuery` contract.)

Auth is auto-detected via Google Cloud credentials. Use `--header` / `-H` to override.

For more control (scripting, direct curl), see the target-specific sections below.

---

## Agent Runtime deployment

Beyond the `run --url` quick test above, you can query the deployment directly.

**Option 1: Python Script**
```python
import json
import agentplatform

with open("deployment_metadata.json") as f:
    engine_id = json.load(f)["remote_agent_runtime_id"]

client = agentplatform.Client(location="us-east1")
agent = client.runtimes.get(name=engine_id)

async for event in agent.async_stream_query(message="Hello!", user_id="test"):
    print(event)
```

**Option 2: Playground**
```bash
agents-cli playground
```

## Cloud Run Deployment

> **Auth required by default.** Cloud Run deploys with `--no-allow-unauthenticated`, so all requests need an `Authorization: Bearer` header with an identity token. Getting a 403? You're likely missing this header. To allow public access, redeploy with `--allow-unauthenticated`.

### Quickest: `run --url` (direct or via local proxy)

`agents-cli run` mints the identity token for you, so you don't have to construct auth headers. Two ways to point it at the service:

```bash
# Direct: use the Service URL from your deploy output
agents-cli run --url https://SERVICE_NAME-PROJECT_NUMBER.REGION.run.app --mode a2a "Hello!"

# Or proxy locally (the flow gcloud suggests after deploy), then use the proxy URL:
gcloud run services proxy SERVICE_NAME --region REGION --project PROJECT
# in another shell (the proxy holds the terminal, listening on 127.0.0.1:8080):
agents-cli run --url http://127.0.0.1:8080 --mode a2a "Hello!"
```

Pass the **base** service (or proxy) URL — not a `/a2a` suffix; the CLI finds the agent card itself. Swap `--mode a2a` for `--mode adk` to use the ADK HTTP API instead.

> **ADK projects.** The session + `/run_sse` calls below are the ADK HTTP surface. On other frameworks the health check and auth header are the same, but call your app's own routes (e.g. the A2A endpoint via `agents-cli run --mode a2a`).

```bash
SERVICE_URL="https://SERVICE_NAME-PROJECT_NUMBER.REGION.run.app"
AUTH="Authorization: Bearer $(gcloud auth print-identity-token)"

# Test health endpoint
curl -H "$AUTH" "$SERVICE_URL/"

# Step 1: Create a session (required before sending messages)
curl -X POST "$SERVICE_URL/apps/app/users/test-user/sessions" \
  -H "Content-Type: application/json" \
  -H "$AUTH" \
  -d '{}'
# → returns JSON with "id" — use this as SESSION_ID below

# Step 2: Send a message via SSE streaming
curl -X POST "$SERVICE_URL/run_sse" \
  -H "Content-Type: application/json" \
  -H "$AUTH" \
  -d '{
    "app_name": "app",
    "user_id": "test-user",
    "session_id": "SESSION_ID",
    "new_message": {"role": "user", "parts": [{"text": "Hello!"}]}
  }'
```

> **Common mistake:** Using `{"message": "Hello!", "user_id": "...", "session_id": "..."}` returns `422 Field required`. The ADK HTTP server expects the `new_message` / `parts` schema shown above, and the session must already exist.

## GKE Deployment

GKE LoadBalancer services are **internal by default**. See `references/gke.md` for curl examples and endpoint details.

## Load Tests

See `tests/load_test/README.md` (Python) or `e2e/load_test/README.md` (Go) for configuration, default settings, and CI/CD integration details. Load tests run automatically during the staging CD pipeline stage.
SKILL.md›
---
name: google-agents-cli-deploy
description: >
  This skill should be used when the user wants to "deploy an agent",
  "deploy my ADK agent", "set up CI/CD", "configure secrets",
  "troubleshoot a deployment", or needs guidance on Agent Runtime,
  Cloud Run, or GKE deployment targets, or binding an agent to an Agent Gateway.
  Covers deployment workflows, service accounts, rollback, and production infrastructure.
  Applies to any framework agents-cli deploys (ADK, LangChain, ...).
  Part of the agents-cli skills suite.
  Do NOT use for agent API code patterns (ADK: use google-agents-cli-adk-code), evaluation
  (use google-agents-cli-eval), or project scaffolding (use google-agents-cli-scaffold).
metadata:
  author: Google
  license: Apache-2.0
  version: 1.11.0
  requires:
    bins:
      - agents-cli
    install: "uv tool install google-agents-cli"
---

# Deployment Guide

> **Requires:** `agents-cli` (`uv tool install google-agents-cli`) — [install uv](https://docs.astral.sh/uv/getting-started/installation/index.md) first if needed.

> Prefer using the `agents-cli` commands throughout this guide — they wrap Terraform, Docker, and deployment into a tested pipeline. If your project isn't scaffolded yet, see `/google-agents-cli-scaffold` to add deployment support first.

### Reference Files

For deeper details, consult these reference files in `references/`:

- **`cloud-run.md`** — Scaling defaults, Dockerfile, session types, networking
- **`agent-runtime.md`** — container-based deploy, unified FastAPI app, the `/api` passthrough, Terraform resource, deployment metadata, CI/CD differences
- **`gke.md`** — GKE Autopilot cluster, Kubernetes manifests, Workload Identity, session types, networking
- **`terraform-patterns.md`** — Custom infrastructure, IAM, state management, importing resources
- **`batch-inference.md`** — BigQuery Remote Function trigger; for Pub/Sub / Eventarc on ADK see `/google-agents-cli-adk-code`
- **`cicd-pipeline.md`** — Full CI/CD pipeline setup, `infra cicd` flags, runner comparison, WIF auth, pipeline stages
- **`testing-deployed-agents.md`** — Testing instructions per deployment target, curl examples, load tests
- **`cmek.md`** — Customer-managed encryption keys on Agent Runtime in Gemini platform: key grants, `deploy --key` for dev, Terraform edits for staging/prod

> **Observability:** See the `/google-agents-cli-observability` skill for Cloud Trace, prompt-response logging, BigQuery Analytics, and third-party integrations.

---

## Deployment Target Decision Matrix

Choose the right deployment target based on your requirements:

| Criteria | Agent Runtime | Cloud Run | GKE |
|----------|-------------|-----------|-----|
| **Scaling** | Managed auto-scaling (configurable min/max, concurrency) | Fully configurable (min/max instances, concurrency, CPU allocation) | Full Kubernetes scaling (HPA, VPA, node auto-provisioning) |
| **Networking** | VPC-SC and PSC-I supported (private VPC connectivity via network attachments) | Full VPC support, direct VPC egress, IAP, ingress rules | Full Kubernetes networking |
| **Session state** | Sessions in Gemini platform (ADK wires `VertexAiSessionService` automatically) | In-memory (dev), Cloud SQL, or a managed sessions backend | In-memory (dev), Cloud SQL, or a managed sessions backend |
| **Batch/event processing** | Trigger endpoints reachable via the Agent Runtime `/api` passthrough | Native trigger endpoints (Pub/Sub, Eventarc); ADK: see `/google-agents-cli-adk-code` | Custom (Kubernetes Jobs, Pub/Sub) |
| **Cost model** | vCPU-hours + memory-hours (not billed when idle) | Per-instance-second + min instance costs | Node pool costs (always-on or auto-provisioned) |
| **Setup complexity** | Lower (managed, purpose-built for agents) | Medium (Dockerfile, Terraform, networking) | Higher (Kubernetes expertise required) |
| **Best for** | Managed infrastructure, minimal ops | Custom infra, full networking control | Full Kubernetes control |

**Ask the user** which deployment target fits their needs. Each is a valid production choice with different trade-offs.

All three targets are container-based, so any language works.

> **Product name mapping:** users may say "Agent Engine", "Vertex AI Agent Engine", or "Agent Runtime"; all mean Agent Runtime. Use `--deployment-target agent_runtime`.

> **Ambient / scheduled / event-driven agents (ADK projects):** ADK's `trigger_sources` registers `/apps/{app}/trigger/*` endpoints on the same FastAPI app for **all** targets. On **Cloud Run** / **GKE** these are public HTTP routes you point a Pub/Sub push subscription or Eventarc trigger at; on **Agent Runtime** the same routes are reachable through its `/api` passthrough (e.g. `.../reasoningEngines/v1/{resource}/api/apps/{app}/trigger/pubsub`). Cloud Run remains the simplest target for unauthenticated trigger sources. See `/google-agents-cli-adk-code` (`references/adk-python.md`, section "12. Event-Driven / Ambient Agents") for the `trigger_sources` pattern.

> **OAuth / user consent agents:** Use **Agent Runtime**, registered with Gemini, for agents that need OAuth 2.0 user consent (e.g., accessing Google Drive, Calendar, or other user-scoped APIs). Cloud Run does not currently support managed OAuth flows. For a worked ADK example, look up OAuth user consent in the topic index in `/google-agents-cli-adk-code` → `references/samples.md`.

> **Live and voice agents:** pass **`--timeout 3600`** on Cloud Run so a session outlives the 300s default, and **`--min-instances 1`** on Cloud Run or Agent Runtime so the first caller after idle does not hear a cold start. GKE sizes pods in Terraform. URL shapes and session caps: `/google-agents-cli-adk-code` (`references/adk-python-live.md`).

---

## Deploying to Dev

### Deploy Workflow

**Task tracking:** Deployment involves multiple sequential steps (infra setup, CI/CD configuration, deploy, verification). Use a task list to track progress through these steps — skipping one often causes failures in later steps that are hard to trace back.

1. If prototype (no deployment target), first enhance: `agents-cli scaffold enhance . --deployment-target <target>`
2. **Notify the human**: paste the eval scores and test results, then ask "Ready to deploy to dev?"
3. **Wait for explicit approval**
4. Once approved: `agents-cli deploy`

> **Agent Runtime timeout recovery:** Agent Runtime deploys can take 5-10 minutes and may exceed command timeouts. If the deploy command is cancelled or times out, the deployment continues server-side. Run `agents-cli deploy --status` to check progress — poll every 60 seconds until it reports completion or failure.

**IMPORTANT**: Never run `agents-cli deploy` without explicit human approval.

> **Not a prerequisite.** `agents-cli deploy` works on its own. One exception: if the user wants Terraform-managed observability (prompt-response logging, BigQuery analytics), run `agents-cli infra single-project --apply` first, see `/google-agents-cli-observability`.

### Single-Project Infrastructure Setup (Optional — Advanced)

`agents-cli infra single-project --apply` runs `terraform apply` in `deployment/terraform/single-project/`. Use this to **provision single-project GCP infrastructure without CI/CD** (service accounts, IAM bindings, telemetry resources, Artifact Registry). It is required before deployment only when Terraform will manage observability; basic Agent Runtime and Cloud Run deployments do not need it, while GKE deploy handles its own targeted Terraform.

```bash
# Optional — provision infrastructure in a single GCP project
agents-cli infra single-project --apply
```

> **Note:** `agents-cli deploy` doesn't automatically use the Terraform-created `app_sa`. Pass the service account explicitly: `agents-cli deploy --service-account SA_EMAIL`.

### Deploy Flag Reference

| Flag | Description | Targets |
|------|-------------|---------|
| `--project` | GCP project ID | All |
| `--region` | GCP region | All |
| `--service-account` | Service account email for the deployed agent | All |
| `--service-name` | Override the deployed service name (Cloud Run service or Agent Runtime display name); defaults to the project name. If you override it, consider updating your Terraform and CI (if present) — they name resources from the project name. Not supported for GKE, whose names are fully owned by Terraform. | Agent Runtime, Cloud Run |
| `--secrets` / `--update-secrets` | Comma-separated `ENV=SECRET` or `ENV=SECRET:VERSION` pairs. Cloud Run adds them to the service's existing secrets; Agent Runtime replaces the agent's existing secrets with the ones you pass. | Agent Runtime, Cloud Run |
| `--env-vars` / `--update-env-vars` | Comma-separated `KEY=VALUE` environment variables | All |
| `--agent-identity` | Enable [Agent Identity](https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/runtime/agent-identity) | Agent Runtime |
| `--no-agent-identity` | Disable Agent Identity. Passing neither `--agent-identity` nor `--no-agent-identity` defaults to no Agent Identity, when a new agent is created, or keeps the current identity type on subsequent re-deployments. | Agent Runtime |
| `--network-attachment` | Network attachment resource name for [PSC interface](https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/runtime/private-service-connect-interface) (enables private VPC connectivity) | Agent Runtime |
| `--dns-peering-domain` | DNS peering domain suffix, e.g. `my-internal.corp.` (requires `--network-attachment`) | Agent Runtime |
| `--dns-peering-project` | Project ID hosting the Cloud DNS managed zone for DNS peering (requires `--network-attachment`) | Agent Runtime |
| `--dns-peering-network` | VPC network name in the target project for DNS peering (requires `--network-attachment`) | Agent Runtime |
| `--agent-gateway-egress` | Bind the agent to an [Agent Gateway](https://docs.cloud.google.com/gemini-enterprise-agent-platform/govern/gateways/agent-gateway-overview) governing outbound traffic. Full resource name of a gateway with `governedAccessPath=AGENT_TO_ANYWHERE`. Empty value unbinds; omit to leave unchanged. See [Agent Gateway](#agent-gateway) | Agent Runtime |
| `--agent-gateway-ingress` | Bind the agent to an Agent Gateway governing inbound traffic. Full resource name of a gateway with `governedAccessPath=CLIENT_TO_AGENT`. Empty value unbinds; omit to leave unchanged | Agent Runtime |
| `--key` | Cloud KMS key for customer-managed encryption (CMEK), as a full `projects/PROJECT/locations/REGION/keyRings/RING/cryptoKeys/KEY` name. Set on create only; for dev deployments (staging/prod set the key in Terraform). See [Customer-managed encryption](#customer-managed-encryption-cmek) | Agent Runtime |
| `--memory` | Memory limit (default: `4Gi`) | Agent Runtime, Cloud Run |
| `--cpu` | CPU limit (default: `1`) | Agent Runtime, Cloud Run |
| `--min-instances` | Minimum number of instances (default: `0`, i.e. scale to zero; the generated Terraform uses `1`) | Agent Runtime, Cloud Run |
| `--max-instances` | Maximum number of instances (default: `10`) | Agent Runtime, Cloud Run |
| `--concurrency` | Concurrent requests per container (default: `8`; see [Sizing a deployment](#sizing-a-deployment)) | Agent Runtime, Cloud Run |
| `--timeout` | Request timeout in seconds, up to `3600` (Cloud Run default: `300`). Applied on create and update; unset leaves the service's current value | Cloud Run |
| `--port` | Container port | Cloud Run, Agent Runtime |
| `--build-args` | Comma-separated `KEY=VALUE` Docker build args | Agent Runtime |
| `--labels` / `--update-labels` | Comma-separated `KEY=VALUE` resource labels to add or update. Additive: adds/updates the labels you name; labels you don't name are preserved. | Agent Runtime, Cloud Run |
| `--remove-labels` | Comma-separated label `KEY`s to remove. Keys that don't exist are ignored. Can be combined with `--labels`, but a key can't appear in both. Cannot be combined with `--clear-labels`. | Agent Runtime, Cloud Run |
| `--clear-labels` | Remove all resource labels (Cloud Run keeps its reserved `created-by` label). Combine with `--labels` to replace the label set; the clear is applied first. Cannot be combined with `--remove-labels`. | Agent Runtime, Cloud Run |
| `--framework` | Framework the deployed container implements (defaults to `framework:` in `agents-cli-manifest.yaml`, which the template sets; `google-adk` when absent). Sets `agent_framework` on the deployment, which picks the Google Cloud console playground and the runtime contract the deployment declares. The API owns the accepted values ([reference](https://docs.cloud.google.com/gemini-enterprise-agent-platform/reference/rest/v1/projects.locations.reasoningEngines#ReasoningEngineSpec)) | Agent Runtime |
| `--iap` | Enable Identity-Aware Proxy | Cloud Run |
| `--image` | Container image URI (skips source build; not supported for Agent Runtime) | Cloud Run, GKE |
| `--no-wait` | Start deployment and return immediately | Agent Runtime, Cloud Run |
| `--status` | Check the status of a pending `--no-wait` deployment | Agent Runtime, Cloud Run |
| `--list` | List existing deployments and exit | All |
| `--dry-run` / `-n` | Print what would be executed without running it | All |
| `--no-confirm-project` | Skip project confirmation prompt | All |

Run `agents-cli deploy --help` for the full flag reference.

> **Advanced Cloud Run Deploys:** If you need features not exposed via `agents-cli` flags, use `--dry-run` (or `-n`) to print the full `gcloud` command, copy it, and add additional arguments as needed.

> **Project Confirmation:** If the project is resolved automatically (not passed via `--project`), the command will prompt for confirmation in interactive mode. Since agents typically run in non-interactive mode, you MUST pass `--no-confirm-project` to proceed if you are relying on automatic project resolution.

---

## Sizing a deployment

Defaults (same on Agent Runtime and Cloud Run): `--cpu 1`, `--memory 4Gi`, `--concurrency 8`, `--min-instances 0`, `--max-instances 10`. The generated `service.tf` matches, except it pins `min_instances = 1` so production deployments don't experience cold starts. The recommended values were tuned against Python agents, so treat them as a starting point and tune them with load tests (see below).

`agents-cli deploy` scales to zero by default so idle dev and demo agents don't hold capacity. Pass `--min-instances 1` (or deploy via Terraform) when you need a warm instance.

The params are coupled — scale them together:

- **One process — scale out, not up.** The container runs a single server process handling many requests concurrently, so throughput comes from `--concurrency` and horizontal scale (`--max-instances`), not from extra worker processes. Raise `--cpu` only when profiling shows real CPU saturation rather than time spent waiting on the model.
- **Memory bounds concurrency.** Each concurrent request keeps its full working set (context window, history, RAG chunks, response buffer) in memory while it waits on the model, so peak ≈ base + `concurrency × per-request memory`. Memory — not CPU — is the first limit, so raising `--concurrency` without `--memory` is the main OOM cause.
- **Concurrency default is conservative.** A worker can serve many concurrent requests while it waits on the model, but per-request memory is agent-specific, so `8` protects a memory-heavy (RAG/multimodal) agent. Light agents can raise it to 16–32+ after load-testing. See [Underutilized asynchronous workers](https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/runtime/optimize-and-scale#underutilized-workers).

```bash
# 4x throughput: scale every param, not just one
agents-cli deploy --cpu 4 --concurrency 16 --memory 16Gi --max-instances 20
```

**Tune with the scaffolded load test** (`tests/load_test/` in a Python project, `e2e/load_test/` in a Go one; run locally or in the CI/CD staging pipeline): drive load, watch *max* latency and memory/OOM restarts, then adjust — high max latency → raise concurrency (+ workers/cpu); OOM → raise memory or lower concurrency.

> On **GKE** these sizing flags are rejected — size via the Terraform manifests + HorizontalPodAutoscaler under `deployment/terraform/`.

---

## Production Deployment — CI/CD Pipeline

For the full CI/CD pipeline setup guide — prerequisites, `infra cicd` flags, runner comparison, WIF authentication, pipeline stages, and production approval — see `references/cicd-pipeline.md`.

---

## Cloud Run Specifics

For detailed infrastructure configuration (scaling defaults, Dockerfile, FastAPI endpoints, session types, networking), see `references/cloud-run.md`. **ADK:** for ADK docs on Cloud Run deployment, fetch `https://adk.dev/deploy/cloud-run/index.md`.

> **ADK projects.** For event-driven / ambient agent deployment on Cloud Run, see the [`ambient-expense-agent`](https://github.com/google/adk-samples/tree/main/core/python/ambient-expense-agent) sample and `/google-agents-cli-adk-code` (`references/adk-python.md`, section "12. Event-Driven / Ambient Agents") for the `trigger_sources` pattern.

---

## Agent Runtime specifics

Agent Runtime is a managed Gemini platform service for deploying agents as containers. Uses container-based deployment: `agents-cli deploy` packages your project and Agent Runtime builds the image from your project's `Dockerfile` (required) — the same image that serves Cloud Run and GKE.

> **No `gcloud` CLI exists for Agent Runtime.** Deploy via `agents-cli deploy`. Query via the Python `agentplatform.Client` SDK.

Deployments can take 5-10 minutes. Use `--no-wait` to start a deployment and return immediately, then check on it later with `--status`:

```bash
# Start deployment without blocking
agents-cli deploy --no-wait

# Check on progress later
agents-cli deploy --status
```

When `--status` detects the operation has completed, it writes `deployment_metadata.json` and prints the same success output as a normal deploy.

For detailed infrastructure configuration (container deploy flow, the unified FastAPI app and `/api` passthrough, Terraform resource, deployment metadata, session/artifact services, CI/CD differences), see `references/agent-runtime.md`. **ADK:** for ADK docs on Agent Runtime deployment, fetch `https://adk.dev/deploy/agent-runtime/index.md`.

---

## GKE Specifics

For detailed infrastructure configuration (Kubernetes manifests, Terraform resources, Workload Identity, session types, networking), see `references/gke.md`. **ADK:** for ADK docs on GKE deployment, fetch `https://adk.dev/deploy/gke/index.md`.

---

## Service Account Architecture

Scaffolded projects use two service accounts:

- **`app_sa`** (per environment) — Runtime identity for the deployed agent. Roles defined in `deployment/terraform/iam.tf`.
- **`cicd_runner_sa`** (CI/CD project) — CI/CD pipeline identity (GitHub Actions / Cloud Build). Lives in the CI/CD project (defaults to prod project), needs permissions in **both** staging and prod projects.

Check `deployment/terraform/iam.tf` for exact role bindings. Cross-project permissions (Cloud Run service agents, artifact registry access) are also configured there.

**Common 403 errors:**
- "Permission denied on Cloud Run" → `cicd_runner_sa` missing deployment role in the target project
- "Cannot act as service account" → Missing `iam.serviceAccountUser` binding on `app_sa`
- "Secret access denied" → `app_sa` missing `secretmanager.secretAccessor`
- "Cloud SQL connection failed / Not authorized" → Runtime service account missing `roles/cloudsql.client`
- "Artifact Registry read denied" → Cloud Run service agent missing read access in CI/CD project

---

## Required Permissions for CI/CD Setup

- **`roles/secretmanager.admin`** granted to the Cloud Build service account (`service-<PROJECT_NUMBER>@gcp-sa-cloudbuild.iam.gserviceaccount.com`) in the CI/CD project. This allows Cloud Build to access the GitHub token stored in Secret Manager.

---

## Required APIs

The following Google Cloud APIs must be enabled in your project for the skills and deployment to work:

- **`cloudbuild.googleapis.com`** — Required for building container images and running CI/CD pipelines.
- **`secretmanager.googleapis.com`** — Required for managing secrets and API keys.
- **`run.googleapis.com`** — Required for deploying to Cloud Run.

Ensure these are enabled before running deployment or CI/CD setup commands:
```bash
gcloud services enable cloudbuild.googleapis.com secretmanager.googleapis.com run.googleapis.com --project=YOUR_PROJECT_ID
```

---

## Secret Manager (for API Credentials)

Instead of passing sensitive keys as environment variables, use GCP Secret Manager.

```bash
# Create a secret
echo -n "YOUR_API_KEY" | gcloud secrets create MY_SECRET_NAME --data-file=-

# Update an existing secret
echo -n "NEW_API_KEY" | gcloud secrets versions add MY_SECRET_NAME --data-file=-
```

**Grant access:** For Cloud Run, grant `secretmanager.secretAccessor` to `app_sa`. For Agent Runtime, grant it to the platform-managed SA (`service-PROJECT_NUMBER@gcp-sa-aiplatform-re.iam.gserviceaccount.com`). For GKE, grant `secretmanager.secretAccessor` to `app_sa`. Access secrets via Kubernetes Secrets or directly via the Secret Manager API with Workload Identity.

**Pass secrets at deploy time (Agent Runtime, Cloud Run):**
```bash
agents-cli deploy --secrets "API_KEY=my-api-key,DB_PASS=db-password:2"
```

Format: `ENV_VAR=SECRET_ID` or `ENV_VAR=SECRET_ID:VERSION` (defaults to latest).

---

## Cloud SQL Permissions (Manual Deployment)

When using Cloud SQL with Cloud Run in a **manual deployment** (e.g., adding `--add-cloudsql-instances` in non-Terraform setups), you must manually grant the `Cloud SQL Client` role to the runtime service account.

Without this, the deployment may succeed but fail at runtime with `cloudsql.instances.get` authorization errors.

```bash
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
  --member="serviceAccount:YOUR_RUNTIME_SA_EMAIL" \
  --role="roles/cloudsql.client"
```

> **Note:** In full Terraform-managed setups (`infra cicd` / `infra single-project`), this role is configured and managed automatically.

---

## Observability

See the **agents-cli-observability** skill for observability configuration (Cloud Trace, prompt-response logging, BigQuery Analytics, third-party integrations).

---

## Testing Your Deployed Agent

The quickest way to test a deployed agent is `agents-cli run --url <service-url> --mode a2a "your prompt"` — it handles auth, sessions, and streaming automatically (supports Agent Runtime and Cloud Run). **ADK:** `--mode adk` talks to the ADK streaming API instead.

For advanced testing (custom headers, session reuse, scripting, load tests), see `references/testing-deployed-agents.md`.

---

## Deploying with a UI (IAP)

IAP (Identity-Aware Proxy) secures a Cloud Run service so only authorized Google accounts can access it. Enable it by adding the `--iap` flag when deploying (Cloud Run only): `agents-cli deploy --iap`.

For Agent Runtime with a custom frontend, use a **decoupled deployment** — deploy the frontend separately to Cloud Run or Cloud Storage, connecting to the Agent Runtime backend API.

For more information on IAP with Cloud Run, see the [Cloud Console IAP settings](https://cloud.google.com/run/docs/securing/identity-aware-proxy-cloud-run#manage_user_or_group_access).

---

## Rollback & Recovery

The primary rollback mechanism is **git-based**: fix the issue, commit, and push to `main`. The CI/CD pipeline will automatically build and deploy the new version through staging → production.

For immediate Cloud Run rollback without a new commit, use revision traffic shifting:
```bash
gcloud run revisions list --service=SERVICE_NAME --region=REGION
gcloud run services update-traffic SERVICE_NAME \
  --to-revisions=REVISION_NAME=100 --region=REGION
```

Agent Runtime doesn't support revision-based rollback — fix and redeploy via `agents-cli deploy`.

For GKE rollback, use `kubectl rollout undo`:
```bash
kubectl rollout undo deployment/DEPLOYMENT_NAME -n NAMESPACE
kubectl rollout status deployment/DEPLOYMENT_NAME -n NAMESPACE
```

---

## Custom Infrastructure (Terraform)

**CRITICAL**: When your agent requires custom infrastructure (Cloud SQL, Pub/Sub, Eventarc, BigQuery, etc.), you MUST define it in Terraform — never create resources manually via `gcloud` commands. Exception: quick experimentation is fine with `gcloud` or console, but production infrastructure must be in Terraform.

For custom infrastructure patterns, consult `references/terraform-patterns.md` for:
- Where to put custom Terraform files (single-project vs CI/CD)
- Resource examples (Pub/Sub, BigQuery, Eventarc triggers)
- IAM bindings for custom resources
- Terraform state management (remote vs local, importing resources)
- Common infrastructure patterns

---

## Troubleshooting

| Issue | Solution |
|-------|----------|
| Terraform state locked | `terraform force-unlock -force LOCK_ID` in deployment/terraform/ |
| GitHub Actions auth failed | Re-run `terraform apply` in CI/CD terraform dir; verify WIF pool/provider |
| Cloud Build authorization pending | Use `github_actions` runner instead |
| Resource already exists | `terraform import` (see `references/terraform-patterns.md`) |
| Agent Runtime deploy timeout / hangs | Deployments take 5-10 min; check if engine was created (see Agent Runtime specifics) |
| Secret not available | Verify `secretAccessor` granted to `app_sa` (not the default compute SA) |
| Cloud SQL connection failed / 403 | Grant `roles/cloudsql.client` to the runtime service account when using manual deployments |
| 403 on deploy | Check `deployment/terraform/iam.tf` — `cicd_runner_sa` needs deployment + SA impersonation roles in the target project |
| 403 when testing Cloud Run | Default is `--no-allow-unauthenticated`; include `Authorization: Bearer $(gcloud auth print-identity-token)` header |
| Cold starts too slow | Set `min_instance_count > 0` in Cloud Run Terraform config |
| Cloud Run 503 errors | Check resource limits (memory/CPU), increase `max_instance_count`, or check container crash logs |
| 403 right after granting IAM role | IAM propagation is not instant — wait a couple of minutes before retrying. Don't keep re-granting the same role |
| Resource seems missing but Terraform created it | Run `terraform state list` to check what Terraform actually manages. Resources created via `null_resource` + `local-exec` (e.g., BQ linked datasets) won't appear in `gcloud` CLI output |
| Deployment failed or agent not responding | Check Cloud Logging: `gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=SERVICE" --project=PROJECT --limit=50 --format="table(timestamp,severity,textPayload)"` for Cloud Run, or `gcloud logging read "resource.type=aiplatform.googleapis.com/ReasoningEngine" --project=PROJECT --limit=50` for Agent Runtime |
| Agent returns errors after deploy | Open Cloud Logging in Console → filter by service name (Cloud Run) or reasoning engine resource (Agent Runtime) → look for stack traces or permission errors in recent log entries |

---

## Platform Registration

For registering deployed agents with Gemini, see `/google-agents-cli-publish`.

---

## Agent Gateway

> **Note:** There are no `agents-cli` commands for creating or managing Agent Gateways or
> Semantic Governance policies — set those up separately (via Terraform or the Cloud Console).
> `agents-cli deploy` is the only gateway-aware command, and its support is a passthrough: it
> binds the agent to an *existing* gateway as part of the Agent Runtime create/update call.

**Agent Gateway** is the networking + security entry/exit point for all agent interactions
(user↔agent, agent↔tool, agent↔agent) — it centralizes access control and governed
connectivity (ingress/egress). It is not a deployment target.

To set up a gateway, follow [Set up an Agent Gateway](https://docs.cloud.google.com/gemini-enterprise-agent-platform/govern/gateways/set-up-agent-gateway)
and [Route Agent Runtime traffic through Agent Gateway](https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/runtime/agent-gateway-runtime-deploy). You may also manage the gateway in Terraform with [`google_network_services_agent_gateway`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/network_services_agent_gateway) (currently in the `google-beta` provider).

Once a gateway exists, `agents-cli deploy` binds an agent to it with `--agent-gateway-egress`
and/or `--agent-gateway-ingress`, each taking a full resource name
(`projects/PROJECT/locations/REGION/agentGateways/GATEWAY`). Only Agent Runtime deployment is
supported, and the agent must have Agent Identity.

An egress gateway performs TLS decryption and inspection on outbound agent communications,
so the image must trust the gateway's root CA. That setup is opt-in at scaffold time. If you're
creating a new project, pass `--agent-gateway` flag to `agents-cli create`:

```bash
agents-cli create my-agent --agent adk -d agent_runtime --agent-gateway
```

Alternatively, you can pass the same flag to `agents-cli scaffold enhance` to upgrade an
existing project:
```bash
agents-cli scaffold enhance . --agent-gateway
```

Either writes a Dockerfile that consumes the `AGENT_GATEWAY_ROOT_CERTIFICATES` build arg the
platform injects, and records `agent_gateway: true` under `create_params` so `scaffold upgrade`
keeps it. Deploying with `--agent-gateway-egress` against a Dockerfile that lacks the build arg
fails with a pointer to the command above; `--no-agent-gateway` removes the setup again.

Background: [Agent Gateway overview](https://docs.cloud.google.com/gemini-enterprise-agent-platform/govern/gateways/agent-gateway-overview).

## Customer-managed encryption (CMEK)

An Agent Runtime agent can be encrypted with your Cloud KMS key. The key is fixed at creation,
so set it where the agent is created: `deploy --key` for dev deployments, or the Terraform files
for staging/prod (`infra cicd`) and `infra single-project`; `--key` cannot add a key to an agent
Terraform created. Required key grants and the Terraform edits: `references/cmek.md`.

## Semantic Governance

**Semantic Governance Policies (SGP)** add a natural-language security/compliance layer
that keeps an agent's tool invocations aligned with user intent and organizational
constraints.

- Overview: https://docs.cloud.google.com/gemini-enterprise-agent-platform/govern/policies/semantic-governance-overview
- Configure: https://docs.cloud.google.com/gemini-enterprise-agent-platform/govern/policies/configure-semantic-governance

---

## Related Skills

- `/google-agents-cli-workflow` — Development workflow, coding guidelines, and operational rules
- `/google-agents-cli-adk-code` — ADK API quick reference for writing agent code
- `/google-agents-cli-eval` — Evaluation methodology, dataset schema, and the eval-fix loop
- `/google-agents-cli-scaffold` — Project creation and enhancement with `agents-cli scaffold create` / `scaffold enhance`
- `/google-agents-cli-observability` — Cloud Trace, logging, BigQuery Analytics, and third-party integrations
- `/google-agents-cli-publish` — Gemini registration