[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M720P2559WMXBD73S6Z7ZE5D":3,"skill-same:01M720P2559WMXBD73S6Z7ZE5D":45},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":18,"linkOnly":19,"markdownContent":20,"markdownBody":21,"frontMatter":22,"refName":23,"commitSha":24,"skillDirectory":25,"forksCount":26,"repositoryHtmlUrl":27,"repositoryHomepageUrl":28,"ownerHtmlUrl":29,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":30,"install":31,"resources":44},"01M720P2559WMXBD73S6Z7ZE5D","install-kube-agents",null,"Install, set up, or deploy kube-agents (the Kubernetes Agentic Harness) and its Platform Agent onto a GKE cluster in a GCP project, interactively or non-interactively. Use when asked to install, bootstrap, or onboard kube-agents, or to plan one with a dry run.","gke-labs\u002Fkube-agents","gke-labs","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F197140281?v=4","01M720MEZR8K6WD2WTHNQE346Q","01M720MF0BRA9RGH1QVT4J0M0E",126,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fgke-labs\u002Fkube-agents\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Finstall-kube-agents\u002FSKILL.md",[],[],"2026-11-10 18:39:20",false,"---\nname: install-kube-agents\ndescription: Install, set up, or deploy kube-agents (the Kubernetes Agentic Harness) and its Platform Agent onto a GKE cluster in a GCP project, interactively or non-interactively. Use when asked to install, bootstrap, or onboard kube-agents, or to plan one with a dry run.\n---\n\n# `install-kube-agents` Skill\n\nInstall `kube-agents` for an operator: detect their environment and interview them for the choices\nthat shape the install, preview it with `--dry-run`, and apply only after they confirm. Follow\n[Install workflow](#install-workflow).\n\n## What `install.sh` actually does\n\nIt is a front-end, not a second provisioner. It loads `install.env` (the install's\nhand-authored configuration), collects anything still missing, generates\n`terraform\u002Fexamples\u002Ffull-install\u002Fterraform.tfvars` from the result, and then runs the composition's\n`lifecycle.sh apply` — the Terraform root in\n[`terraform\u002Fexamples\u002Ffull-install\u002F`](..\u002F..\u002F..\u002Fterraform\u002Fexamples\u002Ffull-install\u002FREADME.md) owns every\nGCP resource and installs the Helm chart (`charts\u002Fkube-agents`) that owns every Kubernetes one.\nThe chart installs the operator and the `PlatformAgent` into the `kubeagents-system` namespace\n(`DEFAULT_NAMESPACE`; `--agent-namespace` overrides it).\nTerraform state goes to a GCS bucket (`\u003Cproject>-kube-agents-tfstate`, versioned, prefix\n`kube-agents\u002F\u003Ccluster>`), so `uninstall.sh` and `upgrade.sh` can find the install from a fresh\nclone. The installer sources\n[`scripts\u002Finstaller\u002Finstaller_common.sh`](..\u002F..\u002F..\u002Fscripts\u002Finstaller\u002FREADME.md) before its\nfirst prompt, so its defaults and accepted values are the ones defined there; that file is where a\ndefault changes.\n\nOrder of operations: resolve the image\u002Fsource ref → check CLI prerequisites (including\n`terraform`, which it offers to install; `make` is not needed) → put the repository on disk and\nverify it against that ref → load `install.env` → interview for what is missing → generate\n`terraform.tfvars` → refuse a service account another install in the project owns\n(`check_service_account_ownership`, before the summary and the dry-run exit) → run\n`lifecycle.sh apply`. The source check happens **before** the interview, so a bad ref fails in\nseconds rather than after a dozen answers. Some steps stay `gcloud` calls outside the apply — before\nit, CMEK, the Workload Identity pool and NetworkPolicy enforcement on a pre-existing cluster; after\nit, the managed-OTel scope on a cluster it created — and the GitHub App PEM import runs through\nthe Minty CLI so the key never enters Terraform state. Re-running the installer (or its `--menu`\nDay-2 panel's Save & Apply) reconciles every change through one `terraform apply`.\n\n## Install workflow\n\nRun the three stages in order. Stages 1 and 2 create and change nothing in the project or cluster;\nnothing is applied until the operator confirms in stage 3. Pass `--non-interactive` to every\n`install.sh` call: a tool call has no terminal for the installer's `\u002Fdev\u002Ftty` prompts, so the\ninterview happens in chat instead.\n\nAfter every `install.sh` call, read the exit code before `\u002Ftmp\u002Fkube-agents-install-report.json`.\nSeveral exits (the service-account ownership check, input validation, a missing tool under\n`--dry-run`, the GitHub App PEM import) write no report and leave an earlier run's in place.\n\nResolve the latest stable release tag from [GitHub Releases](https:\u002F\u002Fgithub.com\u002Fgke-labs\u002Fkube-agents\u002Freleases)\n(e.g. `0.4.0`; a GitLab install needs `0.9.0` or later) first, and substitute it for `\u003CRELEASE_VERSION>` in every command you run or hand\nover; never leave the placeholder unrendered.\n\n### Stage 1: Detect and interview\n\nRead the environment; change nothing:\n\n```bash\ngcloud config get-value project\ngcloud config get-value compute\u002Fregion\ngcloud auth application-default print-access-token >\u002Fdev\u002Fnull && echo \"ADC: ok\"\ngcloud container clusters list --project \u003CPROJECT_ID>\ngcloud storage ls gs:\u002F\u002F\u003CPROJECT_ID>-kube-agents-tfstate\u002Fkube-agents\u002F\n```\n\nNo `ADC: ok` means no Application Default Credentials. Stage 3 cannot run without them unless one\nof the google provider's credential variables is set — `GOOGLE_OAUTH_ACCESS_TOKEN`,\n`GOOGLE_CREDENTIALS`, `GOOGLE_CLOUD_KEYFILE_JSON` or `GCLOUD_KEYFILE_JSON`, which Terraform reads\nbefore ADC (stage 2 says why). With none of them set, have the operator run\n`gcloud auth application-default login` now.\n\nA prefix in that bucket means an install already exists in the project, and so does an\n`install.env` where the installer looks for one: `$KUBE_AGENTS_INSTALL_ENV`, beside `install.sh`,\nthe current directory, or `$HOME\u002Fkube-agents\u002F` (where the `curl | bash` form clones). Ask whether\nthe operator wants [`upgrade-kube-agents`](..\u002Fupgrade-kube-agents\u002FSKILL.md) instead. A re-run reads\nthat `install.env`; [`scripts\u002Finstaller\u002FREADME.md`](..\u002F..\u002F..\u002Fscripts\u002Finstaller\u002FREADME.md) owns the\nprecedence between it, flags and defaults.\n\nPropose the detected value or the default for each decision below, and have the operator confirm or\nchange it. Never ask for a secret in chat: the installer reads each one from the environment it runs\nin, where the operator sets it.\n\n1. **Project and region** — `--gcp-project-id`, `--gcp-region`. Defaults: the active `gcloud`\n   project, and its `compute\u002Fregion` or, when that is unset, `DEFAULT_REGION`.\n2. **Cluster** — create one (`--gke-cluster-name`, default `DEFAULT_CLUSTER_NAME`; Autopilot unless\n   `--gke-cluster-mode=standard` or a zonal region) or install onto one from the list. An existing\n   cluster can need changes; stage 2 lists them.\n3. **Model provider** — `--model-provider`. `vertex_ai` authenticates through Workload Identity and\n   needs no API key; `gemini`, `anthropic` and `openai` read `GEMINI_API_KEY`, `ANTHROPIC_API_KEY`\n   or `OPENAI_API_KEY`, and `gemini` also finds a key stored in Secret Manager. The default is\n   `DEFAULT_MODEL_PROVIDER`; pass the flag for any other choice. `openai_compatible` points the\n   gateway at the operator's own OpenAI-compatible server and has no defaults: ask for the served\n   model name (`--model-default-name`), the base URL with `\u002Fv1` (`--openai-compatible-api-base`),\n   and the server pod's port for an in-cluster Service (`--openai-compatible-target-port`) or the\n   CIDRs of a private address (`--openai-compatible-egress-cidrs`). The key\n   (`OPENAI_COMPATIBLE_API_KEY`) and a CA Secret for a private CA are optional. `sap` (SAP AI\n   Core) and any other LiteLLM provider slug read their credentials from a Secret the operator\n   creates in the agent's namespace and names with `--litellm-env-secret` (never ask for the\n   service key itself); a slug outside the menu also needs `--model-default-name`. The same flag\n   names the Secret holding an `openai_compatible` gateway's OAuth2 client credentials.\n4. **How the operator reaches the agent** — a terminal when no chat flag is passed (see\n   [Handing over a chat-less install](#handing-over-a-chat-less-install)), `--enable-google-chat`,\n   or `--enable-slack` (tokens in `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN`).\n   `--enable-web-console` adds a browser chat page for chat-less installs, reached by port-forward\n   ([Web console](..\u002F..\u002F..\u002Fdocs\u002Fsite\u002Fsrc\u002Fcontent\u002Fdocs\u002Fdeploy\u002Fweb-console.md)).\n   `--enable-hermes-dashboard=true` adds the Hermes Web UI, which a port-forward cannot reach under\n   gVisor; the\n   [PlatformAgent reference](..\u002F..\u002F..\u002Fdocs\u002Fsite\u002Fsrc\u002Fcontent\u002Fdocs\u002Foperator\u002Fplatformagent-crd.md)\n   gives its access path.\n5. **Where the GitOps repository lives**, only if the operator wants GitOps pull requests. Ask\n   which forge holds it. On GitHub, pass `--gitops-org`, `--gitops-repo` and `--github-app-id`; see\n   [GitOps Repository & GitHub Token Minter Configuration](#gitops-repository--github-token-minter-configuration).\n   On GitLab (gitlab.com or a self-managed instance), pass `--gitops-forge=gitlab`, `--gitops-repo`\n   and, for a self-managed instance, `--gitops-host`; see\n   [GitOps Repository on GitLab](#gitops-repository-on-gitlab). Never combine the GitHub App flags\n   with `--gitops-forge=gitlab`: the installer refuses them. On a GitHub Enterprise Server, pass the\n   GitHub flags and `--gitops-host` (and `--gitops-ca-file` for a private CA); see\n   [GitHub Enterprise Server](#github-enterprise-server).\n6. **Whether scale-up failures get investigated** — `--enable-stockout-investigator` (default\n   `false`; it also turns on the Pub\u002FSub adapter). Always offer it. On, the cluster autoscaler's\n   `noScaleUp` logs for the install's own cluster reach the Platform Agent, which diagnoses the\n   Pending workload (quota, ComputeClass fallbacks, capacity advice) and opens a remediation pull\n   request. Off, an unschedulable pod is still triaged from its `FailedScheduling` events, but\n   nothing reads the autoscaler's verdicts or proposes a fix. It adds a Log Router sink, a Pub\u002FSub\n   topic and subscription, and grants (publisher for the sink, subscriber and viewer for the\n   agent); setting `ENABLE_STOCKOUT_INVESTIGATOR=false` in `install.env` removes them on the next\n   run. A pull request needs the GitOps repository from decision 5, and a chat post needs a home\n   channel (`--google-chat-home-channel` or `--slack-home-channel`): under `next`, a proactive post\n   with no home channel is refused and kept for a digest (or goes to Slack when both platforms are on); the gateway logs the refusal and the `PlatformAgent` carries\n   `HomeChannelUnset`.\n\nLeave `--permission-set` at `read-only` unless the operator asks for `custom`. The flags in\n[Adopting a cluster the operator already owns](#adopting-a-cluster-the-operator-already-owns) are\nthe operator's decision, never a default you fill in.\n\n### Stage 2: Dry run\n\nRun the installer with the agreed flags and `--dry-run`. It validates the Terraform configuration\nand, when Application Default Credentials exist, runs `terraform plan` against local state; it\ncreates nothing, the state bucket included:\n\n```bash\ncurl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Fgke-labs\u002Fkube-agents\u002F\u003CRELEASE_VERSION>\u002Finstall.sh | bash -s -- \\\n  --dry-run \\\n  --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\" \\\n  --model-provider=\"gemini\" \\\n  --permission-set=\"read-only\"\n```\n\nA dry run regenerates `terraform.tfvars`, so back up a real deployment's copy first. It writes no\n`install.env`: a dry run provisions nothing, so it has no install to record, and an existing one is\nnever rewritten. It also installs no missing tool — a machine without any installer prerequisite\n(`terraform`, `helm`, `gh`, `gke-gcloud-auth-plugin` and the rest) fails it with\n`Dry-run validation will not install missing tools`, naming the tool; install it and re-run.\n\nGo through the output with the operator:\n\n- `DRY_RUN_SUCCESS` in `\u002Ftmp\u002Fkube-agents-install-report.json` does not clear an existing cluster.\n  The dry run exits before the consent gates, so it never reports `REFUSED_*`.\n- On an existing cluster, the `Existing Cluster Mutations (Adoption)` summary and any\n  `Dry-run: skipping terraform plan because ...` warning are the questions to put to the operator,\n  as [Adopting a cluster the operator already owns](#adopting-a-cluster-the-operator-already-owns)\n  sets out.\n- `No Application Default Credentials; skipping the resource preview` means no plan ran, and on an\n  existing cluster the NetworkPolicy and node-pool checks were skipped too, so their warnings are\n  missing. Have the operator run `gcloud auth application-default login` and repeat stage 2. Do not\n  go to stage 3 without ADC or one of the provider's credential variables\n  (`GOOGLE_OAUTH_ACCESS_TOKEN`, `GOOGLE_CREDENTIALS`, `GOOGLE_CLOUD_KEYFILE_JSON`,\n  `GCLOUD_KEYFILE_JSON`): the apply's Terraform needs one, the installer's own auth check tests\n  neither, and on an existing cluster the Workload Identity, CMEK and NetworkPolicy changes run\n  before Terraform starts — the install fails after making them. A credential variable does not\n  bring the preview back, since the dry run's plan gate checks ADC alone.\n- `No ... API key was provided` is a warning, not a failure: the install would finish with an agent\n  that cannot call its model. Resolve the key before stage 3.\n\nWhen the operator would rather run the apply themselves, use [`--generate-only`](#generate-only-mode)\ninstead: it runs the consent gates and reports `REFUSED_*` itself.\n\n### Stage 3: Confirm and apply\n\nWhen the operator chooses an adoption flag in stage 2, re-run stage 2 with it: the flag changes both\nthe adoption summary and whether `terraform plan` runs at all, so the first preview no longer shows\nwhat will run. Summarize the project, the cluster (new or existing, and each change the run makes to\nan existing one), the namespace, the model provider and how the operator reaches the agent. Wait\nfor an explicit go-ahead. Then run the last stage 2 command without `--dry-run`:\n\n```bash\ncurl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Fgke-labs\u002Fkube-agents\u002F\u003CRELEASE_VERSION>\u002Finstall.sh | bash -s -- \\\n  --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\" \\\n  --model-provider=\"gemini\" \\\n  --permission-set=\"read-only\"\n```\n\n- Non-zero exit: the install did not finish. Relay the error the installer printed. A `REFUSED_*`\n  status leaves the cluster unchanged — relay the options and stop.\n- `SUCCESS`, or `SUCCESS_PENDING_ROLLOUT` (applied, but a deployment had not reported ready; relay\n  the `kubectl rollout status` command the installer printed): relay how to reach the agent — the\n  terminal commands in [Handing over a chat-less install](#handing-over-a-chat-less-install) (including\n  the `kubectl port-forward` command for the web console when `--enable-web-console` was enabled),\n  which also fetch credentials, or the chat platform the operator enabled — then confirm the pods with\n  `kubectl get pods -n kubeagents-system`. Relay `network_policy_enforcement` when it is not\n  `enforced` ([Machine-Readable Results](#machine-readable-results)).\n\nThe `kubectl` commands in this skill assume the default namespace; under `--agent-namespace`,\nsubstitute its value.\n\n### Local sources and pre-confirmed runs\n\nThe same invocation runs from the official release bundle (recommended) or a checkout pinned to the\nrelease tag; add `--dry-run` for stage 2 exactly as above. Run an apply without stages 1 and 2 only\nwhen the operator has already confirmed these exact flags, or in CI.\n\n```bash\ncurl -fsSL https:\u002F\u002Fgithub.com\u002Fgke-labs\u002Fkube-agents\u002Freleases\u002Fdownload\u002F\u003CRELEASE_VERSION>\u002Fkube-agents-\u003CRELEASE_VERSION>.tar.gz | tar -xz\ncd kube-agents-\u003CRELEASE_VERSION>\n.\u002Finstall.sh --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\" \\\n  --model-provider=\"gemini\" \\\n  --permission-set=\"read-only\"\n```\n\nIf a Git checkout is required, clone pinned to the release tag:\n\n```bash\ngit clone --branch \u003CRELEASE_VERSION> https:\u002F\u002Fgithub.com\u002Fgke-labs\u002Fkube-agents.git\ncd kube-agents\n.\u002Finstall.sh --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\" \\\n  --model-provider=\"gemini\" \\\n  --permission-set=\"read-only\"\n```\n\nDo not clone `main` to deploy an official release: manifests and CRD schemas on `main` evolve continuously and diverge from released container images. Running install scripts against a mismatched checkout will fail `verify_local_source_ref` to prevent deploying incompatible manifests.\n\n## Generate-Only Mode\n\nTo generate configuration files (`install.env` and `terraform.tfvars`), run pre-apply validation checks, and hand off the apply to the operator without creating or mutating cloud resources, use `--generate-only`:\n\n```bash\ncurl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Fgke-labs\u002Fkube-agents\u002F\u003CRELEASE_VERSION>\u002Finstall.sh | bash -s -- \\\n  --generate-only \\\n  --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\"\n```\n\nIn `--generate-only` mode, the installer:\n\n1. Writes `install.env` (if absent) and `terraform\u002Fexamples\u002Ffull-install\u002Fterraform.tfvars`. One refusal lives inside this step rather than after it: if the cluster already exists, nothing stated a memory mode (no `--memory=`, no `MEMORY` in `install.env`), and the run cannot ask the cluster whether it is running the Hindsight memory store, the generator stops instead of defaulting to a value whose apply would delete the store's database. That applies under `--generate-only` and `--dry-run` as well, because both write the same real `terraform.tfvars` and `--generate-only` exists to hand it to `lifecycle.sh apply`. It exits 1 having written no `terraform.tfvars` at all — the opposite of the step-2 refusals below. Pass `--memory=hindsight|file|off`, record `MEMORY` in `install.env`, or restore access to the cluster.\n2. Runs the same pre-apply validation checks a real run does: the GitOps organization check, the service-account ownership check, the scope container preflight when a folder or organisation is declared, which on this route, and on the interactive `g` answer, only warns (the apply it hands off may run as another identity; a run that applies, `-y` or the interactive `Y`, refuses, exit 1 with no report status like the ownership check, when the credentials Terraform applies with cannot set IAM policy on a container or an enforced organisation policy forbids the Cloud Asset API), the scope export sink preflight when the drift ingress is on and `SCOPE_PROJECTS` lists a project the drift sinks will cover, which warns and refuses on the same routes when those credentials cannot create a Log Router sink (`logging.sinks.create`) in one of them (a project whose sink the install's Terraform state already holds warns instead when the credentials can read it), and the existing-cluster node-pool and NetworkPolicy consent gates, but not the live-scope check, which needs the install's kubeconfig context and runs only on a run that will apply; the handoff says so. The interactive `g` answer is given after the pre-flight summary, by which point that check has run too, so the two routes differ in that one check. A cluster needing `--migrate-node-pools`, or one enforcing no NetworkPolicy that was given neither `--enable-network-policy` nor `--accept-no-network-policy` (or that combines `--enable-web-console` with `--accept-no-network-policy`), is refused (`REFUSED_MISSING_NODE_POOL_MIGRATION`, `REFUSED_MISSING_NETWORK_POLICY`, `REFUSED_WEB_CONSOLE_WITHOUT_NETWORK_POLICY`), as is one that cannot be described (`FAILED_PREFLIGHT_CLUSTER_UNREADABLE`). A `REFUSED_*` status is a question for the operator, not a flag to add: see [Adopting a cluster the operator already owns](#adopting-a-cluster-the-operator-already-owns). Step 1 has already written both files by then, so a refusal exits 1 leaving `install.env` and `terraform.tfvars` on disk — unvalidated, and with no handoff printed. Do not read the presence of `terraform.tfvars` as success; read the report status.\n3. Prints a checklist of out-of-Terraform prerequisites (CMEK database encryption, Workload Identity, NetworkPolicy, GitHub App PEM import, OTel scope, and, with Google Chat or Slack on, the chat setup checklist commands to run after the apply) and the `lifecycle.sh apply` command with remote state variables (`KUBE_AGENTS_STATE_BUCKET` and `KUBE_AGENTS_STATE_PREFIX`).\n4. Exits 0 with status `GENERATE_ONLY_SUCCESS` in `\u002Ftmp\u002Fkube-agents-install-report.json`, or exits 1 with the `REFUSED_*` \u002F `FAILED_PREFLIGHT_*` status from step 2.\n\nOn a GitLab install, step 2 skips the GitOps organization check: there is no GitHub organization.\nGenerate-only never creates the Secrets (the token Secret, and the CA Secret for a private CA). On a\nfirst install it writes the GitLab keys into `install.env`. Over an existing `install.env` it does\nnot change them: have the operator set `GITOPS_FORGE`, `GITOPS_HOST`, `GITOPS_REPO`,\n`GITLAB_TOKEN_SECRET` and, for a private CA, `GITOPS_CA_SECRET` there before\n`lifecycle.sh apply`. Otherwise the next `upgrade.sh` removes the GitLab forge. After\n`lifecycle.sh apply`, the person who runs the install creates the Secrets by hand (Terraform only\nnames them), as\n[Dry run and generate-only](https:\u002F\u002Fgke-labs.github.io\u002Fkube-agents\u002Finstall\u002Fgitlab\u002F#dry-run-and-generate-only)\nshows.\n\nThe interactive wizard also offers the same choice by answering `g` at the final confirmation step.\n\n## Adopting a cluster the operator already owns\n\nInstalling onto a cluster somebody else made can require changing that cluster, and **modifying a\ncluster the operator already owns is never a decision you make on your own.** The pre-flight\nsummary (`Existing Cluster Mutations (Adoption)`, also printed by `--dry-run`) lists every change\nthe run would make; on an agent-driven run there is no interactive prompt, so the flags are the\nconsent and you must obtain it before passing one. Present each pending change with its cost and ask:\n\n- **Workload Identity pool** — enabled on the control plane without a flag when missing.\n  Non-revertible. Say so before running a real install.\n- **CMEK database encryption** — enabled without a flag when missing: a Cloud KMS key ring and key\n  (permanent), the Cloud KMS API, and a control-plane update. `ALLOW_UNENCRYPTED_SECRETS=true`\n  skips it. Say so before running a real install.\n- **Legacy node pools** (`REFUSED_MISSING_NODE_POOL_MIGRATION`) — two answers: `--migrate-node-pools`\n  recreates every node on those pools and restarts the workloads on them, kube-agents' or not; or\n  stop. There is no install without Workload Identity.\n- **No NetworkPolicy enforcement** (`REFUSED_MISSING_NETWORK_POLICY`) — three answers (or when\n  `--enable-web-console` is enabled, `--enable-network-policy`, `--enable-web-console=false` paired\n  with `--accept-no-network-policy`, or stop, since the web console requires NetworkPolicy enforcement\n  and cannot be combined with `--accept-no-network-policy`), and you present the applicable ones:\n  1. `--enable-network-policy` enables the legacy Calico addon: a control-plane update that may\n     recreate node pools and restart workloads unrelated to kube-agents.\n  2. `--accept-no-network-policy` installs without enforcement (unavailable when `--enable-web-console`\n     is enabled unless `--enable-web-console=false` is also passed to turn the web console off). The cluster is not modified. Every\n     NetworkPolicy kube-agents ships is inert: the agent pod's egress confinement, the shell\n     sandbox's deny-all (the sandbox is where model-authored commands run), and the LiteLLM,\n     minter and Hindsight policies. The confinement lost is kube-agents'\n     own, not the operator's workloads'; \"we trust the workloads in this cluster\" does not answer\n     it. Recorded in the report and on the `PlatformAgent`.\n  3. Stop. The cluster is unchanged.\n- **Web console without NetworkPolicy enforcement** (`REFUSED_WEB_CONSOLE_WITHOUT_NETWORK_POLICY`) —\n  emitted when `--enable-web-console` (`WEB_CONSOLE_ENABLED=true`) is combined with\n  `--accept-no-network-policy` (`ACCEPT_NO_NETWORK_POLICY=true`) against a cluster that still\n  enforces no NetworkPolicy. Present the operator's choices: enable NetworkPolicy enforcement\n  (`--enable-network-policy` and remove `ACCEPT_NO_NETWORK_POLICY=true` from `install.env`), disable\n  the web console (`--enable-web-console=false` \u002F `WEB_CONSOLE_ENABLED=false`), or stop.\n- **gVisor node pool** — on an existing Standard cluster `--enable-gvisor=true` (the default) adds a\n  billable `gvisor-pool` node pool. Say so; `--enable-gvisor=false` runs the agent unsandboxed instead.\n\nA refusal is not a failure to route around: a `REFUSED_*` status with an unchanged cluster is the\ninstaller doing its job. Report it, relay the options above, and pass a flag only when the operator\nhas chosen. When they choose `--accept-no-network-policy` and an `install.env` already exists, tell\nthem to add `ACCEPT_NO_NETWORK_POLICY=true` to it (the installer prints the same instruction): the\nnext `upgrade.sh` regenerates from that file and is refused without the key. When they later\nconfine the cluster, tell them to remove the key again; the installer warns while it lingers.\n\nA GitHub token minter that this install did not make (one from `make deploy-github`, by hand, or\nfrom another Helm release) also stops a run, with exit 1 and no `REFUSED_*` status, in two cases.\nThe run names the GitOps repository with keys that do not match the `PlatformAgent` (it prints the\nlines to set), or the run deploys the chart's own minter over the other one. In the second case,\nask the operator whether to remove the other minter or to keep it with `GITHUB_APP_ID` empty. See\n\"When an upgrade is refused\" in the upgrade page.\n\n## Source verification\n\nBefore provisioning, the installer requires the checkout holding the Terraform configuration and\nchart to be at the same commit as `--image-tag` and to have no uncommitted changes — the install\nsources and the container image must come from one revision. A dirty or mismatched checkout aborts with instructions.\n`--allow-unverified-source` (or `ALLOW_UNVERIFIED_SOURCE=true`) downgrades that to a warning; use it\nwhen iterating on the installer itself, not for a deployment you intend to keep. `--dry-run` is\nlenient already.\n\n## GCP IAM permission sets\n\n`--permission-set` chooses which GCP IAM role bundle the composition grants the agent's GSA (its\n`permission_set` variable; `custom` becomes a `project_roles` list). It does **not** affect\nKubernetes RBAC, which is read-only in every set, and it does not gate the GitOps pull-request\npath, which works in every set. See the site's\n[security and IAM reference](..\u002F..\u002F..\u002Fdocs\u002Fsite\u002Fsrc\u002Fcontent\u002Fdocs\u002Freference\u002Fsecurity-and-iam.md).\n\n| Set         | Grants                                                                           |\n| ----------- | -------------------------------------------------------------------------------- |\n| `read-only` | Auditing and observability: read roles, no write to infrastructure. **Default.** |\n| `custom`    | Exactly the roles passed in `--custom-roles`; no built-in bundle.                |\n\n## Machine-Readable Results\n\nUpon completion, `install.sh` generates a machine-readable JSON status report at `\u002Ftmp\u002Fkube-agents-install-report.json`:\n\n```json\n{\n  \"status\": \"SUCCESS\",\n  \"dry_run\": false,\n  \"generate_only\": false,\n  \"non_interactive\": true,\n  \"project_id\": \"YOUR_GCP_PROJECT_ID\",\n  \"cluster_name\": \"platform-agent-host\",\n  \"timestamp\": \"2026-08-05T03:35:00Z\"\n}\n```\n\nThe full report also carries `gvisor_enabled`, `memory_mode`, and `network_policy_enforcement`. A\nreport written before the run decided them says so: `gvisor_enabled` is `null` before the interview,\nand the other two are empty, rather than restating a default the run never applied. For\n`network_policy_enforcement` that covers a run that failed early, a `--dry-run` (it never reaches the\ncluster step; only with `--accept-no-network-policy` against a cluster that enforces nothing does it\nreport `absent-accepted`), and a `--generate-only` run under `--enable-network-policy`, which has\nnot enabled anything yet.\n`network_policy_enforcement` is `enforced` (Dataplane V2 or Calico, or a cluster this run created),\n`enabled-by-install` (this run turned Calico on, under `--enable-network-policy`), or\n`absent-accepted` (installed without enforcement, under `--accept-no-network-policy`); the last is\nalso stamped onto the `PlatformAgent` as `kubeagents.x-k8s.io\u002Fnetwork-policy-enforcement`, so it\noutlives the report. Relay it to the operator in either of the last two cases.\n\n## Handing over a chat-less install\n\nChat is opt-in and both platforms default off, and a non-interactive run with no\n`--enable-google-chat` or `--enable-slack` selects \"None\" — so the common agent-driven install\nhas no chat and the operator has no way to reach the agent unless you give them one. Under\n`spec.mode: next` the `PlatformAgent` carries the `NoChatPlatform` condition, which says where the\nfindings go instead. The installer prints the way in at the chat-configuration step and again when it finishes, under\n`--- [Talking to the Agent from a Terminal] ---`, with the cluster, region, project and namespace\nalready substituted. Relay those two commands verbatim (plus the `Web Console` `kubectl port-forward`\ncommand and `http:\u002F\u002Flocalhost:8080` URL when `--enable-web-console` was enabled):\n\n```bash\ngcloud container clusters get-credentials \u003CCLUSTER_NAME> --location \u003CREGION> --project \u003CPROJECT_ID> --dns-endpoint\nkubectl exec -it deployment\u002Fplatform-agent-gateway -n kubeagents-system -c platform-agent -- hermes -p platform\n```\n\n- Take `--dns-endpoint` from what the installer printed rather than adding it: `gcloud` rejects it\n  on a cluster publishing no externally reachable DNS endpoint, and the installer omits it there.\n- `-p platform` reaches the Platform Agent. A bare `hermes` reaches the Planning Agent front door,\n  which is where a chat message would have landed.\n- `kubectl port-forward` to the agent pod is not an alternative: the agent is sandboxed under gVisor\n  by default and the forward cannot see into the sandbox. `kubectl exec` enters it. (When\n  `--enable-web-console` is enabled, the separate web console pod does not run under gVisor and is\n  reached via the `kubectl port-forward svc\u002Fkube-agents-web-console -n kubeagents-system 8080:8080`\n  command the installer prints.)\n- Say what the operator does not get until they enable a platform: scheduled reports and\n  alert-driven triage reach chat only; meanwhile the audit reports stay in the report store on the\n  data volume and the triage runs on the board (the `NoChatPlatform` condition says so), and the first-run inventory report waits a\n  day for a human connecting over chat — read it meanwhile with\n  `kubectl exec platform-agent-shell-0 -n kubeagents-system -c shell -- cat \u002Fopt\u002Fdata\u002FINVENTORY.md`\n  (the report is on the shell sandbox pod, not the agent pod); a day after the sweep it is filed as\n  an issue labelled `agent:onboarding` in the GitOps repository when that is on GitHub (else in the\n  install's one managed GitHub repository), and a report\n  that could not be filed is retired a week after the sweep (INSTALL.md, \"What to Expect After\n  Installation\").\n\n## GitOps Repository & GitHub Token Minter Configuration\n\nWhen deploying `kube-agents` with GitOps pull-request workflows enabled, the Platform Agent creates pull requests against an infrastructure-as-code repository via the [GitHub Token Minter](https:\u002F\u002Fgithub.com\u002Fabcxyz\u002Fgithub-token-minter) (`minty`). The minter runs in-cluster and signs short-lived GitHub App installation tokens using a private key securely stored in Google Cloud KMS. (For GitLab or self-managed Gitea GitOps repositories, which use a Kubernetes Secret token instead of the GitHub App token minter, see [GitLab as the GitOps forge](..\u002F..\u002F..\u002Fdocs\u002Fsite\u002Fsrc\u002Fcontent\u002Fdocs\u002Finstall\u002Fgitlab.md) and [Gitea as the GitOps forge](..\u002F..\u002F..\u002Fdocs\u002Fsite\u002Fsrc\u002Fcontent\u002Fdocs\u002Finstall\u002Fgitea.md).)\n\n### Prerequisites\n\nThe GitHub App and its permissions, the private key, the Cloud KMS signing key and its default names, and the Go toolchain the import needs are documented once, in [Token minter](https:\u002F\u002Fgke-labs.github.io\u002Fkube-agents\u002Fdeploy\u002Ftoken-minter\u002F). Read it before running either path below. Restating those values here is how the two copies drift apart, and this file already defers the same way for flag defaults.\n\nOne input decides whether the minter can work at all, so it is worth stating where the command is: `--gitops-org` must be a GitHub **organization**. Minty resolves App installations at `\u002Forgs\u002F{org}\u002Finstallation`, which returns 404 for a personal account, and `install.sh` refuses one rather than deploying a minter that can never mint a token.\n\nThe commands below are stage 2 dry runs showing the GitOps flags. Replace the placeholders and example values with the operator's values from stage 1, review the output as stage 2 describes, and apply through stage 3 once the operator confirms — Path 1 creates a Cloud KMS key ring and key, which cannot be deleted.\n\n### Deployment Path 1: Automated Import via `install.sh`\n\nIn this path, `install.sh` automatically creates the Cloud KMS keyring\u002Fkey (if missing) and imports the GitHub App private key using the Minty CLI before Terraform applies:\n\n```bash\n.\u002Finstall.sh --dry-run --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\" \\\n  --model-provider=\"gemini\" \\\n  --gitops-org=\"YOUR_GITHUB_ORG\" \\\n  --gitops-repo=\"YOUR_GITOPS_REPO\" \\\n  --github-app-id=\"YOUR_GITHUB_APP_ID\" \\\n  --github-pem-path=\"\u002Fpath\u002Fto\u002Fapp-private-key.pem\"\n```\n\nDelete the `.pem` once the stage 3 apply succeeds. Cloud KMS keys cannot be destroyed, and a later run or upgrade finds the `ENABLED` version and skips the import.\n\n### Deployment Path 2: Pre-Provisioned \u002F Ahead-Of-Time (AOT) Key\n\nFor CI\u002FCD pipelines and anywhere runners must not handle raw private keys, the key is imported ahead of time — the procedure, and the keyring and key names `install.sh` expects, are in [Token minter](https:\u002F\u002Fgke-labs.github.io\u002Fkube-agents\u002Fdeploy\u002Ftoken-minter\u002F). Once the key holds an `ENABLED` version, invoke `install.sh` without `--github-pem-path`:\n\n```bash\n.\u002Finstall.sh --dry-run --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\" \\\n  --model-provider=\"gemini\" \\\n  --gitops-org=\"YOUR_GITHUB_ORG\" \\\n  --gitops-repo=\"YOUR_GITOPS_REPO\" \\\n  --github-app-id=\"YOUR_GITHUB_APP_ID\" \\\n  --kms-keyring=\"YOUR_KEY_RING\" \\\n  --kms-key=\"YOUR_KEY\"\n```\n\nPass both KMS flags with the names the operator pre-provisioned. A pre-provisioned key carries no owner labels, and the installer adopts one only when both names are given explicitly.\n\n### GitHub Enterprise Server\n\nIf the operator's GitOps repository is on a GitHub Enterprise Server (a GitHub instance at a host they run, not github.com), `install.sh`, `upgrade.sh` and `uninstall.sh` install, upgrade and remove it, with `--gitops-host` and, for a private CA, `--gitops-ca-file`. Follow [GitHub Enterprise Server in INSTALL.md](..\u002F..\u002F..\u002FINSTALL.md#github-enterprise-server) with them. Its steps and their order:\n\n1. Decide the cluster. A new cluster goes into the project's `default` VPC network (the installer has no network setting). If the instance is reachable only from another VPC or a Shared VPC, the operator creates the cluster in that VPC, and you install onto it with `--gke-cluster-name`.\n2. On an existing cluster, check the network path now (DNS and HTTPS on port 443) with the two `kubectl run` commands in INSTALL.md. On a new cluster, run them right after the install. A private instance needs a route, firewall rules and, for a private DNS zone, a Cloud DNS forwarding zone; those are the operator's to set up.\n3. The operator creates the GitHub App on the instance (not on github.com), with the same permissions as on github.com and no webhook, installed on the organization, the GitOps repository and each context repository, and downloads its private key. The repository must exist and belong to an organization.\n4. Run `install.sh --dry-run` with `--gitops-host=\u003Chost>`, `--gitops-org`, `--gitops-repo` (the name, not a URL), `--github-app-id`, `--github-pem-path` and, only for a private CA, `--gitops-ca-file=\u003Cregular PEM file>`. Show the operator the summary. Then run it without `--dry-run`. The installer imports the App's key into Cloud KMS before the apply, writes the CA into the Secret `github-forge-ca` after the apply, and restarts the token minter so that it reads the CA.\n5. Check the minter's `SOURCE_SYSTEM_API_BASE_URL` and the gitops-state ConfigMap's `github_host` with the commands in INSTALL.md.\n\nRules for the agent:\n\n- `--gitops-host` is a lowercase hostname with no scheme, path or port. Do not give it for github.com, except to switch an install back to github.com. The installer refuses a host under `github.com`, `githubusercontent.com`, `ghe.com` or another domain where GitHub runs its own services, and `gitlab.com`.\n- Do not give `--gitops-ca-file` when a public CA signed the certificate. Do not use `\u002Fdev\u002Fstdin` or a process substitution for it.\n- The organization check asks the instance with the CA and sends no credential. On an instance in private mode it warns that it cannot verify the organization and goes on.\n- To upgrade, run `upgrade.sh`. To remove, run `uninstall.sh`. Both read the forge from `install.env` (`GITOPS_FORGE=github`, `GITOPS_HOST`, `GITOPS_CA_SECRET`).\n- A switch between github.com and an instance needs the App on the new host and a new `--kms-key`, because `install.sh` imports a key only into a key with no `ENABLED` version. The installer refuses a change of host that keeps the recorded App or key. INSTALL.md, \"Switch between github.com and a GitHub Enterprise Server\", gives the command. `--gitops-host=github.com` switches back. After a switch, `platform-agent-gitops-state` still lists the old host's repositories (the operator never removes an entry). Remove them as INSTALL.md says in the same section; after a switch back to github.com this is required, because an entry on the instance holds the minter's policy pruning (`Degraded`, `MinterPruningHeld`).\n- With `--generate-only`, the operator creates the CA Secret and restarts the minter after `lifecycle.sh apply`; INSTALL.md gives the commands. Over an existing `install.env`, generate-only does not record the forge keys: have the operator set `GITOPS_FORGE=github`, `GITOPS_HOST` and, for a private CA, `GITOPS_CA_SECRET` there before `lifecycle.sh apply`, or the next `upgrade.sh` renders github.com.\n- When `install.env` records `GITOPS_FORGE=gitlab`, also pass `--gitops-forge=github` to switch to an instance.\n\nAsk the operator for: the instance's hostname (port 443 only), the organization and repository on it, the App ID and the path of its private key, the CA file if a private CA signed the certificate, whether the cluster exists, and which VPC reaches the instance. An install serves one GitHub host, so this install has no github.com repositories. If the operator does not want `install.sh`, INSTALL.md also has the Terraform + Helm method.\n\n## GitOps Repository on GitLab\n\nA GitLab install has no GitHub App and no token minter. The credential broker holds a GitLab access\ntoken in a Kubernetes Secret, and the agent and its shell sandbox never get it.\n[GitLab as the GitOps forge](https:\u002F\u002Fgke-labs.github.io\u002Fkube-agents\u002Finstall\u002Fgitlab\u002F) has the\nprocedure, the network, DNS and certificate rules, a reachability test, the connection check,\ntoken expiry and rotation, and the `install.env` keys. Read it before the dry run.\n\nRules for the agent:\n\n- Use release 0.9.0 or later. An earlier release has no `--gitops-forge` flag.\n- The operator creates the GitOps project first. The installer does not create it.\n- `--gitops-repo` is the project's full path with its nested groups (`group\u002Fsubgroup\u002Fproject`), or\n  its `https:\u002F\u002F` URL on that host. Put the host in `--gitops-host`, not in the path. Leave\n  `--gitops-host` out for gitlab.com.\n- The broker uses the token only for the declared repositories. Another project in the\n  same group gets no write token. `inspect-repository` reads a public project with no token, and a\n  private one only when it is a context repository and a read-only token is stored. A project outside those groups is refused. Tell the operator to use the token with the smallest reach. That is a project access\n  token on the GitOps project, or a group access token on the project's own group. For\n  `platform\u002Finfra\u002Fgitops`, that group is `platform\u002Finfra`, not `platform`. Tell the operator to\n  record the token's expiry date, and to check the token before the install, as the GitLab page\n  shows.\n- Do not ask the operator to paste the token. Do not put the token in a flag value, a variable or\n  the transcript. Use one of these:\n  - Do not give `--gitlab-token-file`. The install completes and prints the command that creates\n    the Secret, and the operator runs it. Until then, GitLab calls answer\n    `FORGE_CREDENTIAL_UNAVAILABLE`. No restart is necessary afterwards.\n  - The operator gives a file path or a password-manager command, which you pass as\n    `--gitlab-token-file=\u003C(…)`. The installer reads the whole source and removes all whitespace,\n    newlines inside it too. The source must hold only the token. For a `pass` entry with more\n    lines, use `\u003C(pass show gitlab\u002Fagent-token | head -n1)`. The operator unlocks `gpg-agent`\n    before the run. Do not use `\u002Fdev\u002Fstdin` under `curl … | bash`, because standard input is the\n    script there.\n- Only when the operator wants the agent to read a private GitLab context repository, also set\n  `GITLAB_READ_TOKEN_FILE` to a source that holds a read-only token (Reporter role,\n  `read_repository` scope only), with the same rules as the token source above. It must not be\n  the same source as `--gitlab-token-file`. Use a release whose `install.env.example` lists\n  `GITLAB_READ_TOKEN_FILE`; an older release ignores it. The GitLab page's \"Private context\n  repositories\" section has the setup.\n- Only when a private CA signed the instance's certificate, add `--gitops-ca-file` to the dry run\n  and to the stage 3 run. Do not give it for gitlab.com or for a public CA. Ask the operator\n  for the CA certificates (public; never a private key) as a regular PEM file: the root CA, and the\n  intermediate CAs if the server does not send them. Do not use `\u002Fdev\u002Fstdin` or a process\n  substitution for the CA. The installer refuses the flag without a self-managed `--gitops-host`.\n  Use a release whose `install.sh --help` lists `--gitops-ca-file`.\n- The dry run checks that the token source is readable, and stops if a regular token file is\n  empty. It stores no token, and `install.sh` never connects to GitLab. Only the stage 3 run\n  reads the token into the Secret, after the apply.\n- On a cluster that already exists, have the operator run the network-path test from the GitLab\n  page before stage 3, in the `default` namespace: `kubeagents-system` does not exist yet. On a\n  new cluster, run it after stage 3, in `kubeagents-system`. After stage 3, run the connection\n  check from the same page.\n\nThe command below is a stage 2 dry run. Apply through stage 3 once the operator confirms:\n\n```bash\n.\u002Finstall.sh --dry-run --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\" \\\n  --model-provider=\"gemini\" \\\n  --gitops-forge=gitlab \\\n  --gitops-host=\"gitlab.example.com\" \\\n  --gitops-repo=\"platform\u002Finfra\u002Fgitops\" \\\n  --gitlab-token-file=\u003C(pass show gitlab\u002Fagent-token | head -n1)\n```\n\nPass the same `--gitlab-token-file` source, and the same `GITLAB_READ_TOKEN_FILE` if you set one, to\nthe stage 3 run. The dry run only checks them.\n\n## Supported Command-Line Flags\n\nDefaults marked \"`installer_common.sh`\" reach the installer through\n`scripts\u002Finstaller\u002Finstaller_common.sh`; the values themselves are listed in\n`install.defaults.env` at the repository root, not here. Run `.\u002Finstall.sh --help` for the authoritative list.\n\n- Over an existing `install.env`, do not pass a chat flag (`--enable-slack`, `--enable-google-chat`, `--slack-*`, `--google-chat-allowed-users`, `--google-chat-home-channel`, `--google-chat-mode`, `--chat-topic-name`) to change a key the file sets: it is refused. Have the operator edit the key, then re-run without the flag. A key the file lacks is recorded with the value the run applies; Slack tokens never are. [`scripts\u002Finstaller\u002FREADME.md`](..\u002F..\u002F..\u002Fscripts\u002Finstaller\u002FREADME.md#the-install-configuration-installenv) has the rule.\n\n| Flag                                      | Description                                                                                                                                                                                                                                                                                                                                                                       | Default                                                                                                                                            |\n| :---------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `-y, --non-interactive`                   | Run without blocking on `\u002Fdev\u002Ftty` prompts                                                                                                                                                                                                                                                                                                                                        | `false`                                                                                                                                            |\n| `--dry-run`                               | Output plan and `terraform.tfvars` without creating resources                                                                                                                                                                                                                                                                                                                     | `false`                                                                                                                                            |\n| `--generate-only`                         | Write `install.env` and `terraform.tfvars`, run the pre-apply checks, print the operator handoff, and exit without creating or mutating resources. Mutually exclusive with `--dry-run`                                                                                                                                                                                            | `false`                                                                                                                                            |\n| `--menu, --config`                        | Launch the Day-2 control panel instead of installing                                                                                                                                                                                                                                                                                                                              | `false`                                                                                                                                            |\n| `--gcp-project-id=ID`                     | Target GCP Project ID                                                                                                                                                                                                                                                                                                                                                             | Active `gcloud` project                                                                                                                            |\n| `--gcp-region=REGION`                     | Target GCP Region                                                                                                                                                                                                                                                                                                                                                                 | `installer_common.sh` `DEFAULT_REGION`                                                                                                             |\n| `--gke-cluster-name=NAME`                 | GKE Cluster Name                                                                                                                                                                                                                                                                                                                                                                  | `installer_common.sh` `DEFAULT_CLUSTER_NAME`                                                                                                       |\n| `--gke-cluster-mode=MODE`                 | Shape of a cluster this run creates: `autopilot` \\| `standard`. Autopilot is regional: unset at a zonal `--gcp-region` builds `standard`, explicit `autopilot` there is an error. No bearing on an existing cluster, whose live shape the generator probes                                                                                                                        | `autopilot`                                                                                                                                        |\n| `--agent-namespace=NAME`                  | Kubernetes namespace the agent and operator are installed into                                                                                                                                                                                                                                                                                                                    | `installer_common.sh` `DEFAULT_NAMESPACE`                                                                                                          |\n| `--mode=MODE`                             | The `PlatformAgent`'s `spec.mode`: `next` (first-install default; a cluster already running kube-agents keeps its mode) \\| `today` (rollback; a mirrored first install's default). Recorded as `PLATFORM_AGENT_MODE` when `install.env` sets none; later, switch by editing that key, since a disagreeing `--mode` is refused                                                     | `installer_common.sh` `DEFAULT_FIRST_INSTALL_PLATFORM_AGENT_MODE`                                                                                  |\n| `--image-tag=TAG`                         | Validated immutable release tag or full commit SHA (developer\u002FCI only)                                                                                                                                                                                                                                                                                                            | Developer and CI\u002FCD testing only; end users must use official release installations. Default: inferred from baked release, bundle, or local `HEAD` |\n| `--registry-prefix=PATH`                  | Registry path (no URL scheme) for the first-party images this project builds                                                                                                                                                                                                                                                                                                      | `installer_common.sh` `DEFAULT_REGISTRY_PREFIX`                                                                                                    |\n| `--third-party-registry-prefix=PATH`      | Registry path holding the mirrored third-party images (cert-manager, LiteLLM, fluent-bit, token minter, Hindsight). Not implied by `--registry-prefix`                                                                                                                                                                                                                            | _unset_ — upstream registries                                                                                                                      |\n| `--allow-unverified-source`               | Provision from a dirty or mismatched checkout                                                                                                                                                                                                                                                                                                                                     | `false`                                                                                                                                            |\n| `--model-provider=NAME`                   | `gemini` \\| `vertex_ai` \\| `anthropic` \\| `openai` \\| `openai_compatible` \\| `sap` \\| any other LiteLLM provider slug                                                                                                                                                                                                                                                             | `installer_common.sh` `DEFAULT_MODEL_PROVIDER`                                                                                                     |\n| `--vertex-location=LOCATION`              | Vertex AI serving location, a region or `global`. The global endpoint gives no in-region ML processing guarantee                                                                                                                                                                                                                                                                  | `installer_common.sh` `DEFAULT_VERTEX_LOCATION`                                                                                                    |\n| `--gemini-api-key=KEY`                    | Gemini API key                                                                                                                                                                                                                                                                                                                                                                    | Looked up in Secret Manager                                                                                                                        |\n| `--openai-api-key=KEY`                    | OpenAI API key                                                                                                                                                                                                                                                                                                                                                                    | _unset_                                                                                                                                            |\n| `--anthropic-api-key=KEY`                 | Anthropic API key                                                                                                                                                                                                                                                                                                                                                                 | _unset_                                                                                                                                            |\n| `--model-default-name=NAME`               | Model for `model-default`; required with `openai_compatible` (the name the server serves) and with a provider slug outside the menu                                                                                                                                                                                                                                               | _unset_                                                                                                                                            |\n| `--litellm-env-secret=NAME`               | Secret in the agent's namespace the gateway reads whole as environment variables; the operator creates it (SAP AI Core service key, another provider's variables, or an `openai_compatible` gateway's OAuth2 client credentials)                                                                                                                                                  | _unset_                                                                                                                                            |\n| `--openai-compatible-api-base=URL`        | `openai_compatible`: the server's OpenAI-compatible base URL, including `\u002Fv1`                                                                                                                                                                                                                                                                                                     | _unset_                                                                                                                                            |\n| `--openai-compatible-target-port=PORT`    | `openai_compatible`: the server pod's port, required when the URL names an in-cluster Service                                                                                                                                                                                                                                                                                     | _unset_                                                                                                                                            |\n| `--openai-compatible-egress-cidrs=LIST`   | `openai_compatible`: private CIDRs the server is reached at, when the URL is not an in-cluster Service (comma-separated)                                                                                                                                                                                                                                                          | _unset_                                                                                                                                            |\n| `--openai-compatible-ca-secret-name=NAME` | `openai_compatible`: Secret (key `ca.crt`) holding the CA bundle of a server behind a private CA                                                                                                                                                                                                                                                                                  | _unset_                                                                                                                                            |\n| `--openai-compatible-api-key=KEY`         | `openai_compatible`: optional API key the server checks                                                                                                                                                                                                                                                                                                                           | _unset_                                                                                                                                            |\n| `--permission-set=SET`                    | Agent GCP IAM set: `read-only` \\| `custom`                                                                                                                                                                                                                                                                                                                                        | `read-only`                                                                                                                                        |\n| `--custom-roles=ROLES`                    | Roles for `--permission-set=custom` (space- or comma-separated)                                                                                                                                                                                                                                                                                                                   | _unset_                                                                                                                                            |\n| `--scope-projects=IDS`                    | Projects beyond the install's whose GKE clusters get a Cluster Agent (space- or comma-separated); the agent's service account gets the read roles in each. Recorded as `SCOPE_PROJECTS`; a flag on a re-run applies for that run and warns; the preflight checks the credentials Terraform applies with can create a sink in each                                                 | _unset_ (the install's project alone)                                                                                                              |\n| `--scope-folders=IDS`                     | Numeric GCP folder IDs: every project beneath, at any depth; the read roles plus `roles\u002Fcloudasset.viewer` bound on the folder; the Cloud Asset API enabled. Recorded as `SCOPE_FOLDERS`; the preflight checks the credentials Terraform applies with can set IAM policy on each                                                                                                  | _unset_ (no container)                                                                                                                             |\n| `--scope-organizations=IDS`               | Numeric GCP organisation IDs, bound the same way. Wide; prefer folders. Recorded as `SCOPE_ORGANIZATIONS`                                                                                                                                                                                                                                                                         | _unset_ (no container)                                                                                                                             |\n| `--scope-shared-vpc-hosts=IDS`            | Shared VPC host project IDs: every attached service project, resolved when Terraform plans (as the identity it applies with) and granted the read roles, with `roles\u002Fcompute.viewer` in the host for the lookup; a lookup that identity cannot make fails the plan before anything is applied. Recorded as `SCOPE_SHARED_VPC_HOSTS`                                               | _unset_ (no selector)                                                                                                                              |\n| `--scope-metrics-scopes=IDS`              | Metrics Scope scoping-project IDs: every project the scope monitors, resolved and granted the same way, the scoping project too for the lookup; a monitored project the planning identity cannot name is left out by naming its number in `--scope-exclude-projects`. Recorded as `SCOPE_METRICS_SCOPES`                                                                          | _unset_ (no selector)                                                                                                                              |\n| `--scope-max-projects=N`                  | The most projects the reconcile lists per run, the management project included (`spec.scope.maxProjects`, 1 to 5000); a project past it reads over-cap. Recorded as `SCOPE_MAX_PROJECTS`                                                                                                                                                                                          | _unset_ (the CRD's 100)                                                                                                                            |\n| `--scope-exclude-projects=IDS`            | Project IDs or shell-style globs (`*-sandbox`) to leave unmanaged                                                                                                                                                                                                                                                                                                                 | _unset_                                                                                                                                            |\n| `--scope-exclude-clusters=TRIPLES`        | Clusters to leave unmanaged and out of fleet answers and the per-cluster audits, each as `project\u002Flocation\u002Fcluster`                                                                                                                                                                                                                                                               | _unset_                                                                                                                                            |\n| `--scoped-sa-pool-enabled[=BOOL]`         | Arm the scoped service account pool: one reader service account per project the plan lists in scope, created in the install's project; the credential broker selects from them and refuses a cluster in a project with none. Members hold no IAM grant yet, so leave it off. Recorded as `SCOPED_SA_POOL_ENABLED`                                                                 | `false`                                                                                                                                            |\n| `--scoped-sa-pool-max-accounts=N`         | The most pool accounts the plan may create in the install's project, set from its free service-account quota (a whole number of at least 1); a pool past it is refused at plan. Recorded as `SCOPED_SA_POOL_MAX_ACCOUNTS`                                                                                                                                                         | _unset_ (the module's 100)                                                                                                                         |\n| `--gitops-org=ORG`                        | GitHub organization for the GitOps IaC repository. Not used with `--gitops-forge=gitlab`                                                                                                                                                                                                                                                                                          | _unset_                                                                                                                                            |\n| `--gitops-repo=REPO`                      | GitOps IaC repository name. With `--gitops-forge=gitlab`, the project's full path (`group\u002Fsubgroup\u002Fproject`) or its `https:\u002F\u002F` URL on the GitLab host                                                                                                                                                                                                                             | `gke-fleet-iac`                                                                                                                                    |\n| `--gitops-forge=FORGE`                    | Forge that holds the GitOps repository: `github` or `gitlab` (gitlab.com or a self-managed instance). A GitLab install has no GitHub App and no token minter                                                                                                                                                                                                                      | `github`                                                                                                                                           |\n| `--gitops-host=HOST`                      | The forge's hostname, with no scheme, port or path. With `github`: a GitHub Enterprise Server host; a host that GitHub operates and `gitlab.com` are refused, and `github.com` switches back to github.com. With `gitlab`: a self-managed GitLab host; a GitHub hostname is refused                                                                                               | github.com or gitlab.com                                                                                                                           |\n| `--gitlab-token-file=PATH`                | File, `\u002Fdev\u002Fstdin` (not under `curl … \\| bash`) or `\u003C(command)` that holds the GitLab access token (scopes `api` and `write_repository`). A regular file is checked before the apply. Read into the Secret after the apply and never recorded. Without it, a non-interactive run prints the command that creates the Secret                                                       | _unset_                                                                                                                                            |\n| `--gitlab-token-secret=NAME`              | Kubernetes Secret that holds the GitLab token                                                                                                                                                                                                                                                                                                                                     | `gitlab-forge-token`                                                                                                                               |\n| `--gitops-ca-file=PATH`                   | Regular PEM file with the CA certificates of a self-managed GitLab or a GitHub Enterprise Server behind a private CA. Stored in Secret `gitlab-forge-ca` or `github-forge-ca`, and trusted by the credential broker for that host only. Needs a self-managed `--gitops-host`. On a GitHub Enterprise Server the token minter trusts it too, and the installer restarts the minter | _unset_                                                                                                                                            |\n| `--github-app-id=ID`                      | Numeric GitHub App ID for the token minter                                                                                                                                                                                                                                                                                                                                        | _unset_                                                                                                                                            |\n| `--github-pem-path=PATH`                  | Path to downloaded GitHub App private key (`.pem`) for initial Cloud KMS import                                                                                                                                                                                                                                                                                                   | _unset_                                                                                                                                            |\n| `--kms-keyring=NAME`                      | Cloud KMS Key Ring name for the token minter key                                                                                                                                                                                                                                                                                                                                  | `\u003Ccluster>-minter-keyring` (new install)                                                                                                           |\n| `--kms-key=NAME`                          | Cloud KMS CryptoKey name for the token minter signing key                                                                                                                                                                                                                                                                                                                         | `\u003Ccluster>-minter-key` (new install)                                                                                                               |\n| `--enable-google-chat`                    | Enable the Google Chat integration                                                                                                                                                                                                                                                                                                                                                | `false`                                                                                                                                            |\n| `--google-chat-allowed-users=EMAILS`      | Comma-separated chat users allowed to reach the agent; empty allows everyone                                                                                                                                                                                                                                                                                                      | _unset_                                                                                                                                            |\n| `--enable-slack[=true\\|false]`            | Enable the Slack socket-mode relay (non-interactive runs require `--slack-bot-token` and `--slack-app-token`)                                                                                                                                                                                                                                                                     | `false`                                                                                                                                            |\n| `--slack-bot-token=TOKENS`                | Comma-separated Slack bot tokens (`xoxb-...`), one per workspace the agent serves                                                                                                                                                                                                                                                                                                 | _unset_                                                                                                                                            |\n| `--slack-app-token=TOKEN`                 | Slack socket-mode app-level token (`xapp-...`)                                                                                                                                                                                                                                                                                                                                    | _unset_                                                                                                                                            |\n| `--slack-allowed-users=USERS`             | Comma-separated Slack member IDs (`U0123ABCD`, not emails) allowed to reach the agent; empty allows everyone                                                                                                                                                                                                                                                                      | _unset_                                                                                                                                            |\n| `--slack-home-channel=CHANNEL`            | Slack channel ID for unsolicited alerts\u002Fmessages (e.g. `C01234567`)                                                                                                                                                                                                                                                                                                               | _unset_                                                                                                                                            |\n| `--slack-home-channel-name=NAME`          | Display name of that channel (e.g. `#gke-alerts`)                                                                                                                                                                                                                                                                                                                                 | _unset_                                                                                                                                            |\n| `--google-chat-home-channel=SPACE`        | Google Chat space (`spaces\u002F\u003Cid>`) for unsolicited alerts and reports; under `next` a proactive post with no home channel is refused and kept for a digest, or goes to Slack when Slack is on too (`HomeChannelUnset`)                                                                                                                                                             | _unset_                                                                                                                                            |\n| `--enable-stockout-investigator`          | Investigate cluster-autoscaler scale-up failures and open remediation pull requests; also enables the Pub\u002FSub adapter. Recorded as `ENABLE_STOCKOUT_INVESTIGATOR` on a new `install.env`; later, turn it off in `install.env`                                                                                                                                                     | `false`                                                                                                                                            |\n| `--enable-pubsub-platform`                | Enable the Pub\u002FSub platform adapter on its own; `--enable-stockout-investigator` already implies it                                                                                                                                                                                                                                                                               | `false`                                                                                                                                            |\n| `--enable-gvisor=true\\|false`             | Enable GKE Sandbox (gVisor) runtime isolation                                                                                                                                                                                                                                                                                                                                     | `true`                                                                                                                                             |\n| `--enable-hermes-dashboard=true\\|false`   | Enable the Hermes Web UI on port 9119                                                                                                                                                                                                                                                                                                                                             | `false`                                                                                                                                            |\n| `--enable-web-console=true\\|false`        | Deploy the web console, a browser page for the agent reached by `kubectl port-forward`: chat threads, event triage and scheduled checks, and usage. Requires NetworkPolicy enforcement (cannot be combined with `--accept-no-network-policy`)                                                                                                                                     | `false`                                                                                                                                            |\n| `--enable-gke-backup-plan=true\\|false`    | Provision a GKE Backup Plan for the cluster                                                                                                                                                                                                                                                                                                                                       | `false`                                                                                                                                            |\n| `--migrate-node-pools`                    | Authorize migrating legacy GCE metadata server node pools to `GKE_METADATA` (recreates those nodes, restarts their workloads). Without it such a cluster is refused unchanged. The cluster's owner decides; never pass it unasked                                                                                                                                                 | `false`                                                                                                                                            |\n| `--enable-network-policy`                 | Authorize enabling the legacy Calico NetworkPolicy addon on an existing Standard cluster without Dataplane V2 (may recreate nodes). One of two answers (or the only answer when `--enable-web-console` is enabled); with neither the cluster is refused unchanged. The owner decides; never pass it unasked                                                                       | `false`                                                                                                                                            |\n| `--accept-no-network-policy`              | The other answer (unavailable when `--enable-web-console` is enabled): install without modifying the cluster. Every NetworkPolicy kube-agents ships is then inert, the agent sandbox's included; recorded in the report and on the `PlatformAgent`. The owner decides; never pass it unasked                                                                                      | `false`                                                                                                                                            |\n| `--memory=MODE`                           | Long-term agent memory engine: `file` \\| `hindsight` \\| `off`                                                                                                                                                                                                                                                                                                                     | `file`                                                                                                                                             |\n| `-h, --help, -?`                          | Output CLI usage banner and parameter details                                                                                                                                                                                                                                                                                                                                     | `N\u002FA`                                                                                                                                              |\n","# `install-kube-agents` Skill\n\nInstall `kube-agents` for an operator: detect their environment and interview them for the choices\nthat shape the install, preview it with `--dry-run`, and apply only after they confirm. Follow\n[Install workflow](#install-workflow).\n\n## What `install.sh` actually does\n\nIt is a front-end, not a second provisioner. It loads `install.env` (the install's\nhand-authored configuration), collects anything still missing, generates\n`terraform\u002Fexamples\u002Ffull-install\u002Fterraform.tfvars` from the result, and then runs the composition's\n`lifecycle.sh apply` — the Terraform root in\n[`terraform\u002Fexamples\u002Ffull-install\u002F`](..\u002F..\u002F..\u002Fterraform\u002Fexamples\u002Ffull-install\u002FREADME.md) owns every\nGCP resource and installs the Helm chart (`charts\u002Fkube-agents`) that owns every Kubernetes one.\nThe chart installs the operator and the `PlatformAgent` into the `kubeagents-system` namespace\n(`DEFAULT_NAMESPACE`; `--agent-namespace` overrides it).\nTerraform state goes to a GCS bucket (`\u003Cproject>-kube-agents-tfstate`, versioned, prefix\n`kube-agents\u002F\u003Ccluster>`), so `uninstall.sh` and `upgrade.sh` can find the install from a fresh\nclone. The installer sources\n[`scripts\u002Finstaller\u002Finstaller_common.sh`](..\u002F..\u002F..\u002Fscripts\u002Finstaller\u002FREADME.md) before its\nfirst prompt, so its defaults and accepted values are the ones defined there; that file is where a\ndefault changes.\n\nOrder of operations: resolve the image\u002Fsource ref → check CLI prerequisites (including\n`terraform`, which it offers to install; `make` is not needed) → put the repository on disk and\nverify it against that ref → load `install.env` → interview for what is missing → generate\n`terraform.tfvars` → refuse a service account another install in the project owns\n(`check_service_account_ownership`, before the summary and the dry-run exit) → run\n`lifecycle.sh apply`. The source check happens **before** the interview, so a bad ref fails in\nseconds rather than after a dozen answers. Some steps stay `gcloud` calls outside the apply — before\nit, CMEK, the Workload Identity pool and NetworkPolicy enforcement on a pre-existing cluster; after\nit, the managed-OTel scope on a cluster it created — and the GitHub App PEM import runs through\nthe Minty CLI so the key never enters Terraform state. Re-running the installer (or its `--menu`\nDay-2 panel's Save & Apply) reconciles every change through one `terraform apply`.\n\n## Install workflow\n\nRun the three stages in order. Stages 1 and 2 create and change nothing in the project or cluster;\nnothing is applied until the operator confirms in stage 3. Pass `--non-interactive` to every\n`install.sh` call: a tool call has no terminal for the installer's `\u002Fdev\u002Ftty` prompts, so the\ninterview happens in chat instead.\n\nAfter every `install.sh` call, read the exit code before `\u002Ftmp\u002Fkube-agents-install-report.json`.\nSeveral exits (the service-account ownership check, input validation, a missing tool under\n`--dry-run`, the GitHub App PEM import) write no report and leave an earlier run's in place.\n\nResolve the latest stable release tag from [GitHub Releases](https:\u002F\u002Fgithub.com\u002Fgke-labs\u002Fkube-agents\u002Freleases)\n(e.g. `0.4.0`; a GitLab install needs `0.9.0` or later) first, and substitute it for `\u003CRELEASE_VERSION>` in every command you run or hand\nover; never leave the placeholder unrendered.\n\n### Stage 1: Detect and interview\n\nRead the environment; change nothing:\n\n```bash\ngcloud config get-value project\ngcloud config get-value compute\u002Fregion\ngcloud auth application-default print-access-token >\u002Fdev\u002Fnull && echo \"ADC: ok\"\ngcloud container clusters list --project \u003CPROJECT_ID>\ngcloud storage ls gs:\u002F\u002F\u003CPROJECT_ID>-kube-agents-tfstate\u002Fkube-agents\u002F\n```\n\nNo `ADC: ok` means no Application Default Credentials. Stage 3 cannot run without them unless one\nof the google provider's credential variables is set — `GOOGLE_OAUTH_ACCESS_TOKEN`,\n`GOOGLE_CREDENTIALS`, `GOOGLE_CLOUD_KEYFILE_JSON` or `GCLOUD_KEYFILE_JSON`, which Terraform reads\nbefore ADC (stage 2 says why). With none of them set, have the operator run\n`gcloud auth application-default login` now.\n\nA prefix in that bucket means an install already exists in the project, and so does an\n`install.env` where the installer looks for one: `$KUBE_AGENTS_INSTALL_ENV`, beside `install.sh`,\nthe current directory, or `$HOME\u002Fkube-agents\u002F` (where the `curl | bash` form clones). Ask whether\nthe operator wants [`upgrade-kube-agents`](..\u002Fupgrade-kube-agents\u002FSKILL.md) instead. A re-run reads\nthat `install.env`; [`scripts\u002Finstaller\u002FREADME.md`](..\u002F..\u002F..\u002Fscripts\u002Finstaller\u002FREADME.md) owns the\nprecedence between it, flags and defaults.\n\nPropose the detected value or the default for each decision below, and have the operator confirm or\nchange it. Never ask for a secret in chat: the installer reads each one from the environment it runs\nin, where the operator sets it.\n\n1. **Project and region** — `--gcp-project-id`, `--gcp-region`. Defaults: the active `gcloud`\n   project, and its `compute\u002Fregion` or, when that is unset, `DEFAULT_REGION`.\n2. **Cluster** — create one (`--gke-cluster-name`, default `DEFAULT_CLUSTER_NAME`; Autopilot unless\n   `--gke-cluster-mode=standard` or a zonal region) or install onto one from the list. An existing\n   cluster can need changes; stage 2 lists them.\n3. **Model provider** — `--model-provider`. `vertex_ai` authenticates through Workload Identity and\n   needs no API key; `gemini`, `anthropic` and `openai` read `GEMINI_API_KEY`, `ANTHROPIC_API_KEY`\n   or `OPENAI_API_KEY`, and `gemini` also finds a key stored in Secret Manager. The default is\n   `DEFAULT_MODEL_PROVIDER`; pass the flag for any other choice. `openai_compatible` points the\n   gateway at the operator's own OpenAI-compatible server and has no defaults: ask for the served\n   model name (`--model-default-name`), the base URL with `\u002Fv1` (`--openai-compatible-api-base`),\n   and the server pod's port for an in-cluster Service (`--openai-compatible-target-port`) or the\n   CIDRs of a private address (`--openai-compatible-egress-cidrs`). The key\n   (`OPENAI_COMPATIBLE_API_KEY`) and a CA Secret for a private CA are optional. `sap` (SAP AI\n   Core) and any other LiteLLM provider slug read their credentials from a Secret the operator\n   creates in the agent's namespace and names with `--litellm-env-secret` (never ask for the\n   service key itself); a slug outside the menu also needs `--model-default-name`. The same flag\n   names the Secret holding an `openai_compatible` gateway's OAuth2 client credentials.\n4. **How the operator reaches the agent** — a terminal when no chat flag is passed (see\n   [Handing over a chat-less install](#handing-over-a-chat-less-install)), `--enable-google-chat`,\n   or `--enable-slack` (tokens in `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN`).\n   `--enable-web-console` adds a browser chat page for chat-less installs, reached by port-forward\n   ([Web console](..\u002F..\u002F..\u002Fdocs\u002Fsite\u002Fsrc\u002Fcontent\u002Fdocs\u002Fdeploy\u002Fweb-console.md)).\n   `--enable-hermes-dashboard=true` adds the Hermes Web UI, which a port-forward cannot reach under\n   gVisor; the\n   [PlatformAgent reference](..\u002F..\u002F..\u002Fdocs\u002Fsite\u002Fsrc\u002Fcontent\u002Fdocs\u002Foperator\u002Fplatformagent-crd.md)\n   gives its access path.\n5. **Where the GitOps repository lives**, only if the operator wants GitOps pull requests. Ask\n   which forge holds it. On GitHub, pass `--gitops-org`, `--gitops-repo` and `--github-app-id`; see\n   [GitOps Repository & GitHub Token Minter Configuration](#gitops-repository--github-token-minter-configuration).\n   On GitLab (gitlab.com or a self-managed instance), pass `--gitops-forge=gitlab`, `--gitops-repo`\n   and, for a self-managed instance, `--gitops-host`; see\n   [GitOps Repository on GitLab](#gitops-repository-on-gitlab). Never combine the GitHub App flags\n   with `--gitops-forge=gitlab`: the installer refuses them. On a GitHub Enterprise Server, pass the\n   GitHub flags and `--gitops-host` (and `--gitops-ca-file` for a private CA); see\n   [GitHub Enterprise Server](#github-enterprise-server).\n6. **Whether scale-up failures get investigated** — `--enable-stockout-investigator` (default\n   `false`; it also turns on the Pub\u002FSub adapter). Always offer it. On, the cluster autoscaler's\n   `noScaleUp` logs for the install's own cluster reach the Platform Agent, which diagnoses the\n   Pending workload (quota, ComputeClass fallbacks, capacity advice) and opens a remediation pull\n   request. Off, an unschedulable pod is still triaged from its `FailedScheduling` events, but\n   nothing reads the autoscaler's verdicts or proposes a fix. It adds a Log Router sink, a Pub\u002FSub\n   topic and subscription, and grants (publisher for the sink, subscriber and viewer for the\n   agent); setting `ENABLE_STOCKOUT_INVESTIGATOR=false` in `install.env` removes them on the next\n   run. A pull request needs the GitOps repository from decision 5, and a chat post needs a home\n   channel (`--google-chat-home-channel` or `--slack-home-channel`): under `next`, a proactive post\n   with no home channel is refused and kept for a digest (or goes to Slack when both platforms are on); the gateway logs the refusal and the `PlatformAgent` carries\n   `HomeChannelUnset`.\n\nLeave `--permission-set` at `read-only` unless the operator asks for `custom`. The flags in\n[Adopting a cluster the operator already owns](#adopting-a-cluster-the-operator-already-owns) are\nthe operator's decision, never a default you fill in.\n\n### Stage 2: Dry run\n\nRun the installer with the agreed flags and `--dry-run`. It validates the Terraform configuration\nand, when Application Default Credentials exist, runs `terraform plan` against local state; it\ncreates nothing, the state bucket included:\n\n```bash\ncurl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Fgke-labs\u002Fkube-agents\u002F\u003CRELEASE_VERSION>\u002Finstall.sh | bash -s -- \\\n  --dry-run \\\n  --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\" \\\n  --model-provider=\"gemini\" \\\n  --permission-set=\"read-only\"\n```\n\nA dry run regenerates `terraform.tfvars`, so back up a real deployment's copy first. It writes no\n`install.env`: a dry run provisions nothing, so it has no install to record, and an existing one is\nnever rewritten. It also installs no missing tool — a machine without any installer prerequisite\n(`terraform`, `helm`, `gh`, `gke-gcloud-auth-plugin` and the rest) fails it with\n`Dry-run validation will not install missing tools`, naming the tool; install it and re-run.\n\nGo through the output with the operator:\n\n- `DRY_RUN_SUCCESS` in `\u002Ftmp\u002Fkube-agents-install-report.json` does not clear an existing cluster.\n  The dry run exits before the consent gates, so it never reports `REFUSED_*`.\n- On an existing cluster, the `Existing Cluster Mutations (Adoption)` summary and any\n  `Dry-run: skipping terraform plan because ...` warning are the questions to put to the operator,\n  as [Adopting a cluster the operator already owns](#adopting-a-cluster-the-operator-already-owns)\n  sets out.\n- `No Application Default Credentials; skipping the resource preview` means no plan ran, and on an\n  existing cluster the NetworkPolicy and node-pool checks were skipped too, so their warnings are\n  missing. Have the operator run `gcloud auth application-default login` and repeat stage 2. Do not\n  go to stage 3 without ADC or one of the provider's credential variables\n  (`GOOGLE_OAUTH_ACCESS_TOKEN`, `GOOGLE_CREDENTIALS`, `GOOGLE_CLOUD_KEYFILE_JSON`,\n  `GCLOUD_KEYFILE_JSON`): the apply's Terraform needs one, the installer's own auth check tests\n  neither, and on an existing cluster the Workload Identity, CMEK and NetworkPolicy changes run\n  before Terraform starts — the install fails after making them. A credential variable does not\n  bring the preview back, since the dry run's plan gate checks ADC alone.\n- `No ... API key was provided` is a warning, not a failure: the install would finish with an agent\n  that cannot call its model. Resolve the key before stage 3.\n\nWhen the operator would rather run the apply themselves, use [`--generate-only`](#generate-only-mode)\ninstead: it runs the consent gates and reports `REFUSED_*` itself.\n\n### Stage 3: Confirm and apply\n\nWhen the operator chooses an adoption flag in stage 2, re-run stage 2 with it: the flag changes both\nthe adoption summary and whether `terraform plan` runs at all, so the first preview no longer shows\nwhat will run. Summarize the project, the cluster (new or existing, and each change the run makes to\nan existing one), the namespace, the model provider and how the operator reaches the agent. Wait\nfor an explicit go-ahead. Then run the last stage 2 command without `--dry-run`:\n\n```bash\ncurl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Fgke-labs\u002Fkube-agents\u002F\u003CRELEASE_VERSION>\u002Finstall.sh | bash -s -- \\\n  --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\" \\\n  --model-provider=\"gemini\" \\\n  --permission-set=\"read-only\"\n```\n\n- Non-zero exit: the install did not finish. Relay the error the installer printed. A `REFUSED_*`\n  status leaves the cluster unchanged — relay the options and stop.\n- `SUCCESS`, or `SUCCESS_PENDING_ROLLOUT` (applied, but a deployment had not reported ready; relay\n  the `kubectl rollout status` command the installer printed): relay how to reach the agent — the\n  terminal commands in [Handing over a chat-less install](#handing-over-a-chat-less-install) (including\n  the `kubectl port-forward` command for the web console when `--enable-web-console` was enabled),\n  which also fetch credentials, or the chat platform the operator enabled — then confirm the pods with\n  `kubectl get pods -n kubeagents-system`. Relay `network_policy_enforcement` when it is not\n  `enforced` ([Machine-Readable Results](#machine-readable-results)).\n\nThe `kubectl` commands in this skill assume the default namespace; under `--agent-namespace`,\nsubstitute its value.\n\n### Local sources and pre-confirmed runs\n\nThe same invocation runs from the official release bundle (recommended) or a checkout pinned to the\nrelease tag; add `--dry-run` for stage 2 exactly as above. Run an apply without stages 1 and 2 only\nwhen the operator has already confirmed these exact flags, or in CI.\n\n```bash\ncurl -fsSL https:\u002F\u002Fgithub.com\u002Fgke-labs\u002Fkube-agents\u002Freleases\u002Fdownload\u002F\u003CRELEASE_VERSION>\u002Fkube-agents-\u003CRELEASE_VERSION>.tar.gz | tar -xz\ncd kube-agents-\u003CRELEASE_VERSION>\n.\u002Finstall.sh --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\" \\\n  --model-provider=\"gemini\" \\\n  --permission-set=\"read-only\"\n```\n\nIf a Git checkout is required, clone pinned to the release tag:\n\n```bash\ngit clone --branch \u003CRELEASE_VERSION> https:\u002F\u002Fgithub.com\u002Fgke-labs\u002Fkube-agents.git\ncd kube-agents\n.\u002Finstall.sh --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\" \\\n  --model-provider=\"gemini\" \\\n  --permission-set=\"read-only\"\n```\n\nDo not clone `main` to deploy an official release: manifests and CRD schemas on `main` evolve continuously and diverge from released container images. Running install scripts against a mismatched checkout will fail `verify_local_source_ref` to prevent deploying incompatible manifests.\n\n## Generate-Only Mode\n\nTo generate configuration files (`install.env` and `terraform.tfvars`), run pre-apply validation checks, and hand off the apply to the operator without creating or mutating cloud resources, use `--generate-only`:\n\n```bash\ncurl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Fgke-labs\u002Fkube-agents\u002F\u003CRELEASE_VERSION>\u002Finstall.sh | bash -s -- \\\n  --generate-only \\\n  --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\"\n```\n\nIn `--generate-only` mode, the installer:\n\n1. Writes `install.env` (if absent) and `terraform\u002Fexamples\u002Ffull-install\u002Fterraform.tfvars`. One refusal lives inside this step rather than after it: if the cluster already exists, nothing stated a memory mode (no `--memory=`, no `MEMORY` in `install.env`), and the run cannot ask the cluster whether it is running the Hindsight memory store, the generator stops instead of defaulting to a value whose apply would delete the store's database. That applies under `--generate-only` and `--dry-run` as well, because both write the same real `terraform.tfvars` and `--generate-only` exists to hand it to `lifecycle.sh apply`. It exits 1 having written no `terraform.tfvars` at all — the opposite of the step-2 refusals below. Pass `--memory=hindsight|file|off`, record `MEMORY` in `install.env`, or restore access to the cluster.\n2. Runs the same pre-apply validation checks a real run does: the GitOps organization check, the service-account ownership check, the scope container preflight when a folder or organisation is declared, which on this route, and on the interactive `g` answer, only warns (the apply it hands off may run as another identity; a run that applies, `-y` or the interactive `Y`, refuses, exit 1 with no report status like the ownership check, when the credentials Terraform applies with cannot set IAM policy on a container or an enforced organisation policy forbids the Cloud Asset API), the scope export sink preflight when the drift ingress is on and `SCOPE_PROJECTS` lists a project the drift sinks will cover, which warns and refuses on the same routes when those credentials cannot create a Log Router sink (`logging.sinks.create`) in one of them (a project whose sink the install's Terraform state already holds warns instead when the credentials can read it), and the existing-cluster node-pool and NetworkPolicy consent gates, but not the live-scope check, which needs the install's kubeconfig context and runs only on a run that will apply; the handoff says so. The interactive `g` answer is given after the pre-flight summary, by which point that check has run too, so the two routes differ in that one check. A cluster needing `--migrate-node-pools`, or one enforcing no NetworkPolicy that was given neither `--enable-network-policy` nor `--accept-no-network-policy` (or that combines `--enable-web-console` with `--accept-no-network-policy`), is refused (`REFUSED_MISSING_NODE_POOL_MIGRATION`, `REFUSED_MISSING_NETWORK_POLICY`, `REFUSED_WEB_CONSOLE_WITHOUT_NETWORK_POLICY`), as is one that cannot be described (`FAILED_PREFLIGHT_CLUSTER_UNREADABLE`). A `REFUSED_*` status is a question for the operator, not a flag to add: see [Adopting a cluster the operator already owns](#adopting-a-cluster-the-operator-already-owns). Step 1 has already written both files by then, so a refusal exits 1 leaving `install.env` and `terraform.tfvars` on disk — unvalidated, and with no handoff printed. Do not read the presence of `terraform.tfvars` as success; read the report status.\n3. Prints a checklist of out-of-Terraform prerequisites (CMEK database encryption, Workload Identity, NetworkPolicy, GitHub App PEM import, OTel scope, and, with Google Chat or Slack on, the chat setup checklist commands to run after the apply) and the `lifecycle.sh apply` command with remote state variables (`KUBE_AGENTS_STATE_BUCKET` and `KUBE_AGENTS_STATE_PREFIX`).\n4. Exits 0 with status `GENERATE_ONLY_SUCCESS` in `\u002Ftmp\u002Fkube-agents-install-report.json`, or exits 1 with the `REFUSED_*` \u002F `FAILED_PREFLIGHT_*` status from step 2.\n\nOn a GitLab install, step 2 skips the GitOps organization check: there is no GitHub organization.\nGenerate-only never creates the Secrets (the token Secret, and the CA Secret for a private CA). On a\nfirst install it writes the GitLab keys into `install.env`. Over an existing `install.env` it does\nnot change them: have the operator set `GITOPS_FORGE`, `GITOPS_HOST`, `GITOPS_REPO`,\n`GITLAB_TOKEN_SECRET` and, for a private CA, `GITOPS_CA_SECRET` there before\n`lifecycle.sh apply`. Otherwise the next `upgrade.sh` removes the GitLab forge. After\n`lifecycle.sh apply`, the person who runs the install creates the Secrets by hand (Terraform only\nnames them), as\n[Dry run and generate-only](https:\u002F\u002Fgke-labs.github.io\u002Fkube-agents\u002Finstall\u002Fgitlab\u002F#dry-run-and-generate-only)\nshows.\n\nThe interactive wizard also offers the same choice by answering `g` at the final confirmation step.\n\n## Adopting a cluster the operator already owns\n\nInstalling onto a cluster somebody else made can require changing that cluster, and **modifying a\ncluster the operator already owns is never a decision you make on your own.** The pre-flight\nsummary (`Existing Cluster Mutations (Adoption)`, also printed by `--dry-run`) lists every change\nthe run would make; on an agent-driven run there is no interactive prompt, so the flags are the\nconsent and you must obtain it before passing one. Present each pending change with its cost and ask:\n\n- **Workload Identity pool** — enabled on the control plane without a flag when missing.\n  Non-revertible. Say so before running a real install.\n- **CMEK database encryption** — enabled without a flag when missing: a Cloud KMS key ring and key\n  (permanent), the Cloud KMS API, and a control-plane update. `ALLOW_UNENCRYPTED_SECRETS=true`\n  skips it. Say so before running a real install.\n- **Legacy node pools** (`REFUSED_MISSING_NODE_POOL_MIGRATION`) — two answers: `--migrate-node-pools`\n  recreates every node on those pools and restarts the workloads on them, kube-agents' or not; or\n  stop. There is no install without Workload Identity.\n- **No NetworkPolicy enforcement** (`REFUSED_MISSING_NETWORK_POLICY`) — three answers (or when\n  `--enable-web-console` is enabled, `--enable-network-policy`, `--enable-web-console=false` paired\n  with `--accept-no-network-policy`, or stop, since the web console requires NetworkPolicy enforcement\n  and cannot be combined with `--accept-no-network-policy`), and you present the applicable ones:\n  1. `--enable-network-policy` enables the legacy Calico addon: a control-plane update that may\n     recreate node pools and restart workloads unrelated to kube-agents.\n  2. `--accept-no-network-policy` installs without enforcement (unavailable when `--enable-web-console`\n     is enabled unless `--enable-web-console=false` is also passed to turn the web console off). The cluster is not modified. Every\n     NetworkPolicy kube-agents ships is inert: the agent pod's egress confinement, the shell\n     sandbox's deny-all (the sandbox is where model-authored commands run), and the LiteLLM,\n     minter and Hindsight policies. The confinement lost is kube-agents'\n     own, not the operator's workloads'; \"we trust the workloads in this cluster\" does not answer\n     it. Recorded in the report and on the `PlatformAgent`.\n  3. Stop. The cluster is unchanged.\n- **Web console without NetworkPolicy enforcement** (`REFUSED_WEB_CONSOLE_WITHOUT_NETWORK_POLICY`) —\n  emitted when `--enable-web-console` (`WEB_CONSOLE_ENABLED=true`) is combined with\n  `--accept-no-network-policy` (`ACCEPT_NO_NETWORK_POLICY=true`) against a cluster that still\n  enforces no NetworkPolicy. Present the operator's choices: enable NetworkPolicy enforcement\n  (`--enable-network-policy` and remove `ACCEPT_NO_NETWORK_POLICY=true` from `install.env`), disable\n  the web console (`--enable-web-console=false` \u002F `WEB_CONSOLE_ENABLED=false`), or stop.\n- **gVisor node pool** — on an existing Standard cluster `--enable-gvisor=true` (the default) adds a\n  billable `gvisor-pool` node pool. Say so; `--enable-gvisor=false` runs the agent unsandboxed instead.\n\nA refusal is not a failure to route around: a `REFUSED_*` status with an unchanged cluster is the\ninstaller doing its job. Report it, relay the options above, and pass a flag only when the operator\nhas chosen. When they choose `--accept-no-network-policy` and an `install.env` already exists, tell\nthem to add `ACCEPT_NO_NETWORK_POLICY=true` to it (the installer prints the same instruction): the\nnext `upgrade.sh` regenerates from that file and is refused without the key. When they later\nconfine the cluster, tell them to remove the key again; the installer warns while it lingers.\n\nA GitHub token minter that this install did not make (one from `make deploy-github`, by hand, or\nfrom another Helm release) also stops a run, with exit 1 and no `REFUSED_*` status, in two cases.\nThe run names the GitOps repository with keys that do not match the `PlatformAgent` (it prints the\nlines to set), or the run deploys the chart's own minter over the other one. In the second case,\nask the operator whether to remove the other minter or to keep it with `GITHUB_APP_ID` empty. See\n\"When an upgrade is refused\" in the upgrade page.\n\n## Source verification\n\nBefore provisioning, the installer requires the checkout holding the Terraform configuration and\nchart to be at the same commit as `--image-tag` and to have no uncommitted changes — the install\nsources and the container image must come from one revision. A dirty or mismatched checkout aborts with instructions.\n`--allow-unverified-source` (or `ALLOW_UNVERIFIED_SOURCE=true`) downgrades that to a warning; use it\nwhen iterating on the installer itself, not for a deployment you intend to keep. `--dry-run` is\nlenient already.\n\n## GCP IAM permission sets\n\n`--permission-set` chooses which GCP IAM role bundle the composition grants the agent's GSA (its\n`permission_set` variable; `custom` becomes a `project_roles` list). It does **not** affect\nKubernetes RBAC, which is read-only in every set, and it does not gate the GitOps pull-request\npath, which works in every set. See the site's\n[security and IAM reference](..\u002F..\u002F..\u002Fdocs\u002Fsite\u002Fsrc\u002Fcontent\u002Fdocs\u002Freference\u002Fsecurity-and-iam.md).\n\n| Set         | Grants                                                                           |\n| ----------- | -------------------------------------------------------------------------------- |\n| `read-only` | Auditing and observability: read roles, no write to infrastructure. **Default.** |\n| `custom`    | Exactly the roles passed in `--custom-roles`; no built-in bundle.                |\n\n## Machine-Readable Results\n\nUpon completion, `install.sh` generates a machine-readable JSON status report at `\u002Ftmp\u002Fkube-agents-install-report.json`:\n\n```json\n{\n  \"status\": \"SUCCESS\",\n  \"dry_run\": false,\n  \"generate_only\": false,\n  \"non_interactive\": true,\n  \"project_id\": \"YOUR_GCP_PROJECT_ID\",\n  \"cluster_name\": \"platform-agent-host\",\n  \"timestamp\": \"2026-08-05T03:35:00Z\"\n}\n```\n\nThe full report also carries `gvisor_enabled`, `memory_mode`, and `network_policy_enforcement`. A\nreport written before the run decided them says so: `gvisor_enabled` is `null` before the interview,\nand the other two are empty, rather than restating a default the run never applied. For\n`network_policy_enforcement` that covers a run that failed early, a `--dry-run` (it never reaches the\ncluster step; only with `--accept-no-network-policy` against a cluster that enforces nothing does it\nreport `absent-accepted`), and a `--generate-only` run under `--enable-network-policy`, which has\nnot enabled anything yet.\n`network_policy_enforcement` is `enforced` (Dataplane V2 or Calico, or a cluster this run created),\n`enabled-by-install` (this run turned Calico on, under `--enable-network-policy`), or\n`absent-accepted` (installed without enforcement, under `--accept-no-network-policy`); the last is\nalso stamped onto the `PlatformAgent` as `kubeagents.x-k8s.io\u002Fnetwork-policy-enforcement`, so it\noutlives the report. Relay it to the operator in either of the last two cases.\n\n## Handing over a chat-less install\n\nChat is opt-in and both platforms default off, and a non-interactive run with no\n`--enable-google-chat` or `--enable-slack` selects \"None\" — so the common agent-driven install\nhas no chat and the operator has no way to reach the agent unless you give them one. Under\n`spec.mode: next` the `PlatformAgent` carries the `NoChatPlatform` condition, which says where the\nfindings go instead. The installer prints the way in at the chat-configuration step and again when it finishes, under\n`--- [Talking to the Agent from a Terminal] ---`, with the cluster, region, project and namespace\nalready substituted. Relay those two commands verbatim (plus the `Web Console` `kubectl port-forward`\ncommand and `http:\u002F\u002Flocalhost:8080` URL when `--enable-web-console` was enabled):\n\n```bash\ngcloud container clusters get-credentials \u003CCLUSTER_NAME> --location \u003CREGION> --project \u003CPROJECT_ID> --dns-endpoint\nkubectl exec -it deployment\u002Fplatform-agent-gateway -n kubeagents-system -c platform-agent -- hermes -p platform\n```\n\n- Take `--dns-endpoint` from what the installer printed rather than adding it: `gcloud` rejects it\n  on a cluster publishing no externally reachable DNS endpoint, and the installer omits it there.\n- `-p platform` reaches the Platform Agent. A bare `hermes` reaches the Planning Agent front door,\n  which is where a chat message would have landed.\n- `kubectl port-forward` to the agent pod is not an alternative: the agent is sandboxed under gVisor\n  by default and the forward cannot see into the sandbox. `kubectl exec` enters it. (When\n  `--enable-web-console` is enabled, the separate web console pod does not run under gVisor and is\n  reached via the `kubectl port-forward svc\u002Fkube-agents-web-console -n kubeagents-system 8080:8080`\n  command the installer prints.)\n- Say what the operator does not get until they enable a platform: scheduled reports and\n  alert-driven triage reach chat only; meanwhile the audit reports stay in the report store on the\n  data volume and the triage runs on the board (the `NoChatPlatform` condition says so), and the first-run inventory report waits a\n  day for a human connecting over chat — read it meanwhile with\n  `kubectl exec platform-agent-shell-0 -n kubeagents-system -c shell -- cat \u002Fopt\u002Fdata\u002FINVENTORY.md`\n  (the report is on the shell sandbox pod, not the agent pod); a day after the sweep it is filed as\n  an issue labelled `agent:onboarding` in the GitOps repository when that is on GitHub (else in the\n  install's one managed GitHub repository), and a report\n  that could not be filed is retired a week after the sweep (INSTALL.md, \"What to Expect After\n  Installation\").\n\n## GitOps Repository & GitHub Token Minter Configuration\n\nWhen deploying `kube-agents` with GitOps pull-request workflows enabled, the Platform Agent creates pull requests against an infrastructure-as-code repository via the [GitHub Token Minter](https:\u002F\u002Fgithub.com\u002Fabcxyz\u002Fgithub-token-minter) (`minty`). The minter runs in-cluster and signs short-lived GitHub App installation tokens using a private key securely stored in Google Cloud KMS. (For GitLab or self-managed Gitea GitOps repositories, which use a Kubernetes Secret token instead of the GitHub App token minter, see [GitLab as the GitOps forge](..\u002F..\u002F..\u002Fdocs\u002Fsite\u002Fsrc\u002Fcontent\u002Fdocs\u002Finstall\u002Fgitlab.md) and [Gitea as the GitOps forge](..\u002F..\u002F..\u002Fdocs\u002Fsite\u002Fsrc\u002Fcontent\u002Fdocs\u002Finstall\u002Fgitea.md).)\n\n### Prerequisites\n\nThe GitHub App and its permissions, the private key, the Cloud KMS signing key and its default names, and the Go toolchain the import needs are documented once, in [Token minter](https:\u002F\u002Fgke-labs.github.io\u002Fkube-agents\u002Fdeploy\u002Ftoken-minter\u002F). Read it before running either path below. Restating those values here is how the two copies drift apart, and this file already defers the same way for flag defaults.\n\nOne input decides whether the minter can work at all, so it is worth stating where the command is: `--gitops-org` must be a GitHub **organization**. Minty resolves App installations at `\u002Forgs\u002F{org}\u002Finstallation`, which returns 404 for a personal account, and `install.sh` refuses one rather than deploying a minter that can never mint a token.\n\nThe commands below are stage 2 dry runs showing the GitOps flags. Replace the placeholders and example values with the operator's values from stage 1, review the output as stage 2 describes, and apply through stage 3 once the operator confirms — Path 1 creates a Cloud KMS key ring and key, which cannot be deleted.\n\n### Deployment Path 1: Automated Import via `install.sh`\n\nIn this path, `install.sh` automatically creates the Cloud KMS keyring\u002Fkey (if missing) and imports the GitHub App private key using the Minty CLI before Terraform applies:\n\n```bash\n.\u002Finstall.sh --dry-run --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\" \\\n  --model-provider=\"gemini\" \\\n  --gitops-org=\"YOUR_GITHUB_ORG\" \\\n  --gitops-repo=\"YOUR_GITOPS_REPO\" \\\n  --github-app-id=\"YOUR_GITHUB_APP_ID\" \\\n  --github-pem-path=\"\u002Fpath\u002Fto\u002Fapp-private-key.pem\"\n```\n\nDelete the `.pem` once the stage 3 apply succeeds. Cloud KMS keys cannot be destroyed, and a later run or upgrade finds the `ENABLED` version and skips the import.\n\n### Deployment Path 2: Pre-Provisioned \u002F Ahead-Of-Time (AOT) Key\n\nFor CI\u002FCD pipelines and anywhere runners must not handle raw private keys, the key is imported ahead of time — the procedure, and the keyring and key names `install.sh` expects, are in [Token minter](https:\u002F\u002Fgke-labs.github.io\u002Fkube-agents\u002Fdeploy\u002Ftoken-minter\u002F). Once the key holds an `ENABLED` version, invoke `install.sh` without `--github-pem-path`:\n\n```bash\n.\u002Finstall.sh --dry-run --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\" \\\n  --model-provider=\"gemini\" \\\n  --gitops-org=\"YOUR_GITHUB_ORG\" \\\n  --gitops-repo=\"YOUR_GITOPS_REPO\" \\\n  --github-app-id=\"YOUR_GITHUB_APP_ID\" \\\n  --kms-keyring=\"YOUR_KEY_RING\" \\\n  --kms-key=\"YOUR_KEY\"\n```\n\nPass both KMS flags with the names the operator pre-provisioned. A pre-provisioned key carries no owner labels, and the installer adopts one only when both names are given explicitly.\n\n### GitHub Enterprise Server\n\nIf the operator's GitOps repository is on a GitHub Enterprise Server (a GitHub instance at a host they run, not github.com), `install.sh`, `upgrade.sh` and `uninstall.sh` install, upgrade and remove it, with `--gitops-host` and, for a private CA, `--gitops-ca-file`. Follow [GitHub Enterprise Server in INSTALL.md](..\u002F..\u002F..\u002FINSTALL.md#github-enterprise-server) with them. Its steps and their order:\n\n1. Decide the cluster. A new cluster goes into the project's `default` VPC network (the installer has no network setting). If the instance is reachable only from another VPC or a Shared VPC, the operator creates the cluster in that VPC, and you install onto it with `--gke-cluster-name`.\n2. On an existing cluster, check the network path now (DNS and HTTPS on port 443) with the two `kubectl run` commands in INSTALL.md. On a new cluster, run them right after the install. A private instance needs a route, firewall rules and, for a private DNS zone, a Cloud DNS forwarding zone; those are the operator's to set up.\n3. The operator creates the GitHub App on the instance (not on github.com), with the same permissions as on github.com and no webhook, installed on the organization, the GitOps repository and each context repository, and downloads its private key. The repository must exist and belong to an organization.\n4. Run `install.sh --dry-run` with `--gitops-host=\u003Chost>`, `--gitops-org`, `--gitops-repo` (the name, not a URL), `--github-app-id`, `--github-pem-path` and, only for a private CA, `--gitops-ca-file=\u003Cregular PEM file>`. Show the operator the summary. Then run it without `--dry-run`. The installer imports the App's key into Cloud KMS before the apply, writes the CA into the Secret `github-forge-ca` after the apply, and restarts the token minter so that it reads the CA.\n5. Check the minter's `SOURCE_SYSTEM_API_BASE_URL` and the gitops-state ConfigMap's `github_host` with the commands in INSTALL.md.\n\nRules for the agent:\n\n- `--gitops-host` is a lowercase hostname with no scheme, path or port. Do not give it for github.com, except to switch an install back to github.com. The installer refuses a host under `github.com`, `githubusercontent.com`, `ghe.com` or another domain where GitHub runs its own services, and `gitlab.com`.\n- Do not give `--gitops-ca-file` when a public CA signed the certificate. Do not use `\u002Fdev\u002Fstdin` or a process substitution for it.\n- The organization check asks the instance with the CA and sends no credential. On an instance in private mode it warns that it cannot verify the organization and goes on.\n- To upgrade, run `upgrade.sh`. To remove, run `uninstall.sh`. Both read the forge from `install.env` (`GITOPS_FORGE=github`, `GITOPS_HOST`, `GITOPS_CA_SECRET`).\n- A switch between github.com and an instance needs the App on the new host and a new `--kms-key`, because `install.sh` imports a key only into a key with no `ENABLED` version. The installer refuses a change of host that keeps the recorded App or key. INSTALL.md, \"Switch between github.com and a GitHub Enterprise Server\", gives the command. `--gitops-host=github.com` switches back. After a switch, `platform-agent-gitops-state` still lists the old host's repositories (the operator never removes an entry). Remove them as INSTALL.md says in the same section; after a switch back to github.com this is required, because an entry on the instance holds the minter's policy pruning (`Degraded`, `MinterPruningHeld`).\n- With `--generate-only`, the operator creates the CA Secret and restarts the minter after `lifecycle.sh apply`; INSTALL.md gives the commands. Over an existing `install.env`, generate-only does not record the forge keys: have the operator set `GITOPS_FORGE=github`, `GITOPS_HOST` and, for a private CA, `GITOPS_CA_SECRET` there before `lifecycle.sh apply`, or the next `upgrade.sh` renders github.com.\n- When `install.env` records `GITOPS_FORGE=gitlab`, also pass `--gitops-forge=github` to switch to an instance.\n\nAsk the operator for: the instance's hostname (port 443 only), the organization and repository on it, the App ID and the path of its private key, the CA file if a private CA signed the certificate, whether the cluster exists, and which VPC reaches the instance. An install serves one GitHub host, so this install has no github.com repositories. If the operator does not want `install.sh`, INSTALL.md also has the Terraform + Helm method.\n\n## GitOps Repository on GitLab\n\nA GitLab install has no GitHub App and no token minter. The credential broker holds a GitLab access\ntoken in a Kubernetes Secret, and the agent and its shell sandbox never get it.\n[GitLab as the GitOps forge](https:\u002F\u002Fgke-labs.github.io\u002Fkube-agents\u002Finstall\u002Fgitlab\u002F) has the\nprocedure, the network, DNS and certificate rules, a reachability test, the connection check,\ntoken expiry and rotation, and the `install.env` keys. Read it before the dry run.\n\nRules for the agent:\n\n- Use release 0.9.0 or later. An earlier release has no `--gitops-forge` flag.\n- The operator creates the GitOps project first. The installer does not create it.\n- `--gitops-repo` is the project's full path with its nested groups (`group\u002Fsubgroup\u002Fproject`), or\n  its `https:\u002F\u002F` URL on that host. Put the host in `--gitops-host`, not in the path. Leave\n  `--gitops-host` out for gitlab.com.\n- The broker uses the token only for the declared repositories. Another project in the\n  same group gets no write token. `inspect-repository` reads a public project with no token, and a\n  private one only when it is a context repository and a read-only token is stored. A project outside those groups is refused. Tell the operator to use the token with the smallest reach. That is a project access\n  token on the GitOps project, or a group access token on the project's own group. For\n  `platform\u002Finfra\u002Fgitops`, that group is `platform\u002Finfra`, not `platform`. Tell the operator to\n  record the token's expiry date, and to check the token before the install, as the GitLab page\n  shows.\n- Do not ask the operator to paste the token. Do not put the token in a flag value, a variable or\n  the transcript. Use one of these:\n  - Do not give `--gitlab-token-file`. The install completes and prints the command that creates\n    the Secret, and the operator runs it. Until then, GitLab calls answer\n    `FORGE_CREDENTIAL_UNAVAILABLE`. No restart is necessary afterwards.\n  - The operator gives a file path or a password-manager command, which you pass as\n    `--gitlab-token-file=\u003C(…)`. The installer reads the whole source and removes all whitespace,\n    newlines inside it too. The source must hold only the token. For a `pass` entry with more\n    lines, use `\u003C(pass show gitlab\u002Fagent-token | head -n1)`. The operator unlocks `gpg-agent`\n    before the run. Do not use `\u002Fdev\u002Fstdin` under `curl … | bash`, because standard input is the\n    script there.\n- Only when the operator wants the agent to read a private GitLab context repository, also set\n  `GITLAB_READ_TOKEN_FILE` to a source that holds a read-only token (Reporter role,\n  `read_repository` scope only), with the same rules as the token source above. It must not be\n  the same source as `--gitlab-token-file`. Use a release whose `install.env.example` lists\n  `GITLAB_READ_TOKEN_FILE`; an older release ignores it. The GitLab page's \"Private context\n  repositories\" section has the setup.\n- Only when a private CA signed the instance's certificate, add `--gitops-ca-file` to the dry run\n  and to the stage 3 run. Do not give it for gitlab.com or for a public CA. Ask the operator\n  for the CA certificates (public; never a private key) as a regular PEM file: the root CA, and the\n  intermediate CAs if the server does not send them. Do not use `\u002Fdev\u002Fstdin` or a process\n  substitution for the CA. The installer refuses the flag without a self-managed `--gitops-host`.\n  Use a release whose `install.sh --help` lists `--gitops-ca-file`.\n- The dry run checks that the token source is readable, and stops if a regular token file is\n  empty. It stores no token, and `install.sh` never connects to GitLab. Only the stage 3 run\n  reads the token into the Secret, after the apply.\n- On a cluster that already exists, have the operator run the network-path test from the GitLab\n  page before stage 3, in the `default` namespace: `kubeagents-system` does not exist yet. On a\n  new cluster, run it after stage 3, in `kubeagents-system`. After stage 3, run the connection\n  check from the same page.\n\nThe command below is a stage 2 dry run. Apply through stage 3 once the operator confirms:\n\n```bash\n.\u002Finstall.sh --dry-run --non-interactive \\\n  --gcp-project-id=\"YOUR_GCP_PROJECT_ID\" \\\n  --gke-cluster-name=\"platform-agent-host\" \\\n  --gcp-region=\"us-central1\" \\\n  --model-provider=\"gemini\" \\\n  --gitops-forge=gitlab \\\n  --gitops-host=\"gitlab.example.com\" \\\n  --gitops-repo=\"platform\u002Finfra\u002Fgitops\" \\\n  --gitlab-token-file=\u003C(pass show gitlab\u002Fagent-token | head -n1)\n```\n\nPass the same `--gitlab-token-file` source, and the same `GITLAB_READ_TOKEN_FILE` if you set one, to\nthe stage 3 run. The dry run only checks them.\n\n## Supported Command-Line Flags\n\nDefaults marked \"`installer_common.sh`\" reach the installer through\n`scripts\u002Finstaller\u002Finstaller_common.sh`; the values themselves are listed in\n`install.defaults.env` at the repository root, not here. Run `.\u002Finstall.sh --help` for the authoritative list.\n\n- Over an existing `install.env`, do not pass a chat flag (`--enable-slack`, `--enable-google-chat`, `--slack-*`, `--google-chat-allowed-users`, `--google-chat-home-channel`, `--google-chat-mode`, `--chat-topic-name`) to change a key the file sets: it is refused. Have the operator edit the key, then re-run without the flag. A key the file lacks is recorded with the value the run applies; Slack tokens never are. [`scripts\u002Finstaller\u002FREADME.md`](..\u002F..\u002F..\u002Fscripts\u002Finstaller\u002FREADME.md#the-install-configuration-installenv) has the rule.\n\n| Flag                                      | Description                                                                                                                                                                                                                                                                                                                                                                       | Default                                                                                                                                            |\n| :---------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `-y, --non-interactive`                   | Run without blocking on `\u002Fdev\u002Ftty` prompts                                                                                                                                                                                                                                                                                                                                        | `false`                                                                                                                                            |\n| `--dry-run`                               | Output plan and `terraform.tfvars` without creating resources                                                                                                                                                                                                                                                                                                                     | `false`                                                                                                                                            |\n| `--generate-only`                         | Write `install.env` and `terraform.tfvars`, run the pre-apply checks, print the operator handoff, and exit without creating or mutating resources. Mutually exclusive with `--dry-run`                                                                                                                                                                                            | `false`                                                                                                                                            |\n| `--menu, --config`                        | Launch the Day-2 control panel instead of installing                                                                                                                                                                                                                                                                                                                              | `false`                                                                                                                                            |\n| `--gcp-project-id=ID`                     | Target GCP Project ID                                                                                                                                                                                                                                                                                                                                                             | Active `gcloud` project                                                                                                                            |\n| `--gcp-region=REGION`                     | Target GCP Region                                                                                                                                                                                                                                                                                                                                                                 | `installer_common.sh` `DEFAULT_REGION`                                                                                                             |\n| `--gke-cluster-name=NAME`                 | GKE Cluster Name                                                                                                                                                                                                                                                                                                                                                                  | `installer_common.sh` `DEFAULT_CLUSTER_NAME`                                                                                                       |\n| `--gke-cluster-mode=MODE`                 | Shape of a cluster this run creates: `autopilot` \\| `standard`. Autopilot is regional: unset at a zonal `--gcp-region` builds `standard`, explicit `autopilot` there is an error. No bearing on an existing cluster, whose live shape the generator probes                                                                                                                        | `autopilot`                                                                                                                                        |\n| `--agent-namespace=NAME`                  | Kubernetes namespace the agent and operator are installed into                                                                                                                                                                                                                                                                                                                    | `installer_common.sh` `DEFAULT_NAMESPACE`                                                                                                          |\n| `--mode=MODE`                             | The `PlatformAgent`'s `spec.mode`: `next` (first-install default; a cluster already running kube-agents keeps its mode) \\| `today` (rollback; a mirrored first install's default). Recorded as `PLATFORM_AGENT_MODE` when `install.env` sets none; later, switch by editing that key, since a disagreeing `--mode` is refused                                                     | `installer_common.sh` `DEFAULT_FIRST_INSTALL_PLATFORM_AGENT_MODE`                                                                                  |\n| `--image-tag=TAG`                         | Validated immutable release tag or full commit SHA (developer\u002FCI only)                                                                                                                                                                                                                                                                                                            | Developer and CI\u002FCD testing only; end users must use official release installations. Default: inferred from baked release, bundle, or local `HEAD` |\n| `--registry-prefix=PATH`                  | Registry path (no URL scheme) for the first-party images this project builds                                                                                                                                                                                                                                                                                                      | `installer_common.sh` `DEFAULT_REGISTRY_PREFIX`                                                                                                    |\n| `--third-party-registry-prefix=PATH`      | Registry path holding the mirrored third-party images (cert-manager, LiteLLM, fluent-bit, token minter, Hindsight). Not implied by `--registry-prefix`                                                                                                                                                                                                                            | _unset_ — upstream registries                                                                                                                      |\n| `--allow-unverified-source`               | Provision from a dirty or mismatched checkout                                                                                                                                                                                                                                                                                                                                     | `false`                                                                                                                                            |\n| `--model-provider=NAME`                   | `gemini` \\| `vertex_ai` \\| `anthropic` \\| `openai` \\| `openai_compatible` \\| `sap` \\| any other LiteLLM provider slug                                                                                                                                                                                                                                                             | `installer_common.sh` `DEFAULT_MODEL_PROVIDER`                                                                                                     |\n| `--vertex-location=LOCATION`              | Vertex AI serving location, a region or `global`. The global endpoint gives no in-region ML processing guarantee                                                                                                                                                                                                                                                                  | `installer_common.sh` `DEFAULT_VERTEX_LOCATION`                                                                                                    |\n| `--gemini-api-key=KEY`                    | Gemini API key                                                                                                                                                                                                                                                                                                                                                                    | Looked up in Secret Manager                                                                                                                        |\n| `--openai-api-key=KEY`                    | OpenAI API key                                                                                                                                                                                                                                                                                                                                                                    | _unset_                                                                                                                                            |\n| `--anthropic-api-key=KEY`                 | Anthropic API key                                                                                                                                                                                                                                                                                                                                                                 | _unset_                                                                                                                                            |\n| `--model-default-name=NAME`               | Model for `model-default`; required with `openai_compatible` (the name the server serves) and with a provider slug outside the menu                                                                                                                                                                                                                                               | _unset_                                                                                                                                            |\n| `--litellm-env-secret=NAME`               | Secret in the agent's namespace the gateway reads whole as environment variables; the operator creates it (SAP AI Core service key, another provider's variables, or an `openai_compatible` gateway's OAuth2 client credentials)                                                                                                                                                  | _unset_                                                                                                                                            |\n| `--openai-compatible-api-base=URL`        | `openai_compatible`: the server's OpenAI-compatible base URL, including `\u002Fv1`                                                                                                                                                                                                                                                                                                     | _unset_                                                                                                                                            |\n| `--openai-compatible-target-port=PORT`    | `openai_compatible`: the server pod's port, required when the URL names an in-cluster Service                                                                                                                                                                                                                                                                                     | _unset_                                                                                                                                            |\n| `--openai-compatible-egress-cidrs=LIST`   | `openai_compatible`: private CIDRs the server is reached at, when the URL is not an in-cluster Service (comma-separated)                                                                                                                                                                                                                                                          | _unset_                                                                                                                                            |\n| `--openai-compatible-ca-secret-name=NAME` | `openai_compatible`: Secret (key `ca.crt`) holding the CA bundle of a server behind a private CA                                                                                                                                                                                                                                                                                  | _unset_                                                                                                                                            |\n| `--openai-compatible-api-key=KEY`         | `openai_compatible`: optional API key the server checks                                                                                                                                                                                                                                                                                                                           | _unset_                                                                                                                                            |\n| `--permission-set=SET`                    | Agent GCP IAM set: `read-only` \\| `custom`                                                                                                                                                                                                                                                                                                                                        | `read-only`                                                                                                                                        |\n| `--custom-roles=ROLES`                    | Roles for `--permission-set=custom` (space- or comma-separated)                                                                                                                                                                                                                                                                                                                   | _unset_                                                                                                                                            |\n| `--scope-projects=IDS`                    | Projects beyond the install's whose GKE clusters get a Cluster Agent (space- or comma-separated); the agent's service account gets the read roles in each. Recorded as `SCOPE_PROJECTS`; a flag on a re-run applies for that run and warns; the preflight checks the credentials Terraform applies with can create a sink in each                                                 | _unset_ (the install's project alone)                                                                                                              |\n| `--scope-folders=IDS`                     | Numeric GCP folder IDs: every project beneath, at any depth; the read roles plus `roles\u002Fcloudasset.viewer` bound on the folder; the Cloud Asset API enabled. Recorded as `SCOPE_FOLDERS`; the preflight checks the credentials Terraform applies with can set IAM policy on each                                                                                                  | _unset_ (no container)                                                                                                                             |\n| `--scope-organizations=IDS`               | Numeric GCP organisation IDs, bound the same way. Wide; prefer folders. Recorded as `SCOPE_ORGANIZATIONS`                                                                                                                                                                                                                                                                         | _unset_ (no container)                                                                                                                             |\n| `--scope-shared-vpc-hosts=IDS`            | Shared VPC host project IDs: every attached service project, resolved when Terraform plans (as the identity it applies with) and granted the read roles, with `roles\u002Fcompute.viewer` in the host for the lookup; a lookup that identity cannot make fails the plan before anything is applied. Recorded as `SCOPE_SHARED_VPC_HOSTS`                                               | _unset_ (no selector)                                                                                                                              |\n| `--scope-metrics-scopes=IDS`              | Metrics Scope scoping-project IDs: every project the scope monitors, resolved and granted the same way, the scoping project too for the lookup; a monitored project the planning identity cannot name is left out by naming its number in `--scope-exclude-projects`. Recorded as `SCOPE_METRICS_SCOPES`                                                                          | _unset_ (no selector)                                                                                                                              |\n| `--scope-max-projects=N`                  | The most projects the reconcile lists per run, the management project included (`spec.scope.maxProjects`, 1 to 5000); a project past it reads over-cap. Recorded as `SCOPE_MAX_PROJECTS`                                                                                                                                                                                          | _unset_ (the CRD's 100)                                                                                                                            |\n| `--scope-exclude-projects=IDS`            | Project IDs or shell-style globs (`*-sandbox`) to leave unmanaged                                                                                                                                                                                                                                                                                                                 | _unset_                                                                                                                                            |\n| `--scope-exclude-clusters=TRIPLES`        | Clusters to leave unmanaged and out of fleet answers and the per-cluster audits, each as `project\u002Flocation\u002Fcluster`                                                                                                                                                                                                                                                               | _unset_                                                                                                                                            |\n| `--scoped-sa-pool-enabled[=BOOL]`         | Arm the scoped service account pool: one reader service account per project the plan lists in scope, created in the install's project; the credential broker selects from them and refuses a cluster in a project with none. Members hold no IAM grant yet, so leave it off. Recorded as `SCOPED_SA_POOL_ENABLED`                                                                 | `false`                                                                                                                                            |\n| `--scoped-sa-pool-max-accounts=N`         | The most pool accounts the plan may create in the install's project, set from its free service-account quota (a whole number of at least 1); a pool past it is refused at plan. Recorded as `SCOPED_SA_POOL_MAX_ACCOUNTS`                                                                                                                                                         | _unset_ (the module's 100)                                                                                                                         |\n| `--gitops-org=ORG`                        | GitHub organization for the GitOps IaC repository. Not used with `--gitops-forge=gitlab`                                                                                                                                                                                                                                                                                          | _unset_                                                                                                                                            |\n| `--gitops-repo=REPO`                      | GitOps IaC repository name. With `--gitops-forge=gitlab`, the project's full path (`group\u002Fsubgroup\u002Fproject`) or its `https:\u002F\u002F` URL on the GitLab host                                                                                                                                                                                                                             | `gke-fleet-iac`                                                                                                                                    |\n| `--gitops-forge=FORGE`                    | Forge that holds the GitOps repository: `github` or `gitlab` (gitlab.com or a self-managed instance). A GitLab install has no GitHub App and no token minter                                                                                                                                                                                                                      | `github`                                                                                                                                           |\n| `--gitops-host=HOST`                      | The forge's hostname, with no scheme, port or path. With `github`: a GitHub Enterprise Server host; a host that GitHub operates and `gitlab.com` are refused, and `github.com` switches back to github.com. With `gitlab`: a self-managed GitLab host; a GitHub hostname is refused                                                                                               | github.com or gitlab.com                                                                                                                           |\n| `--gitlab-token-file=PATH`                | File, `\u002Fdev\u002Fstdin` (not under `curl … \\| bash`) or `\u003C(command)` that holds the GitLab access token (scopes `api` and `write_repository`). A regular file is checked before the apply. Read into the Secret after the apply and never recorded. Without it, a non-interactive run prints the command that creates the Secret                                                       | _unset_                                                                                                                                            |\n| `--gitlab-token-secret=NAME`              | Kubernetes Secret that holds the GitLab token                                                                                                                                                                                                                                                                                                                                     | `gitlab-forge-token`                                                                                                                               |\n| `--gitops-ca-file=PATH`                   | Regular PEM file with the CA certificates of a self-managed GitLab or a GitHub Enterprise Server behind a private CA. Stored in Secret `gitlab-forge-ca` or `github-forge-ca`, and trusted by the credential broker for that host only. Needs a self-managed `--gitops-host`. On a GitHub Enterprise Server the token minter trusts it too, and the installer restarts the minter | _unset_                                                                                                                                            |\n| `--github-app-id=ID`                      | Numeric GitHub App ID for the token minter                                                                                                                                                                                                                                                                                                                                        | _unset_                                                                                                                                            |\n| `--github-pem-path=PATH`                  | Path to downloaded GitHub App private key (`.pem`) for initial Cloud KMS import                                                                                                                                                                                                                                                                                                   | _unset_                                                                                                                                            |\n| `--kms-keyring=NAME`                      | Cloud KMS Key Ring name for the token minter key                                                                                                                                                                                                                                                                                                                                  | `\u003Ccluster>-minter-keyring` (new install)                                                                                                           |\n| `--kms-key=NAME`                          | Cloud KMS CryptoKey name for the token minter signing key                                                                                                                                                                                                                                                                                                                         | `\u003Ccluster>-minter-key` (new install)                                                                                                               |\n| `--enable-google-chat`                    | Enable the Google Chat integration                                                                                                                                                                                                                                                                                                                                                | `false`                                                                                                                                            |\n| `--google-chat-allowed-users=EMAILS`      | Comma-separated chat users allowed to reach the agent; empty allows everyone                                                                                                                                                                                                                                                                                                      | _unset_                                                                                                                                            |\n| `--enable-slack[=true\\|false]`            | Enable the Slack socket-mode relay (non-interactive runs require `--slack-bot-token` and `--slack-app-token`)                                                                                                                                                                                                                                                                     | `false`                                                                                                                                            |\n| `--slack-bot-token=TOKENS`                | Comma-separated Slack bot tokens (`xoxb-...`), one per workspace the agent serves                                                                                                                                                                                                                                                                                                 | _unset_                                                                                                                                            |\n| `--slack-app-token=TOKEN`                 | Slack socket-mode app-level token (`xapp-...`)                                                                                                                                                                                                                                                                                                                                    | _unset_                                                                                                                                            |\n| `--slack-allowed-users=USERS`             | Comma-separated Slack member IDs (`U0123ABCD`, not emails) allowed to reach the agent; empty allows everyone                                                                                                                                                                                                                                                                      | _unset_                                                                                                                                            |\n| `--slack-home-channel=CHANNEL`            | Slack channel ID for unsolicited alerts\u002Fmessages (e.g. `C01234567`)                                                                                                                                                                                                                                                                                                               | _unset_                                                                                                                                            |\n| `--slack-home-channel-name=NAME`          | Display name of that channel (e.g. `#gke-alerts`)                                                                                                                                                                                                                                                                                                                                 | _unset_                                                                                                                                            |\n| `--google-chat-home-channel=SPACE`        | Google Chat space (`spaces\u002F\u003Cid>`) for unsolicited alerts and reports; under `next` a proactive post with no home channel is refused and kept for a digest, or goes to Slack when Slack is on too (`HomeChannelUnset`)                                                                                                                                                             | _unset_                                                                                                                                            |\n| `--enable-stockout-investigator`          | Investigate cluster-autoscaler scale-up failures and open remediation pull requests; also enables the Pub\u002FSub adapter. Recorded as `ENABLE_STOCKOUT_INVESTIGATOR` on a new `install.env`; later, turn it off in `install.env`                                                                                                                                                     | `false`                                                                                                                                            |\n| `--enable-pubsub-platform`                | Enable the Pub\u002FSub platform adapter on its own; `--enable-stockout-investigator` already implies it                                                                                                                                                                                                                                                                               | `false`                                                                                                                                            |\n| `--enable-gvisor=true\\|false`             | Enable GKE Sandbox (gVisor) runtime isolation                                                                                                                                                                                                                                                                                                                                     | `true`                                                                                                                                             |\n| `--enable-hermes-dashboard=true\\|false`   | Enable the Hermes Web UI on port 9119                                                                                                                                                                                                                                                                                                                                             | `false`                                                                                                                                            |\n| `--enable-web-console=true\\|false`        | Deploy the web console, a browser page for the agent reached by `kubectl port-forward`: chat threads, event triage and scheduled checks, and usage. Requires NetworkPolicy enforcement (cannot be combined with `--accept-no-network-policy`)                                                                                                                                     | `false`                                                                                                                                            |\n| `--enable-gke-backup-plan=true\\|false`    | Provision a GKE Backup Plan for the cluster                                                                                                                                                                                                                                                                                                                                       | `false`                                                                                                                                            |\n| `--migrate-node-pools`                    | Authorize migrating legacy GCE metadata server node pools to `GKE_METADATA` (recreates those nodes, restarts their workloads). Without it such a cluster is refused unchanged. The cluster's owner decides; never pass it unasked                                                                                                                                                 | `false`                                                                                                                                            |\n| `--enable-network-policy`                 | Authorize enabling the legacy Calico NetworkPolicy addon on an existing Standard cluster without Dataplane V2 (may recreate nodes). One of two answers (or the only answer when `--enable-web-console` is enabled); with neither the cluster is refused unchanged. The owner decides; never pass it unasked                                                                       | `false`                                                                                                                                            |\n| `--accept-no-network-policy`              | The other answer (unavailable when `--enable-web-console` is enabled): install without modifying the cluster. Every NetworkPolicy kube-agents ships is then inert, the agent sandbox's included; recorded in the report and on the `PlatformAgent`. The owner decides; never pass it unasked                                                                                      | `false`                                                                                                                                            |\n| `--memory=MODE`                           | Long-term agent memory engine: `file` \\| `hindsight` \\| `off`                                                                                                                                                                                                                                                                                                                     | `file`                                                                                                                                             |\n| `-h, --help, -?`                          | Output CLI usage banner and parameter details                                                                                                                                                                                                                                                                                                                                     | `N\u002FA`                                                                                                                                              |\n",{"name":5,"description":7},"main","bc0d0d1f8a8781f08503e5d70c08684a084fb005",".agents\u002Fskills\u002Finstall-kube-agents",43,"https:\u002F\u002Fgithub.com\u002Fgke-labs\u002Fkube-agents","https:\u002F\u002Fgke-labs.github.io\u002Fkube-agents\u002F","https:\u002F\u002Fgithub.com\u002Fgke-labs","2026-11-12 05:35:56",{"source":8,"skillName":5,"commands":32,"prompt":42,"zipAvailable":43},[33,36,39],{"tool":34,"command":35},"npx","npx skills add gke-labs\u002Fkube-agents --skill install-kube-agents",{"tool":37,"command":38},"bunx","bunx skills add gke-labs\u002Fkube-agents --skill install-kube-agents",{"tool":40,"command":41},"pnpm","pnpm dlx skills add gke-labs\u002Fkube-agents --skill install-kube-agents","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fgke-labs\u002Fkube-agents\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Finstall-kube-agents\u002FSKILL.md",true,[],[46,53,60,67,74,81],{"skillNo":47,"name":48,"nameZh":6,"description":49,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":50,"categories":51,"keywords":52,"publishedAt":18},"01M720VZZQNT2JQVK1JJCXX977","gce-compute-fleet-audit","Audits standalone GCE virtual machines, Managed Instance Groups (MIGs), serial console boot failures, sole-tenant node group headroom, and orphaned disk snapshots.","https:\u002F\u002Fgithub.com\u002Fgke-labs\u002Fkube-agents\u002Fblob\u002Fmain\u002Fagents\u002Fplatform\u002Fskills\u002Fgce-compute-fleet-audit\u002FSKILL.md",[],[],{"skillNo":54,"name":55,"nameZh":6,"description":56,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":57,"categories":58,"keywords":59,"publishedAt":18},"01M720VKEW26G8QGT9NY4XQDR5","fleet-audit","Publish the findings of an autonomous fleet audit as one continuously-rewritten issue per audit stream per install on the GitOps repository's forge, and propose fixes as narrow remediation pull requests (merge requests on GitLab).","https:\u002F\u002Fgithub.com\u002Fgke-labs\u002Fkube-agents\u002Fblob\u002Fmain\u002Fagents\u002Fplatform\u002Fskills\u002Ffleet-audit\u002FSKILL.md",[],[],{"skillNo":61,"name":62,"nameZh":6,"description":63,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":64,"categories":65,"keywords":66,"publishedAt":18},"01M720SYHF3JZYBM8CW52MCXS8","gke-rightsizing-checks","Five required checks, and the arithmetic, before any proposal to lower a workload's CPU or memory requests or limits, or to change a node pool's machine type or size, and before any claim of capacity freed. Read-only. For \"lower the requests of X\", \"right-size this deployment\", \"can this pool use a smaller machine type\", and any answer that claims nodes, CPU, memory or a percentage freed.","https:\u002F\u002Fgithub.com\u002Fgke-labs\u002Fkube-agents\u002Fblob\u002Fmain\u002Fagents\u002Fcluster\u002Fskills\u002Fgke-rightsizing-checks\u002FSKILL.md",[],[],{"skillNo":68,"name":69,"nameZh":6,"description":70,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":71,"categories":72,"keywords":73,"publishedAt":18},"01M720PC2JZ2NV08DK93YMH1VA","review-chat-ux","Reviews a diff that changes what users read in Slack or Google Chat against the chat UX rules, and reports each violation with file:line and the rule it breaks.","https:\u002F\u002Fgithub.com\u002Fgke-labs\u002Fkube-agents\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Freview-chat-ux\u002FSKILL.md",[],[],{"skillNo":75,"name":76,"nameZh":6,"description":77,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":18},"01M720WDAGMMRY1FXYVHGE7HBV","github-issue-resolver","Autonomously poll, triage, investigate, and resolve unaddressed open issues on our target repository (on GitHub or GitLab) strictly within authorized scope.","https:\u002F\u002Fgithub.com\u002Fgke-labs\u002Fkube-agents\u002Fblob\u002Fmain\u002Fagents\u002Fplatform\u002Fskills\u002Fgithub-issue-resolver\u002FSKILL.md",[],[],{"skillNo":82,"name":83,"nameZh":6,"description":84,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":85,"categories":86,"keywords":87,"publishedAt":18},"01M720PETPTF0TE7NEZC31BW5X","review-docs-drift","Reviews a pull request for documentation drift — finds which docs the change should have updated, verifies doc claims against source, and checks the docs map and AGENTS.md themselves for staleness.","https:\u002F\u002Fgithub.com\u002Fgke-labs\u002Fkube-agents\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Freview-docs-drift\u002FSKILL.md",[],[]]