Skip to content

Quick start (GKE)

The installer (install.sh) bootstraps everything you need for a running Platform Agent on GKE: cluster, CRDs, IAM, secrets, Chat integration, inference gateway, and, for a GitOps repository on GitHub, the token minter. It is a front door, not a second provisioner — the engine underneath is the Terraform composition terraform/examples/full-install, which owns every GCP resource and installs the kube-agents Helm chart, which owns every Kubernetes resource.

Prefer declarative IaC over the interview? Apply the same composition by hand with your own terraform.tfvars — see the composition README for the hand-driven path. Both paths run the same Terraform, so they create the same resources.

Run the release-pinned installer (substituting <RELEASE_VERSION> with the desired release tag from GitHub Releases, for example 0.4.0; a GitLab install needs 0.9.0 or later):

Terminal window
curl -fsSL https://raw.githubusercontent.com/gke-labs/kube-agents/<RELEASE_VERSION>/install.sh | bash

The interactive installer is strongly recommended for initial installation: it detects context from your gcloud session and guides you through project selection, cluster options, and model provider credentials with minimal typing.

When running the release-pinned installer, the baked release version is used automatically.

For headless environments, AI Agents, or CI/CD automated deployments where an interactive TTY is unavailable, use --non-interactive with parameter flags:

Terminal window
curl -fsSL https://raw.githubusercontent.com/gke-labs/kube-agents/<RELEASE_VERSION>/install.sh | bash -s -- \
--non-interactive \
--gcp-project-id="my-gcp-project" \
--gke-cluster-name="platform-agent-host" \
--gcp-region="us-central1" \
--model-provider="gemini" \
--permission-set="read-only"

Give an AI coding assistant this prompt. It needs no checkout of the repository:

Install the latest official release of kube-agents (github.com/gke-labs/kube-agents) into my GCP project.
Follow INSTALL.md from that release tag — do not invent installer URLs, namespaces, or model names.
First inspect my gcloud project and existing GKE clusters and confirm the target, cluster, model provider,
and credential with me. Run install.sh with --dry-run and show me its printed summary before you change anything.
Only run the real install after I say yes.
  1. Obtain the release sources.

    Download and extract the self-contained release archive from GitHub Releases (recommended):

    Terminal window
    curl -fsSL https://github.com/gke-labs/kube-agents/releases/download/<RELEASE_VERSION>/kube-agents-<RELEASE_VERSION>.tar.gz | tar -xz
    cd kube-agents-<RELEASE_VERSION>

    Alternatively, if you require a Git checkout, clone pinned to an official release tag (for example, 0.4.0; a GitLab install needs 0.9.0 or later):

    Terminal window
    git clone --branch <RELEASE_VERSION> https://github.com/gke-labs/kube-agents.git
    cd kube-agents
  2. Run the installer. If you enable Slack, create the Slack app and its tokens first (Slack app setup): the installer asks for both tokens.

    Terminal window
    ./install.sh

    The run is interactive: it prompts for the values it needs (project ID, region, new or existing cluster, the new cluster’s shape, chat integrations, model provider and API key, GitOps repo, agent permission set, gVisor, web UI, and memory). When run from an official release checkout or bundle, install.sh automatically uses that release version and verifies that local files match the container images. Answers are saved to install.env (git-ignored, chmod 600), the install’s configuration; terraform/examples/full-install/terraform.tfvars is regenerated from it on every run, so the two stay in step. A re-run loads install.env first, so a flag you omit keeps the value it records instead of reverting to a default — with one exception: the installer never writes --github-pem-path there. After the first import the private key lives in Cloud KMS, so a re-run needs neither the flag nor the file, and you are meant to delete the .pem. The installer then runs the composition’s lifecycle.sh apply, and a re-run reconciles every change through one terraform apply — safe to Ctrl-C and run again.

  3. Configure Google Chat (only if you enabled the Google Chat integration). In the Chat API console, point the bot’s Pub/Sub connection at the created topic (projects/<PROJECT_ID>/topics/<CHAT_TOPIC_NAME>). The installer prints the exact topic name and step-by-step instructions when it finishes.

  4. Verify.

    Terminal window
    kubectl get platformagents -n kubeagents-system
    kubectl get pods -n kubeagents-system

    You should see one PlatformAgent resource plus pods for the operator (kube-agents-controller-manager), LiteLLM (litellm), and three for the Platform Agent itself: the gateway (platform-agent-gateway), the shell sandbox (platform-agent-shell), and the credential broker (platform-agent-credential-proxy). The GitHub token minter (github-token-minter) also appears if you enabled the GitHub integration. When all three agent pods are Running and Google Chat is configured, DM your Chat app and it should reply.

    The gateway logs errors while it starts up, and they do not mean the install failed. Repeated Connection refused from the Google Chat relay, and a one-off slack connect timed out after 30s, are ordering: both relays dial the credential broker before it is listening. They retry until it is, and on a fresh install the messages stopped on their own within about seven minutes. Treat them as a fault only if they are still arriving well after that, and inspect the envoy-credential-proxy container in the platform-agent-credential-proxy pod rather than the relay reporting the error.

    If you enabled Slack, check that the credential broker authenticated the bot token (Slack app setup has the full check, including a test DM):

    Terminal window
    kubectl logs deployment/platform-agent-credential-proxy -n kubeagents-system -c envoy-credential-proxy | grep -E 'Slack relay (enabled|initialization)|Slack bot token'

    Slack relay enabled workspaces=1 (one per bot token) means the bot is connected. Slack bot token authentication failed names the Slack error for a token Slack refused, and Slack relay initialization failed; retrying repeats every 30 seconds until one token authenticates. The pattern leaves out Slack relay operation failed, which is one Slack call the agent made failing later, not the connection.

    A first install runs spec.mode: next unless you passed --mode=today or mirrored its third-party images, and a next install runs more pods and reports more conditions, and with Slack as its only chat platform its Slack connects from a different pod: check it with Verify a spec.mode: next install as well.

  5. Talk to the agent.

    With Google Chat or Slack enabled, DM the app. Both are opt-in and off by default, so an install that enabled neither talks to the agent over kubectl exec instead — the installer prints these two commands when you choose “None” at its chat prompt and again when it finishes, with your cluster, region, project and namespace already filled in:

    Terminal window
    gcloud container clusters get-credentials <CLUSTER_NAME> --location <REGION> --project <PROJECT_ID> --dns-endpoint
    kubectl exec -it deployment/platform-agent-gateway -n kubeagents-system -c platform-agent -- hermes -p platform

    Drop --dns-endpoint on a cluster that publishes no externally reachable DNS endpoint, where gcloud rejects it. -p platform reaches the Platform Agent directly; a bare hermes reaches the Planning Agent front door, which is where a chat message would have landed. ChatOps covers the difference and what a chat-less install does not exercise.

A first install runs spec.mode: next unless you pass --mode=today, or mirror the third-party images with --third-party-registry-prefix, which installs today unless you pass --mode=next. next puts the A2A stack in front of the agent: the NATS bus, its auth callout, the capability verifier, the A2A console and the A2A gateway, which takes the chat backend. Common flags and toggles covers the flag and how a later run keeps the mode. These checks come after step 4’s, against the same namespace and the PlatformAgent named platform-agent.

  1. Confirm the mode.

    Terminal window
    kubectl get platformagent platform-agent -n kubeagents-system -o jsonpath='{.spec.mode}{"\n"}'

    It prints next. An empty line means today, and nothing below applies.

  2. Wait for Ready. Under next, Ready also waits for the NATS StatefulSet, the auth callout, the first completion of the bus provisioning Job and, once a chat backend is configured, the A2A gateway. A first boot can take over fifteen minutes.

    Terminal window
    kubectl wait platformagent/platform-agent -n kubeagents-system --for=condition=Ready --timeout=25m

    While it waits, the phase reads Provisioning. Once the agent’s own pod is ready, the Ready condition’s message names the A2A workload it is waiting on; before that it reads Waiting for deployment replicas to be ready. That is a slow start. phase: Degraded with reason A2AProvisionFailed is a broken one: the provisioning Job failed, and its pod log says why.

  3. Read the A2A conditions.

    Terminal window
    kubectl get platformagent platform-agent -n kubeagents-system \
    -o jsonpath='{range .status.conditions[*]}{.type}{"\t"}{.status}{"\t"}{.reason}{"\n"}{end}'

    A healthy next install shows the three rows below, and the Ready message reads Gateway, shell sandbox, credential broker, NATS, auth callout, bus provisioning and A2A gateway are all ready:

    Type Status Reason
    Ready True Reconciled
    BusProvisioned True ProvisionJobComplete
    BusCredentialsReady True CalloutServing

    A2AGateway and A2AVerifier appear only when something needs a look. A2AGateway False with NoChatBackend means no chat backend is enabled, so the operator runs no A2A gateway, and the install still reaches Ready with a message saying so; with WaitingForReplica a backend has come back and the gateway’s replica is not ready yet. A2AVerifier False with VerifierNotReady means no verifier replica is ready, and every task submitted meanwhile is refused. BusCredentialsReady False with CalloutAbsent means no auth callout is deployed and nothing can authenticate to the bus. With CalloutUnavailable, fewer than all callout replicas are ready on the current spec, which every rollout of the callout passes through; the message gives the counts, and only with none ready does the bus refuse every new connection. The status reference has every condition.

  4. List the A2A pods. Every pod the A2A stack runs carries the label app.kubernetes.io/part-of=a2a-next, so this lists them apart from step 4’s pods:

    Terminal window
    kubectl get pods -n kubeagents-system -l app.kubernetes.io/part-of=a2a-next
    Pod How many Expected state
    platform-agent-a2a-nats-0 1 Running
    platform-agent-a2a-callout-… 2 Running
    platform-agent-a2a-verifier-… 2 Running
    platform-agent-a2a-console-… 1 Running
    platform-agent-a2a-gateway-… 1, with a chat backend only Running
    platform-agent-a2a-provision-<hash>-… 1 per run, more if it retries Completed; a failed attempt stays Error; all removed 24 hours after the Job finishes
    <profile>-<animal>-<hex> 1 per session For a /session conversation, Running until 30 minutes after its last message, and for 4 hours at most; for a delegate message, Running while its one task runs

    The A2A gateway creates a session pod only for a conversation switched to /session or a message that starts with delegate; other tasks run in the agent pod. It runs up to spec.harness.tuning.maxSessions (10 by default) at once, and a fresh install has none.

  5. Check the agent’s bridge. The operator adds a hermes-bridge container to the agent pod once the bus is provisioned, so the platform-agent-gateway pod is replaced once during a first boot. The bridge takes tasks off the bus and runs them on the agent’s API server.

    Terminal window
    kubectl get deployment platform-agent-gateway -n kubeagents-system -o jsonpath='{.spec.template.spec.containers[*].name}{"\n"}'
    kubectl logs deployment/platform-agent-gateway -n kubeagents-system -c hermes-bridge | grep 'hermes bridge consuming'

    The first command lists hermes-bridge among the containers. The second prints one JSON line with "msg":"hermes bridge consuming", "profile":"platform" and "executor":"api". "executor":"cli" means the operator sets A2A_BRIDGE_EXECUTOR=cli, and the bridge runs each task as its own hermes chat process instead. A PlatformAgent that declares its own hermes-bridge sidecar in spec.deployment.sidecars keeps it, the operator adds none, and that sidecar’s executor is whatever its own environment selects.

  6. Check Slack, if you enabled it. With Slack as the only chat platform enabled, the A2A gateway holds Slack’s Socket Mode connection and the credential broker’s Slack relay from step 4 does not run. First confirm which backend the gateway took:

    Terminal window
    kubectl logs deployment/platform-agent-a2a-gateway -n kubeagents-system -c gateway | grep '"msg":"a2a gateway starting"'

    "backend":"slack" means the gateway holds Slack; check the connection with the next steps in Slack app setup. "backend":"gchat" means Google Chat is enabled too and holds the gateway: Slack is then answered by the Hermes Slack platform, as under today, and step 4’s check applies instead. A Slack failure under next shows only in the gateway’s log, never in the PlatformAgent’s .status: a refused token logs chat backend stopped; restarting it, the console stays up and the gateway retries with a backoff, while the pod stays Running.

Startup reads differently under next. On a first boot the operator creates the A2A gateway only once an auth callout replica is serving, and adds the bridge only once the provisioning Job has completed, so for the first several minutes there is no gateway pod and the agent pod has no hermes-bridge container; the Ready message names what is still missing. The A2A gateway reads the Slack tokens from the install’s Secret and dials Slack itself, so the one-off slack connect timed out after 30s from step 4 is not expected under next. If it does appear, the Hermes Slack platform is running, which under next means Google Chat is enabled as well.

The installer collected your answers, wrote install.env and terraform.tfvars, and ran one terraform apply through the composition’s lifecycle.sh — GCP APIs, the GKE cluster (CMEK, Workload Identity, the host-discovery label), IAM and Workload Identity bindings, cert-manager, the drift detector’s Log Router sink, Pub/Sub topic and pull subscription, and the Helm chart carrying the operator, the PlatformAgent CR, the credentials Secret, and the LiteLLM gateway. Terraform state lives in a versioned GCS bucket (<project>-kube-agents-tfstate, prefix kube-agents/<cluster>), so uninstall.sh and upgrade.sh can find the install from a fresh clone.

When adopting an existing cluster, four settings stay gcloud pre-steps because a Terraform data source cannot express them: CMEK database encryption, Workload Identity pool enablement, node-pool migration to GKE_METADATA (requires --migrate-node-pools), and legacy Calico NetworkPolicy enforcement (requires --enable-network-policy; --accept-no-network-policy installs without it instead and changes nothing on the cluster). On freshly created clusters, setting the managed OpenTelemetry collection scope stays a gcloud post-step (no provider field exists). The one-shot GitHub App key import into KMS also runs outside Terraform, so the PEM never enters state.

Changing your mind later is the same engine: running the installer with --menu (e.g. ./install.sh --menu or $HOME/kube-agents/install.sh --menu) opens a Day-2 control panel whose Save & Apply regenerates terraform.tfvars and re-applies.

  • --generate-only — writes install.env (when absent) and terraform.tfvars, runs pre-apply validation checks, prints out-of-Terraform prerequisites and lifecycle.sh commands, and exits 0 without applying. Those checks include the existing-cluster consent gates, so adopting a cluster that needs --migrate-node-pools, or one that enforces no NetworkPolicy and was given neither --enable-network-policy nor --accept-no-network-policy, exits 1 instead; INSTALL.md is canonical for the mode.
  • --dry-run — always runs terraform validate, and adds a full terraform plan preview when Application Default Credentials exist (on an adopted cluster lacking Workload Identity, or lacking NetworkPolicy enforcement without --accept-no-network-policy, the plan is skipped with remediation commands); nothing is created, and state stays local. It writes no install.env: a dry run provisions nothing, so it has no install to record.
  • --gke-cluster-mode=autopilot|standard — the shape of a cluster this run creates; autopilot unless you say otherwise, and the interactive installer asks when it is about to create one. Autopilot clusters are regional, and what a zonal --gcp-region does depends on who chose Autopilot: passing --gke-cluster-mode=autopilot explicitly aborts at the interview, while leaving the flag unset builds Standard instead, since nothing asked for Autopilot there. An invalid mode value aborts the run whatever the target is. The chosen shape has no bearing on a cluster that already exists: the installer writes that cluster’s probed shape instead, for the reason scripts/installer/README.md gives.
  • --migrate-node-pools — authorizes migrating existing node pools using the legacy GCE metadata server to GKE_METADATA when adopting a cluster (recreates nodes and restarts workloads; required on clusters with legacy pools, else the install aborts).
  • --enable-network-policy — authorizes enabling the legacy Calico NetworkPolicy addon and node enforcement when adopting a GKE Standard cluster without Dataplane V2 (may recreate nodes and restart workloads). One of two answers for such a cluster; with neither, the install aborts unchanged.
  • --accept-no-network-policy — the other answer: install onto that cluster without modifying it. Every NetworkPolicy the install ships is then inert, the agent sandbox’s included, and the choice is recorded in the install report and on the PlatformAgent. Installing without NetworkPolicy enforcement says exactly what that costs.
  • --enable-gvisor=false — runs the agent on the standard container runtime. The default is true: the agent executes model-authored commands, so it runs under the gvisor RuntimeClass unless you say otherwise. Autopilot, the default shape, ships that RuntimeClass and needs no node pool, from GKE 1.27.4-gke.800 onwards — the installer stops before applying anything if an Autopilot cluster it is adopting is older than that. On a Standard cluster the sandbox needs a node pool of its own, which the run provisions (the gke-cluster module’s enable_gvisor_node_pool).
  • --enable-google-chat — provisions the Google Chat Pub/Sub backend and enables the CR’s googleChat integration.
  • --enable-slack — enables the CR’s slack integration. It needs --slack-bot-token and --slack-app-token; a non-interactive run missing either aborts before applying, since the relay cannot open a socket without both. --slack-allowed-users (Slack member IDs such as U0123ABCD, not emails), --slack-home-channel, and --slack-home-channel-name are optional, and an empty allowlist allows everyone in the workspace. Create the app and its tokens first: Slack app setup.
  • On a later run, the chat flags must agree with install.env. upgrade.sh renders Slack and Google Chat from that file and takes no chat flag, so install.sh refuses --enable-slack, --enable-google-chat or a --slack-*, --google-chat-allowed-users, --google-chat-home-channel, --google-chat-mode or --chat-topic-name value that disagrees with a key the file sets; change the key instead. A key the file does not set is recorded there with the value the run applies, except the Slack tokens, which later runs recover from the cluster. scripts/installer/README.md has the full rule.
  • --agent-namespace=NAME — the namespace the agent and operator are installed into; kubeagents-system unless you say otherwise.
  • --mode=next|today — the PlatformAgent’s spec.mode; next on a first install unless you say otherwise, which puts the NATS bus and the A2A gateway in front of the agent. today is the previous stack, kept as a rollback for one release. A first install that mirrors its third-party images with --third-party-registry-prefix gets today unless you pass --mode=next, because nothing in the install points next’s NATS images at the mirror. When neither --mode nor install.env names a mode, a cluster that already runs kube-agents keeps the mode it runs, today included. The first install records the mode in install.env as PLATFORM_AGENT_MODE; with an install.env you wrote yourself, --mode appends the key when the file sets none. To switch a running install, edit that key and re-run install.sh or a full upgrade.sh, which say it is a mode switch before they apply it; a --mode that disagrees with the file is refused, because the next run would switch back. scripts/installer/README.md is canonical for the key.
  • Under next (a first install’s default), Slack is answered by the A2A gateway only when it is the one chat platform enabled. With Google Chat enabled too, Google Chat holds the gateway and Slack stays on the Hermes Slack platform. The interactive installer’s chat menu pre-selects Google Chat on a first install, so choose Slack (Socket Mode App) there, or pass --enable-slack without --enable-google-chat; on an install whose install.env already records GOOGLE_CHAT_ENABLED=true, set it to false there instead. The A2A gateway also serves one Slack workspace: pass one bot token in --slack-bot-token. With a comma-separated list the gateway fails to authenticate, Slack goes unanswered, and only the gateway pod’s log says why.
  • --enable-gke-backup-plan — provisions the GKE Backup plan for the cluster. Left unset, the composition’s own default decides.
  • --enable-drift-detector=false — installs without out-of-band change detection. The default is true: a cluster reconciled from a GitOps repository is one where a change made outside that repository is worth reporting, and nothing else reports it. Turning it on costs three GCP resources in the install’s project — a Log Router sink, a Pub/Sub topic and a pull subscription — and a sink in each project a declared scope lists, and it reaches past the clusters this install manages: the sinks export the admin-activity audit records of every GKE cluster in those projects, including clusters this install does not manage, and the subscription retains a copy of them for 31 days. On this first install the flag sticks: install.sh writes install.env and records the value you passed. It never rewrites that key afterwards, so on a later run the same flag applies to that run alone — which is why the durable opt-out is an ENABLE_DRIFT_DETECTOR=false line in install.env. spec.harness.driftDetector has the rest: what the detector reports, what it drops, and why the file is the place to opt out.
  • --gitops-org=ORG, --gitops-repo=REPO, --github-app-id=ID, --github-pem-path=PATH — configures GitOps PR automation via the in-cluster GitHub token minter (Minty). When --github-pem-path is provided, install.sh imports the private key into Cloud KMS during install; alternatively, pre-provision the key AOT beforehand (see Token minter).
  • --gitops-forge=gitlab, --gitops-host=HOST, --gitlab-token-file=PATH, --gitops-ca-file=PATH — connects a GitOps project on gitlab.com or a self-managed GitLab instead, with no token minter. --gitops-ca-file gives the private CA that signed a self-managed instance’s certificate. See GitLab as the GitOps forge.
  • --gitops-host=HOST, --gitops-ca-file=PATH with the GitHub flags above — connects a GitOps repository on a GitHub Enterprise Server, with the GitHub App created on the instance. See GitHub Enterprise Server in INSTALL.md.
  • --memory=file|hindsight|off — the agent’s long-term memory store; hindsight deploys the Hindsight API and Postgres into the cluster.

Run the installer with --help (e.g. ./install.sh --help) for the full list.

Some operator behaviour is set by environment variables on the operator’s controller manager, such as A2A_NATS_IMAGE and A2A_PROVISION_IMAGE, the images a mirrored spec.mode: next install points at its own registry. install.sh has no flag for them. Set them in the chart’s operator.extraEnv list, which is appended after the variables the chart renders and so wins over them. On an install made with install.sh, put the list in a TF_VAR_extra_helm_values line in install.env, which every later install.sh and upgrade.sh run reads:

Terminal window
TF_VAR_extra_helm_values='{operator={extraEnv=[{name="A2A_NATS_IMAGE",value="<REGISTRY>/nats:<TAG>"},{name="A2A_PROVISION_IMAGE",value="<REGISTRY>/nats-box:<TAG>"}]}}'

The value is an HCL object that Helm merges over the chart values the composition computes, so the same line can carry other chart keys; there is one such line, so put every extra value in it. Do not edit terraform/examples/full-install/terraform.tfvars for this: the installer regenerates that file on every run. Check it took after the next run:

Terminal window
kubectl get deployment kube-agents-controller-manager -n kubeagents-system -o jsonpath='{range .spec.template.spec.containers[0].env[*]}{.name}={.value}{"\n"}{end}' | grep '^A2A_'

On a hand-driven Terraform apply, set extra_helm_values in terraform.tfvars instead; on a bare Helm install, set operator.extraEnv in your values file.

Run the upgrade.sh published for the release you are moving to; it carries that version, so the run names no tag. See Upgrade for the modes, the previews, and what a run refuses.

Run ./uninstall.sh — it drives the same composition’s lifecycle.sh destroy against the state in GCS. See Uninstall for the full procedure and the asymmetries it handles.