[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M698Z35P4SQ48BTJ5PTE72AT":3,"skill-same:01M698Z35P4SQ48BTJ5PTE72AT":184},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":26,"publishedAt":47,"linkOnly":48,"markdownContent":49,"markdownBody":50,"frontMatter":51,"refName":55,"commitSha":56,"skillDirectory":57,"forksCount":58,"repositoryHtmlUrl":59,"repositoryHomepageUrl":6,"ownerHtmlUrl":60,"ownerBlogUrl":61,"ownerDescription":62,"updatedAt":63,"install":64,"resources":77},"01M698Z35P4SQ48BTJ5PTE72AT","apigee-agent-gateway-governance",null,"Stands up an Apigee API proxy that a Google Cloud Agent Gateway calls as an ext-proc (Service Extension) callout on an agent's LLM traffic, to meter token usage per model, extract caller identity, and apply Model Armor prompt screening or a semantic cache. Use when wiring Apigee in as an AI gateway behind an Agent Gateway, when an agent's model egress needs governing, or when a callout proxy returns no verdict and you need to find out why. The distinguishing question is whether Apigee forwards the traffic or only inspects it: this skill is inspect-only, and builds the proxy itself. Don't use it when Apigee is meant to receive a request and route it onward to a backend — including hosting an MCP server behind Apigee — which needs a proxy with a target instead. Don't use for governing the agent's calls to tools or MCP servers. Don't use for choosing or switching which model a request goes to. Don't use for creating the Agent Gateway itself, nor for Apigee hybrid.","google\u002Fskills","google","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F1342004?v=4","01M698VYEFDM0VJBZDYYJ2T83S","Google","01M698WR0H76ENHE587T9EJT89",24122,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,9],"apigee agent gateway governance","governance","gateway","apigee","question","when","wiring","inspect","which","traffic","you","switching","route","up","only","MCP","model","api","mcp server","google cloud","2026-11-10 10:12:30",false,"---\nname: apigee-agent-gateway-governance\nmetadata:\n  version: \"1.0.2\"\n  category: AiAndMachineLearning\ndescription: >-\n  Stands up an Apigee API proxy that a Google Cloud Agent Gateway calls as an\n  ext-proc (Service Extension) callout on an agent's LLM traffic, to meter\n  token usage per model, extract caller identity, and apply Model Armor prompt\n  screening or a semantic cache. Use when wiring Apigee in as an AI gateway\n  behind an Agent Gateway, when an agent's model egress needs governing, or\n  when a callout proxy returns no verdict and you need to find out why. The\n  distinguishing question is whether Apigee forwards the traffic or only\n  inspects it: this skill is inspect-only, and builds the proxy itself. Don't\n  use it when Apigee is meant to receive a request and route it onward to a\n  backend — including hosting an MCP server behind Apigee — which needs a proxy\n  with a target instead. Don't use for governing the agent's calls to tools or\n  MCP servers. Don't use for choosing or switching which model a request goes\n  to. Don't use for creating the Agent Gateway itself, nor for Apigee hybrid.\n---\n\n# Apigee as an Agent Gateway Service Extension\n\nWires an Apigee proxy in as an **ext-proc callout** on a GCP Agent Gateway. The\nproxy is a **processor, not an API proxy**: it has no backend, inspects the LLM\nrequest and response the gateway hands it, and returns a verdict.\n\n## Usage Attribution\n\n**MUST READ**: Before executing any `gcloud`, REST API, or MCP command, you\nMUST read and follow the instructions in\n[Usage Attribution](references\u002Fattribution.md).\n\nYou build the proxy; the user does not.\n\n**Two modes, one schema. Decide which before anything else, and say which.**\nInput file present → **prod mode**: it is the contract, every value comes from\nit, and a missing or malformed field is a refusal, not a default. No input file\nand the user is describing an outcome → **dev mode**: apply the documented\ndefaults, show a plan with durations and get approval, start with identity and\nmetering only, write `apigee-agent-gateway-governance.yaml` at the end as the\nhandoff. `assets\u002Fapigee-agent-gateway-governance.example.yaml` documents the\nschema both modes use.\n\nModes, defaults, durations, cost disclosure and the interlocks that bind both:\n`references\u002Fmodes.md`. **Read it before you start.**\n\n**Colour the plan's ACTION column** — green `REUSE`, cyan `CREATE`\u002F`RESERVE`,\nyellow `APPEND`\u002F`MODIFY`; full palette in `references\u002Fmodes.md`. Yellow is the\none that earns its place: it is the only signal that a resource the user already\nowns is being changed. Rendered plain, the one row that modifies something is\nindistinguishable from the dozen that do not — which is exactly the row the\napproval exists for.\n\n## Prerequisite: where the agent runs\n\nCheck this **before** building anything. It decides whether traffic can reach\nthe gateway at all, and it is silent when wrong — every step below still\nsucceeds while nothing arrives.\n\n**Use Agent Runtime**, created with `agent_gateway_config` \u002F\n`agent_to_anywhere_config`. It is the supported path and the orchestrator builds\nthe client-side PSC, routing, DNS and cert trust for you. There is no `gcloud`\nsurface for this and no console path — it is SDK-only, at agent creation time.\nSee\n[Route traffic through Agent Gateway](https:\u002F\u002Fdocs.cloud.google.com\u002Fgemini-enterprise-agent-platform\u002Fscale\u002Fruntime\u002Fagent-gateway-runtime-deploy.md.txt)\nfor the binding, and `references\u002Fnetworking-psc.md` for a worked example.\n\n**Set `GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES: \"False\"` in the\nagent's `env_vars`, or every invocation 401s** on the agent's own\n`sessions.create`, before user code runs. Deploy-time invisible. Full symptom\nand isolation: `references\u002Fgotchas.md`, Agent Gateway.\n\nWhere the agent runs is otherwise settled — the premise is a working gateway and\na working agent. Do not explain how to build one or list unsupported compute\nunprompted; `references\u002Fnetworking-psc.md` has the constraints if it turns out\nto matter.\n\n## Tool versions\n\nDo not assume these are installed or current.\n\nTool                                         | Minimum     | Why\n-------------------------------------------- | ----------- | ---\n`gcloud`                                     | **588.0.0** | `apigee apis import --from-template` is on the GA track at this version; older SDKs need `gcloud beta`\n`google-cloud-aiplatform[agent_engines,adk]` | `>=2.1,\u003C3`  | Agent Runtime client used to bind the agent to the gateway\n`google-adk`                                 | `>=2.9,\u003C3`  | only if you build the test agent with ADK\n\n**On gcloud >= 588.0.0: every feature must keep its `uid`, and the template must\nnever declare `endpoints` or `targets`.** All six features set `uid` today and\nthe template declares neither, so the shipped bundle is correct — but both are\nload-bearing now, and breaking either one still compiles, still deploys, and\nreports nothing. Mechanism and symptoms: `references\u002Fgotchas.md`, under\nAuthoring (AFT).\n\n```bash\ngcloud version | head -1          # confirm >= 588.0.0\ngcloud components update          # if older\npip install \"google-cloud-aiplatform[agent_engines,adk]>=2.1,\u003C3\" \"google-adk>=2.9,\u003C3\"\n```\n\n`scripts\u002Fcompile_check.py` needs PyYAML and names the interpreter if it is\nmissing. `pip install pyyaml`, or run it with a Python 3 that has it; gcloud's\nbundled interpreter does not, and the SDK's vendored `yaml` is Python 2.\n\n```bash\npython3 scripts\u002Fcompile_check.py assets\u002Fapigee-proxy-template.yaml\n```\n\n## Execution model\n\nThree layers, and the choice between them is not stylistic:\n\nLayer                 | Used for                                               | Why\n--------------------- | ------------------------------------------------------ | ---\n**AFT YAML template** | the proxy itself                                       | version-controlled, reviewable, diffable\n**`gcloud`**          | PSC, DNS, AuthzExtension, AuthzPolicy                  | native commands exist\n**REST (`curl`)**     | env properties, envgroups, deployments, debug sessions | no `gcloud` equivalent today\n\nThe customer owns **one** input file\n(`assets\u002Fapigee-agent-gateway-governance.example.yaml`). It is the\nversion-controlled artifact. Never hand-edit the generated proxy: regenerate it.\n\n**Run the Apigee-side sequence with the script, not by hand.** Steps 1, 3 and 4\nbelow are order-dependent and each fails in a way that reads as a different\nproblem. `scripts\u002Fdeploy_proxy.sh` does all three, preserves the properties\nmap, polls the async operation, and exits non-zero if the deployment does not\ncome up `EXTENSIBLE`:\n\n```bash\nscripts\u002Fdeploy_proxy.sh --org \"$ORG\" --env \"$ENV\" --proxy \"$PROXY\" \\\n  --template assets\u002Fapigee-proxy-template.yaml [--service-account \"$SA\"] --yes \\\n  [--index-endpoint-id ID --index-id ID --sc-project P --sc-region R --embeddings-model M]\n```\n\nRun it without `--yes` first: it prints the plan and changes nothing. Show that\nplan, then re-run with `--yes` once the user agrees.\n\n**The vector-search flags are the one exception to \"edit the default in every\nfeature that declares it\".** Those three ids are substituted at deploy time\nand checked for leftovers; hand-editing them into `feature-semantic-cache.yaml`\ndeploys fine, skips the check, and makes the file single-use.\n\n`scripts\u002Fensure_gateway_connectivity.sh` is the same idea for the gateway leg\n(step 0). It is the only part of this skill that modifies a resource the user\nalready owns, so it applies nothing without `--yes`.\n\nThe steps are written out below so you can diagnose a failure, not so you can\nretype them.\n\n## Inputs you must resolve first\n\n**Environment prerequisites: `references\u002Fsetup.md`** — gcloud SDK (588+),\nproject, billing, `gcloud services enable`, region, credentials, IAM. Nothing\nbelow creates any of it.\n\nProd: from the input file, then environment, then ask — never invent. Dev: apply\nthe `references\u002Fmodes.md` defaults, and ask only for what has none.\n\n-   `project`, `region`\n-   `apigee.org`, `apigee.environment`, `apigee.envgroup`, `apigee.hostname`,\n    `apigee.proxy`\n-   `network.vpc`, `network.subnet`, `network.networkAttachment`\n-   `gateway.name`, `gateway.matchPaths` (a **list**, one entry per wire shape),\n    `gateway.manageNetworkConfig`\n\n**`matchPaths` drives TWO artifacts; write both from the same list.** The\n**AuthzPolicy** `paths[].suffix` gets one suffix per entry, verbatim with\nleading punctuation kept — it decides what the gateway routes to Apigee. The\ntemplate's **`LLM_URI_MATCH`** gets one `(request.uri Like \"*\u003Centry>*\")` clause\nper entry, OR-ed, leading `:` or `\u002F` **stripped** — it decides what the proxy\nacts on once it arrives. The shipped two-entry default gives:\n\n```\n((request.uri Like \"*generateContent*\") or (request.uri Like \"*chat\u002Fcompletions*\"))\n```\n\nThe AuthzPolicy is the outer gate and wins: a suffix list narrower than\n`matchPaths` means the missing shape never reaches Apigee at all. Derive both\nfrom the input file and never from the default when the file disagrees — the\nfile is the reviewable contract and a mismatch is invisible once deployed.\n\n**A one-entry list is legitimate** and governs one shape. Dropping an entry\ngoverns one fewer **silently** — deploys clean, zero transactions for the shape\ndropped — and narrows `feature-normalize`, Model Armor and the cache together.\n`references\u002Fgotchas.md`, Runtime.\n\n**`semanticCache` is a KNOWN ISSUE whenever it is on — warn, then build it.**\nThe key is the prompt embedding alone: no caller, no model, no shape. A hit\ntherefore crosses callers — similar prompts share an answer — and one model\nbehind one shape does not make it safe. With more shapes the first model to\nanswer answers for the other too: empty HTTP 200 across shapes, wrong-model\nbody within one. Warn whenever the cache is on, not only on multi-shape, then\nbuild. Wording: `references\u002Fmodes.md`; mechanism: `references\u002Fgotchas.md`.\n\n**Each entry is a SUFFIX match.** You write these, not the user — derive them\nfrom which endpoint their agent actually calls, and do not ask them to reason\nabout suffixes. Vertex's OpenAI-compatible endpoint and `api.openai.com` need\ndifferent entries, and getting it wrong governs nothing silently; the forms and\nthe evidence are in `references\u002Fgotchas.md`, Runtime.\n\n**There is no `apiShape` field.** Metering does not depend on the wire shape —\nthe count is normalised in JS across both — so the field selected nothing and\nwas removed. Adding a shape is an `||` branch in `extract-model.js` plus an\nentry in `matchPaths`.\n\n**Three places carry the callout hostname and all three must match** — the\nApigee envgroup, the AuthzExtension `authority`\u002F`service`, and the private DNS A\nrecord. You set all three from one value: a build invariant to hold, not a\nquestion for the user. It becomes a topic only when a callout never lands, and\nthen it is the first check. Build order matters alongside it — the\nAuthzExtension is inert until the host resolves and the proxy is deployed, so\n\"the extension exists\" is not evidence that leg works. `references\u002Fgotchas.md`,\nRuntime.\n\n## Workflow\n\n### 0. Gateway: give it a route into the VPC\n\nA gateway with no `networkConfig` has no route into the customer's VPC and\ncannot resolve the callout hostname. **This skill builds it** — network\nattachment, egress and DNS peering — so its absence is a build step, not a\nblocker, and should be reported that way. Do not open with an alarm about a\nresource you are about to create.\n\nOne thing here does need the user, and it is the reason this is step 0 rather\nthan a silent prerequisite: attaching egress is **one-way and affects traffic\nthey already run**. Settle it in the plan, before anything executes — see\n\"Confirm everything up front\" below. Capturing a known-good agent turn first is\nworth doing when they are nervous about a production gateway, because it\nseparates \"never worked\" from \"we broke it\"; it is a precaution to offer, not a\nstep to impose.\n\n```bash\ngcloud network-services agent-gateways describe \"$GATEWAY\" \\\n  --location=\"$REGION\" --project=\"$PROJECT\" --format='value(networkConfig)'\ngcloud network-security authz-policies list \\\n  --location=\"$REGION\" --project=\"$PROJECT\"\n```\n\n**A console-created gateway already has a CUSTOM AuthzPolicy** — an IAP\n`REQUEST_AUTHZ` one, which coexists with ours and is not needed for the egress\nJWT. You never create the gateway, so assess it rather than assuming.\n`references\u002Fgotchas.md`, Agent Gateway.\n\nEmpty `networkConfig` output means missing, and it is easy to miss: the gateway\nstill serves ordinary agent traffic perfectly, because that path goes to public\nGoogle endpoints and never enters the VPC. Only the Apigee callout fails.\n\nIf it is missing, behaviour depends on `gateway.manageNetworkConfig`:\n\n-   **`true`** — run the script below. It creates the network attachment, then\n    read-modify-writes the gateway to add the egress attachment and DNS peering.\n    Agreement for this is taken in the plan, not here; `--yes` carries it.\n-   **`false`** — the user has said they want to make this change themselves.\n    Print the script invocation and let them run it. Do not modify the gateway.\n\n```bash\nscripts\u002Fensure_gateway_connectivity.sh \\\n  --gateway \"$GATEWAY\" --project \"$PROJECT\" --region \"$REGION\" \\\n  --vpc \"$VPC\" --subnet \"$SUBNET\" --attachment \"$ATTACHMENT\" \\\n  --domain \"$DNS_DOMAIN\"            # add --yes only after the user agrees\n```\n\nEither way `networkConfig` has to exist before the Apigee work starts — not\nbecause its absence is an error, but because every later step then succeeds and\ngoverns nothing.\n\nUse the script, not the raw commands: `import` is a whole-resource upsert and a\nhand-built YAML silently drops the gateway's `registries` and `protocols`. Why,\nand the `etag` and `PROTOCOL_UNSPECIFIED` handling: `references\u002Fgotchas.md`.\n\n### 1. Apigee: enable the environment\n\nThe env must carry `apigee-service-extension-enabled=true`. No `gcloud` flag\nexists; PATCH the properties map.\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapigee.googleapis.com\u002Fv1\u002Forganizations\u002F$ORG\u002Fenvironments\u002F$ENV\"\n```\n\n**The PATCH replaces the whole map.** Read it first and re-send every property\nyou intend to keep, or you will silently drop them.\n\n```bash\ncurl -X PATCH -H \"Authorization: Bearer $TOKEN\" -H \"Content-Type: application\u002Fjson\" \\\n  \"https:\u002F\u002Fapigee.googleapis.com\u002Fv1\u002Forganizations\u002F$ORG\u002Fenvironments\u002F$ENV?updateMask=properties\" \\\n  -d '{\"properties\":{\"property\":[{\"name\":\"apigee-service-extension-enabled\",\"value\":\"true\"}]}}'\n```\n\nThis does **not** disable Apigee Analytics — the two coexist; if analytics looks\nmissing it is the separate per-environment add-on. `references\u002Fgotchas.md`.\n\n**Use a dedicated environment**, attached to **exactly one envgroup**. An\nenvironment in two makes the gateway 404 every request with the extension never\ninvoked. It also leaves the `\u002F` base path free, which is what the proxy ships\nwith.\n\n`scripts\u002Fensure_apigee_environment.sh` creates the environment, attaches it to\nthe regional runtime instance, and creates and attaches the envgroup — so step 2\nis already done when it succeeds. It creates billable resources and applies\nnothing without `--yes`; that agreement is taken in the plan, not here.\n\n```bash\nscripts\u002Fensure_apigee_environment.sh \\\n  --org \"$ORG\" --env \"$ENV\" --region \"$REGION\" \\\n  --envgroup \"$ENVGROUP\" --hostname \"$HOSTNAME\"      # add --yes to apply\n```\n\nIt is idempotent and resumable, and refuses with exit 2 when the environment is\nalready in another envgroup or the envgroup exists on a different hostname. It\nalso handles three things a runbook tends to get wrong — the omitted `type` on\ncreate, the four-minute instance attachment that **locks the environment**, and\npolling the operation rather than a `GET` that succeeds too early.\n\n### 2. Apigee: envgroup on the callout hostname\n\nDone already if step 1's script ran. Verify, or do it by hand for an envgroup\nthat already exists. The hostname must be exactly the callout host, and the\ngroup must be attached to the env.\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapigee.googleapis.com\u002Fv1\u002Forganizations\u002F$ORG\u002Fenvgroups\u002F$ENVGROUP\u002Fattachments\"\n```\n\n**How many envgroups is the environment in?** More than one and the callout\nnever lands. `deploy_proxy.sh` enforces this before touching anything and exits\n2 naming them; `assess_integration.sh` reports it. Read the failure literally if\nit fires — the environment is wrong, not the script.\n\n### 3. Build the proxy from the template\n\n```bash\ngcloud apigee apis import organizations\u002F$ORG\u002Fapis\u002F$PROXY \\\n  --from-template=assets\u002Fapigee-proxy-template.yaml\n```\n\nEach `features:` entry resolves **relative to the template file's own\ndirectory**, never the working directory, so a bare filename must sit beside the\ntemplate (a subpath like `features\u002Ffeature-identity.yaml` works too). `assets\u002F`\nis flat by convention, not compiler demand — but moving a file without updating\n`features:` fails the import with a bare `FileNotFoundError`.\n\nIf you get `Permission denied on resource ... (or it may not exist)` on an org\nyou demonstrably have access to, it is the credential, not the access. With\ncontext-aware access enabled the default gcloud credential is rejected. Pass the\nADC token instead:\n\n```bash\nCLOUDSDK_AUTH_ACCESS_TOKEN=$(gcloud auth application-default print-access-token) \\\n  gcloud apigee apis import ...\n```\n\nTo change a parameter, **edit the `default` in every FEATURE that declares it**\n— not in the template. `gcloud apigee apis import` passes an empty\ncaller-parameter map, so each feature resolves its own default and the\ntemplate's declaration of the same name is never read. Editing the template\nchanges nothing, silently, and the compile still passes. Measured: setting\n`MA_CONTINUE_ON_ERROR` to DRY_RUN in the template shipped ENFORCE on a\nfail-closed policy. `LLM_URI_MATCH` is declared in four features and all four\nmust agree. `scripts\u002Fcompile_check.py` fails on any disagreement. There is no\n`--parameters` flag.\n\nEnable an optional recipe by adding its feature to the template's `features`\nlist. See `references\u002Frecipes.md`.\n\n**`tokenMetering.models` in the input file is NOT generative.** It documents\nwhich buckets exist; it does not create them. A bucket is real only when it has\nan `LLMTokenQuota` step in `feature-token-metering.yaml` and a matching\n`*_TOKEN_LIMIT` parameter. Three exist: `gemini-pro`, `gemini-flash` and `other`\n— the catch-all for any model whose name contains neither `pro` nor `flash`.\nAdding a fourth entry to the list meters nothing, and **omitting `other`\nsilently unmeters every non-Gemini model**: the count is extracted and then\ndiscarded, with no `ratelimit` reference anywhere in the transaction. Adding a\nmodel is a feature-file edit, not a config change.\n\n### 4. Deploy\n\nDeploy to the service-extension env, then confirm the deployment reports\n`proxyDeploymentType: EXTENSIBLE`.\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"...\u002Fenvironments\u002F$ENV\u002Fdeployments\"\n```\n\n`STANDARD` means it will **not** govern the agent's off-Apigee tool egress.\n\nIf any policy calls a Google API — Model Armor, embeddings, Vector Search —\ndeploy **with a service account** or the deploy is rejected with\n`MISSING_SERVICE_ACCOUNT`:\n\n```bash\ncurl -X POST -H \"Authorization: Bearer $TOKEN\" \\\n  \"...\u002Fapis\u002F$PROXY\u002Frevisions\u002F$REV\u002Fdeployments?override=true&serviceAccount=$DEPLOY_SA\"\n```\n\n### 5. Networking\n\nThis is the **gateway → Apigee** leg only. PSC endpoint to the Apigee instance\nservice attachment, plus a private DNS A record. See\n`references\u002Fnetworking-psc.md`. The Apigee instance `consumerAcceptList` must\ninclude your project or the connection never reaches `ACCEPTED`.\n\nThe **agent → gateway** leg is the prerequisite above, and is not built here.\n\n### 6. Bind the gateway\n\nAuthzExtension then AuthzPolicy — in that order, and only once the host resolves\nand the proxy is deployed. The extension is inert until then.\n\n**Both document bodies ship as assets. Copy and substitute; do not invent them**\n— `assets\u002Fauthz-extension.example.yaml` and `assets\u002Fauthz-policy.example.yaml`.\nThey carry the fields that are load-bearing and have no obvious default:\n`wireFormat: EXT_PROC_GRPC`, `failOpen`, the `authority`\u002F`service` pair, the\n`metadata` body flags, and the policy's `customProvider` \u002F `httpRules` nesting.\n\n```bash\ngcloud service-extensions authz-extensions import $AUTHZ_EXT \\\n  --source=authz-extension.yaml --location=$REGION --project=$PROJECT\ngcloud beta network-security authz-policies import $AUTHZ_POLICY \\\n  --source=authz-policy.yaml --location=$REGION --project=$PROJECT\n```\n\n**The policy's resource paths take the project NUMBER, not the ID** — both\n`target.resources` and `customProvider.authzExtension.resources`. Resolve it\nyourself; narrating two numbers at the user does not help. The trap, for when a\ncallout never lands: the number in the gateway's\n`serviceExtensionsServiceAccount` is the Google-managed tenant project, not\ntheirs, and it is the only one on screen, so it is the natural thing to copy. A\nwrong number imports cleanly and targets nothing. Copy the slots from\n`assets\u002Fauthz-policy.example.yaml`.\n\n### 7. Verify\n\nYou **cannot** test with curl — the host is internal and the proxy expects the\ngateway's IAP-egress JWT. Run `scripts\u002Fread_debug_session.sh --arm`, drive a\nturn, read it back with `--session`. Expect **one** transaction per turn per\ngoverned shape under a suffix list this narrow; zero alone does not mean the\ncallout is dead — the script prints the probe that settles it.\n\n## Hard rules\n\n**Gate every step — how urgently depends on your AuthzPolicy.** An ungated\n`continueOnError=\"false\"` policy faults on any call that carries no prompt, and\nthat **breaks the agent's turn**. Historically the most common failure here.\n\nA narrow suffix list routes only the model call to Apigee, so the trap mostly\ndoes not fire. Broaden the policy and it fires at once: the ext-proc then sees\nevery agent egress — session create\u002Fread, `appendEvent`, OpenTelemetry exports —\nmeasured at 12 calls for one single-question turn, of which one was the model\ncall. Gate the steps regardless; a condition is cheap and it is what makes the\npolicy safe to widen later. Full measurement: `references\u002Fgotchas.md`.\n\n-   model call: `(request.uri Like \"*generateContent*\")`\n\n**Never fail-close on your own metering.** Keep `continueOnError=\"true\"` on the\nJavaScript and quota policies so a metering bug cannot break LLM traffic.\n\n**This skill meters by default and can enforce on request.** Enforcement is\n`assets\u002Ffeature-token-enforce.yaml`: request-flow `EnforceOnly` policies sharing\neach `SharedName` with the response-flow `CountOnly` half. Add it to the\ntemplate's feature list. Verified live on ext-proc traffic — the over-budget\ncaller gets a **429**, model never called.\n\nNever offer `CountOnly=false` as a substitute: in the response flow it cannot\ngate (the answer is already on the wire) and it freezes the counter, losing the\nmetering too.\n\nState two things whenever you offer it. It is **one request late** — the turn\nthat crosses the limit completes, because a token cost is not known until the\nmodel answers. And it buckets on the **requested** model, which under an OpenAI\nalias can differ from the resolved model the counting side bills. The two traps\nthat both present as \"it never refuses\": `references\u002Fgotchas.md`.\n\n**Normalize the model to a bucket.** With an OpenAI-compatible endpoint the\nmodel is an alias in the request body and the *resolved* name is in the response\nbody (`google\u002Fgemini-2.5-pro`). Exact-matching a raw name fails on the response\npath.\n\n**`jsonPath` needs its third argument `true`** so it returns a scalar. Without\nit the quota policy receives a node it cannot parse.\n\n## References\n\n-   `references\u002Fmodes.md` — **read first.** How to answer \"what can you do\", dev\n    vs prod, the defaults, durations, cost disclosure, the interlocks, and\n    reading an assess report\n-   `scripts\u002Fassess_integration.sh` — **read-only.** Setup Status Summary then a\n    per-check table; `--json` carries both. Lead with the summary and read out\n    the **Enforcement** row. Run it before planning: it decides CREATE vs REUSE.\n    `TODO` is a build step, not a failure; only `FAIL` exits non-zero\n-   `references\u002Fnetworking-psc.md` — both network legs, PSC, DNS, acceptance\n-   `references\u002Frecipes.md` — Model Armor and semantic cache\n-   `references\u002Fverify.md` — debug sessions and the checklist script\n-   `references\u002Fsetup.md` — SDK, project, billing, APIs, region, auth, IAM\n-   `references\u002Fgotchas.md` — 63 failure modes and their symptoms\n-   `assets\u002Fapigee-agent-gateway-governance.example.yaml` — the input contract\n-   `assets\u002Fapigee-proxy-template.yaml` — the AFT template. Features merge by\n    `priority` and all sit beside it: `feature-identity.yaml` (10),\n    `feature-normalize.yaml` (15), `feature-model-armor.yaml` (20),\n    `feature-semantic-cache.yaml` (30), `feature-token-metering.yaml` (50).\n-   `assets\u002Fauthz-extension.example.yaml`, `assets\u002Fauthz-policy.example.yaml` —\n    the step-6 bodies. Copy and substitute; paths take the project **number**\n\n    **Anything read out of the request body is normalised once, at priority 15,\n    not inside whichever feature needs it first.** `LLM_URI_MATCH` is shared, so\n    widening it arms every feature gated on it simultaneously — including Model\n    Armor, which fails closed. Model Armor and the cache both read\n    `agw.user_prompt`; the quotas read `agw.total_tokens`, set on the response\n    path by the metering feature's own JS for the same reason.\n-   `scripts\u002Fensure_apigee_environment.sh` — create the environment, attach it\n    to the instance, create and attach the envgroup. Idempotent, `--yes` gated\n-   `scripts\u002Fensure_gateway_connectivity.sh` — route the gateway into the VPC. Read-modify-write; `--yes` gated\n-   `scripts\u002Fensure_vector_search.sh` — only when `semanticCache` is enabled:\n    build index and endpoint, deploy one on the other, read back the domain.\n    `--yes` gated, ~34 min, bills continuously\n-   `scripts\u002Fteardown_vector_search.sh` — the only thing that STOPS that charge. Refuses until the revision drops `SCL-lookup`. `--yes` gated\n-   `scripts\u002Fdeploy_proxy.sh` — the Apigee-side sequence, idempotent, `--dry-run`\n-   `scripts\u002Fread_debug_session.sh` — `--arm` a session (printing when it\n    actually closes) and read it back as the `verify.md` checklist, instead of\n    parsing the transaction JSON by hand\n-   `scripts\u002Fcompile_check.py` — compile the template offline, no org needed\n","# Apigee as an Agent Gateway Service Extension\n\nWires an Apigee proxy in as an **ext-proc callout** on a GCP Agent Gateway. The\nproxy is a **processor, not an API proxy**: it has no backend, inspects the LLM\nrequest and response the gateway hands it, and returns a verdict.\n\n## Usage Attribution\n\n**MUST READ**: Before executing any `gcloud`, REST API, or MCP command, you\nMUST read and follow the instructions in\n[Usage Attribution](references\u002Fattribution.md).\n\nYou build the proxy; the user does not.\n\n**Two modes, one schema. Decide which before anything else, and say which.**\nInput file present → **prod mode**: it is the contract, every value comes from\nit, and a missing or malformed field is a refusal, not a default. No input file\nand the user is describing an outcome → **dev mode**: apply the documented\ndefaults, show a plan with durations and get approval, start with identity and\nmetering only, write `apigee-agent-gateway-governance.yaml` at the end as the\nhandoff. `assets\u002Fapigee-agent-gateway-governance.example.yaml` documents the\nschema both modes use.\n\nModes, defaults, durations, cost disclosure and the interlocks that bind both:\n`references\u002Fmodes.md`. **Read it before you start.**\n\n**Colour the plan's ACTION column** — green `REUSE`, cyan `CREATE`\u002F`RESERVE`,\nyellow `APPEND`\u002F`MODIFY`; full palette in `references\u002Fmodes.md`. Yellow is the\none that earns its place: it is the only signal that a resource the user already\nowns is being changed. Rendered plain, the one row that modifies something is\nindistinguishable from the dozen that do not — which is exactly the row the\napproval exists for.\n\n## Prerequisite: where the agent runs\n\nCheck this **before** building anything. It decides whether traffic can reach\nthe gateway at all, and it is silent when wrong — every step below still\nsucceeds while nothing arrives.\n\n**Use Agent Runtime**, created with `agent_gateway_config` \u002F\n`agent_to_anywhere_config`. It is the supported path and the orchestrator builds\nthe client-side PSC, routing, DNS and cert trust for you. There is no `gcloud`\nsurface for this and no console path — it is SDK-only, at agent creation time.\nSee\n[Route traffic through Agent Gateway](https:\u002F\u002Fdocs.cloud.google.com\u002Fgemini-enterprise-agent-platform\u002Fscale\u002Fruntime\u002Fagent-gateway-runtime-deploy.md.txt)\nfor the binding, and `references\u002Fnetworking-psc.md` for a worked example.\n\n**Set `GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES: \"False\"` in the\nagent's `env_vars`, or every invocation 401s** on the agent's own\n`sessions.create`, before user code runs. Deploy-time invisible. Full symptom\nand isolation: `references\u002Fgotchas.md`, Agent Gateway.\n\nWhere the agent runs is otherwise settled — the premise is a working gateway and\na working agent. Do not explain how to build one or list unsupported compute\nunprompted; `references\u002Fnetworking-psc.md` has the constraints if it turns out\nto matter.\n\n## Tool versions\n\nDo not assume these are installed or current.\n\nTool                                         | Minimum     | Why\n-------------------------------------------- | ----------- | ---\n`gcloud`                                     | **588.0.0** | `apigee apis import --from-template` is on the GA track at this version; older SDKs need `gcloud beta`\n`google-cloud-aiplatform[agent_engines,adk]` | `>=2.1,\u003C3`  | Agent Runtime client used to bind the agent to the gateway\n`google-adk`                                 | `>=2.9,\u003C3`  | only if you build the test agent with ADK\n\n**On gcloud >= 588.0.0: every feature must keep its `uid`, and the template must\nnever declare `endpoints` or `targets`.** All six features set `uid` today and\nthe template declares neither, so the shipped bundle is correct — but both are\nload-bearing now, and breaking either one still compiles, still deploys, and\nreports nothing. Mechanism and symptoms: `references\u002Fgotchas.md`, under\nAuthoring (AFT).\n\n```bash\ngcloud version | head -1          # confirm >= 588.0.0\ngcloud components update          # if older\npip install \"google-cloud-aiplatform[agent_engines,adk]>=2.1,\u003C3\" \"google-adk>=2.9,\u003C3\"\n```\n\n`scripts\u002Fcompile_check.py` needs PyYAML and names the interpreter if it is\nmissing. `pip install pyyaml`, or run it with a Python 3 that has it; gcloud's\nbundled interpreter does not, and the SDK's vendored `yaml` is Python 2.\n\n```bash\npython3 scripts\u002Fcompile_check.py assets\u002Fapigee-proxy-template.yaml\n```\n\n## Execution model\n\nThree layers, and the choice between them is not stylistic:\n\nLayer                 | Used for                                               | Why\n--------------------- | ------------------------------------------------------ | ---\n**AFT YAML template** | the proxy itself                                       | version-controlled, reviewable, diffable\n**`gcloud`**          | PSC, DNS, AuthzExtension, AuthzPolicy                  | native commands exist\n**REST (`curl`)**     | env properties, envgroups, deployments, debug sessions | no `gcloud` equivalent today\n\nThe customer owns **one** input file\n(`assets\u002Fapigee-agent-gateway-governance.example.yaml`). It is the\nversion-controlled artifact. Never hand-edit the generated proxy: regenerate it.\n\n**Run the Apigee-side sequence with the script, not by hand.** Steps 1, 3 and 4\nbelow are order-dependent and each fails in a way that reads as a different\nproblem. `scripts\u002Fdeploy_proxy.sh` does all three, preserves the properties\nmap, polls the async operation, and exits non-zero if the deployment does not\ncome up `EXTENSIBLE`:\n\n```bash\nscripts\u002Fdeploy_proxy.sh --org \"$ORG\" --env \"$ENV\" --proxy \"$PROXY\" \\\n  --template assets\u002Fapigee-proxy-template.yaml [--service-account \"$SA\"] --yes \\\n  [--index-endpoint-id ID --index-id ID --sc-project P --sc-region R --embeddings-model M]\n```\n\nRun it without `--yes` first: it prints the plan and changes nothing. Show that\nplan, then re-run with `--yes` once the user agrees.\n\n**The vector-search flags are the one exception to \"edit the default in every\nfeature that declares it\".** Those three ids are substituted at deploy time\nand checked for leftovers; hand-editing them into `feature-semantic-cache.yaml`\ndeploys fine, skips the check, and makes the file single-use.\n\n`scripts\u002Fensure_gateway_connectivity.sh` is the same idea for the gateway leg\n(step 0). It is the only part of this skill that modifies a resource the user\nalready owns, so it applies nothing without `--yes`.\n\nThe steps are written out below so you can diagnose a failure, not so you can\nretype them.\n\n## Inputs you must resolve first\n\n**Environment prerequisites: `references\u002Fsetup.md`** — gcloud SDK (588+),\nproject, billing, `gcloud services enable`, region, credentials, IAM. Nothing\nbelow creates any of it.\n\nProd: from the input file, then environment, then ask — never invent. Dev: apply\nthe `references\u002Fmodes.md` defaults, and ask only for what has none.\n\n-   `project`, `region`\n-   `apigee.org`, `apigee.environment`, `apigee.envgroup`, `apigee.hostname`,\n    `apigee.proxy`\n-   `network.vpc`, `network.subnet`, `network.networkAttachment`\n-   `gateway.name`, `gateway.matchPaths` (a **list**, one entry per wire shape),\n    `gateway.manageNetworkConfig`\n\n**`matchPaths` drives TWO artifacts; write both from the same list.** The\n**AuthzPolicy** `paths[].suffix` gets one suffix per entry, verbatim with\nleading punctuation kept — it decides what the gateway routes to Apigee. The\ntemplate's **`LLM_URI_MATCH`** gets one `(request.uri Like \"*\u003Centry>*\")` clause\nper entry, OR-ed, leading `:` or `\u002F` **stripped** — it decides what the proxy\nacts on once it arrives. The shipped two-entry default gives:\n\n```\n((request.uri Like \"*generateContent*\") or (request.uri Like \"*chat\u002Fcompletions*\"))\n```\n\nThe AuthzPolicy is the outer gate and wins: a suffix list narrower than\n`matchPaths` means the missing shape never reaches Apigee at all. Derive both\nfrom the input file and never from the default when the file disagrees — the\nfile is the reviewable contract and a mismatch is invisible once deployed.\n\n**A one-entry list is legitimate** and governs one shape. Dropping an entry\ngoverns one fewer **silently** — deploys clean, zero transactions for the shape\ndropped — and narrows `feature-normalize`, Model Armor and the cache together.\n`references\u002Fgotchas.md`, Runtime.\n\n**`semanticCache` is a KNOWN ISSUE whenever it is on — warn, then build it.**\nThe key is the prompt embedding alone: no caller, no model, no shape. A hit\ntherefore crosses callers — similar prompts share an answer — and one model\nbehind one shape does not make it safe. With more shapes the first model to\nanswer answers for the other too: empty HTTP 200 across shapes, wrong-model\nbody within one. Warn whenever the cache is on, not only on multi-shape, then\nbuild. Wording: `references\u002Fmodes.md`; mechanism: `references\u002Fgotchas.md`.\n\n**Each entry is a SUFFIX match.** You write these, not the user — derive them\nfrom which endpoint their agent actually calls, and do not ask them to reason\nabout suffixes. Vertex's OpenAI-compatible endpoint and `api.openai.com` need\ndifferent entries, and getting it wrong governs nothing silently; the forms and\nthe evidence are in `references\u002Fgotchas.md`, Runtime.\n\n**There is no `apiShape` field.** Metering does not depend on the wire shape —\nthe count is normalised in JS across both — so the field selected nothing and\nwas removed. Adding a shape is an `||` branch in `extract-model.js` plus an\nentry in `matchPaths`.\n\n**Three places carry the callout hostname and all three must match** — the\nApigee envgroup, the AuthzExtension `authority`\u002F`service`, and the private DNS A\nrecord. You set all three from one value: a build invariant to hold, not a\nquestion for the user. It becomes a topic only when a callout never lands, and\nthen it is the first check. Build order matters alongside it — the\nAuthzExtension is inert until the host resolves and the proxy is deployed, so\n\"the extension exists\" is not evidence that leg works. `references\u002Fgotchas.md`,\nRuntime.\n\n## Workflow\n\n### 0. Gateway: give it a route into the VPC\n\nA gateway with no `networkConfig` has no route into the customer's VPC and\ncannot resolve the callout hostname. **This skill builds it** — network\nattachment, egress and DNS peering — so its absence is a build step, not a\nblocker, and should be reported that way. Do not open with an alarm about a\nresource you are about to create.\n\nOne thing here does need the user, and it is the reason this is step 0 rather\nthan a silent prerequisite: attaching egress is **one-way and affects traffic\nthey already run**. Settle it in the plan, before anything executes — see\n\"Confirm everything up front\" below. Capturing a known-good agent turn first is\nworth doing when they are nervous about a production gateway, because it\nseparates \"never worked\" from \"we broke it\"; it is a precaution to offer, not a\nstep to impose.\n\n```bash\ngcloud network-services agent-gateways describe \"$GATEWAY\" \\\n  --location=\"$REGION\" --project=\"$PROJECT\" --format='value(networkConfig)'\ngcloud network-security authz-policies list \\\n  --location=\"$REGION\" --project=\"$PROJECT\"\n```\n\n**A console-created gateway already has a CUSTOM AuthzPolicy** — an IAP\n`REQUEST_AUTHZ` one, which coexists with ours and is not needed for the egress\nJWT. You never create the gateway, so assess it rather than assuming.\n`references\u002Fgotchas.md`, Agent Gateway.\n\nEmpty `networkConfig` output means missing, and it is easy to miss: the gateway\nstill serves ordinary agent traffic perfectly, because that path goes to public\nGoogle endpoints and never enters the VPC. Only the Apigee callout fails.\n\nIf it is missing, behaviour depends on `gateway.manageNetworkConfig`:\n\n-   **`true`** — run the script below. It creates the network attachment, then\n    read-modify-writes the gateway to add the egress attachment and DNS peering.\n    Agreement for this is taken in the plan, not here; `--yes` carries it.\n-   **`false`** — the user has said they want to make this change themselves.\n    Print the script invocation and let them run it. Do not modify the gateway.\n\n```bash\nscripts\u002Fensure_gateway_connectivity.sh \\\n  --gateway \"$GATEWAY\" --project \"$PROJECT\" --region \"$REGION\" \\\n  --vpc \"$VPC\" --subnet \"$SUBNET\" --attachment \"$ATTACHMENT\" \\\n  --domain \"$DNS_DOMAIN\"            # add --yes only after the user agrees\n```\n\nEither way `networkConfig` has to exist before the Apigee work starts — not\nbecause its absence is an error, but because every later step then succeeds and\ngoverns nothing.\n\nUse the script, not the raw commands: `import` is a whole-resource upsert and a\nhand-built YAML silently drops the gateway's `registries` and `protocols`. Why,\nand the `etag` and `PROTOCOL_UNSPECIFIED` handling: `references\u002Fgotchas.md`.\n\n### 1. Apigee: enable the environment\n\nThe env must carry `apigee-service-extension-enabled=true`. No `gcloud` flag\nexists; PATCH the properties map.\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapigee.googleapis.com\u002Fv1\u002Forganizations\u002F$ORG\u002Fenvironments\u002F$ENV\"\n```\n\n**The PATCH replaces the whole map.** Read it first and re-send every property\nyou intend to keep, or you will silently drop them.\n\n```bash\ncurl -X PATCH -H \"Authorization: Bearer $TOKEN\" -H \"Content-Type: application\u002Fjson\" \\\n  \"https:\u002F\u002Fapigee.googleapis.com\u002Fv1\u002Forganizations\u002F$ORG\u002Fenvironments\u002F$ENV?updateMask=properties\" \\\n  -d '{\"properties\":{\"property\":[{\"name\":\"apigee-service-extension-enabled\",\"value\":\"true\"}]}}'\n```\n\nThis does **not** disable Apigee Analytics — the two coexist; if analytics looks\nmissing it is the separate per-environment add-on. `references\u002Fgotchas.md`.\n\n**Use a dedicated environment**, attached to **exactly one envgroup**. An\nenvironment in two makes the gateway 404 every request with the extension never\ninvoked. It also leaves the `\u002F` base path free, which is what the proxy ships\nwith.\n\n`scripts\u002Fensure_apigee_environment.sh` creates the environment, attaches it to\nthe regional runtime instance, and creates and attaches the envgroup — so step 2\nis already done when it succeeds. It creates billable resources and applies\nnothing without `--yes`; that agreement is taken in the plan, not here.\n\n```bash\nscripts\u002Fensure_apigee_environment.sh \\\n  --org \"$ORG\" --env \"$ENV\" --region \"$REGION\" \\\n  --envgroup \"$ENVGROUP\" --hostname \"$HOSTNAME\"      # add --yes to apply\n```\n\nIt is idempotent and resumable, and refuses with exit 2 when the environment is\nalready in another envgroup or the envgroup exists on a different hostname. It\nalso handles three things a runbook tends to get wrong — the omitted `type` on\ncreate, the four-minute instance attachment that **locks the environment**, and\npolling the operation rather than a `GET` that succeeds too early.\n\n### 2. Apigee: envgroup on the callout hostname\n\nDone already if step 1's script ran. Verify, or do it by hand for an envgroup\nthat already exists. The hostname must be exactly the callout host, and the\ngroup must be attached to the env.\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapigee.googleapis.com\u002Fv1\u002Forganizations\u002F$ORG\u002Fenvgroups\u002F$ENVGROUP\u002Fattachments\"\n```\n\n**How many envgroups is the environment in?** More than one and the callout\nnever lands. `deploy_proxy.sh` enforces this before touching anything and exits\n2 naming them; `assess_integration.sh` reports it. Read the failure literally if\nit fires — the environment is wrong, not the script.\n\n### 3. Build the proxy from the template\n\n```bash\ngcloud apigee apis import organizations\u002F$ORG\u002Fapis\u002F$PROXY \\\n  --from-template=assets\u002Fapigee-proxy-template.yaml\n```\n\nEach `features:` entry resolves **relative to the template file's own\ndirectory**, never the working directory, so a bare filename must sit beside the\ntemplate (a subpath like `features\u002Ffeature-identity.yaml` works too). `assets\u002F`\nis flat by convention, not compiler demand — but moving a file without updating\n`features:` fails the import with a bare `FileNotFoundError`.\n\nIf you get `Permission denied on resource ... (or it may not exist)` on an org\nyou demonstrably have access to, it is the credential, not the access. With\ncontext-aware access enabled the default gcloud credential is rejected. Pass the\nADC token instead:\n\n```bash\nCLOUDSDK_AUTH_ACCESS_TOKEN=$(gcloud auth application-default print-access-token) \\\n  gcloud apigee apis import ...\n```\n\nTo change a parameter, **edit the `default` in every FEATURE that declares it**\n— not in the template. `gcloud apigee apis import` passes an empty\ncaller-parameter map, so each feature resolves its own default and the\ntemplate's declaration of the same name is never read. Editing the template\nchanges nothing, silently, and the compile still passes. Measured: setting\n`MA_CONTINUE_ON_ERROR` to DRY_RUN in the template shipped ENFORCE on a\nfail-closed policy. `LLM_URI_MATCH` is declared in four features and all four\nmust agree. `scripts\u002Fcompile_check.py` fails on any disagreement. There is no\n`--parameters` flag.\n\nEnable an optional recipe by adding its feature to the template's `features`\nlist. See `references\u002Frecipes.md`.\n\n**`tokenMetering.models` in the input file is NOT generative.** It documents\nwhich buckets exist; it does not create them. A bucket is real only when it has\nan `LLMTokenQuota` step in `feature-token-metering.yaml` and a matching\n`*_TOKEN_LIMIT` parameter. Three exist: `gemini-pro`, `gemini-flash` and `other`\n— the catch-all for any model whose name contains neither `pro` nor `flash`.\nAdding a fourth entry to the list meters nothing, and **omitting `other`\nsilently unmeters every non-Gemini model**: the count is extracted and then\ndiscarded, with no `ratelimit` reference anywhere in the transaction. Adding a\nmodel is a feature-file edit, not a config change.\n\n### 4. Deploy\n\nDeploy to the service-extension env, then confirm the deployment reports\n`proxyDeploymentType: EXTENSIBLE`.\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n  \"...\u002Fenvironments\u002F$ENV\u002Fdeployments\"\n```\n\n`STANDARD` means it will **not** govern the agent's off-Apigee tool egress.\n\nIf any policy calls a Google API — Model Armor, embeddings, Vector Search —\ndeploy **with a service account** or the deploy is rejected with\n`MISSING_SERVICE_ACCOUNT`:\n\n```bash\ncurl -X POST -H \"Authorization: Bearer $TOKEN\" \\\n  \"...\u002Fapis\u002F$PROXY\u002Frevisions\u002F$REV\u002Fdeployments?override=true&serviceAccount=$DEPLOY_SA\"\n```\n\n### 5. Networking\n\nThis is the **gateway → Apigee** leg only. PSC endpoint to the Apigee instance\nservice attachment, plus a private DNS A record. See\n`references\u002Fnetworking-psc.md`. The Apigee instance `consumerAcceptList` must\ninclude your project or the connection never reaches `ACCEPTED`.\n\nThe **agent → gateway** leg is the prerequisite above, and is not built here.\n\n### 6. Bind the gateway\n\nAuthzExtension then AuthzPolicy — in that order, and only once the host resolves\nand the proxy is deployed. The extension is inert until then.\n\n**Both document bodies ship as assets. Copy and substitute; do not invent them**\n— `assets\u002Fauthz-extension.example.yaml` and `assets\u002Fauthz-policy.example.yaml`.\nThey carry the fields that are load-bearing and have no obvious default:\n`wireFormat: EXT_PROC_GRPC`, `failOpen`, the `authority`\u002F`service` pair, the\n`metadata` body flags, and the policy's `customProvider` \u002F `httpRules` nesting.\n\n```bash\ngcloud service-extensions authz-extensions import $AUTHZ_EXT \\\n  --source=authz-extension.yaml --location=$REGION --project=$PROJECT\ngcloud beta network-security authz-policies import $AUTHZ_POLICY \\\n  --source=authz-policy.yaml --location=$REGION --project=$PROJECT\n```\n\n**The policy's resource paths take the project NUMBER, not the ID** — both\n`target.resources` and `customProvider.authzExtension.resources`. Resolve it\nyourself; narrating two numbers at the user does not help. The trap, for when a\ncallout never lands: the number in the gateway's\n`serviceExtensionsServiceAccount` is the Google-managed tenant project, not\ntheirs, and it is the only one on screen, so it is the natural thing to copy. A\nwrong number imports cleanly and targets nothing. Copy the slots from\n`assets\u002Fauthz-policy.example.yaml`.\n\n### 7. Verify\n\nYou **cannot** test with curl — the host is internal and the proxy expects the\ngateway's IAP-egress JWT. Run `scripts\u002Fread_debug_session.sh --arm`, drive a\nturn, read it back with `--session`. Expect **one** transaction per turn per\ngoverned shape under a suffix list this narrow; zero alone does not mean the\ncallout is dead — the script prints the probe that settles it.\n\n## Hard rules\n\n**Gate every step — how urgently depends on your AuthzPolicy.** An ungated\n`continueOnError=\"false\"` policy faults on any call that carries no prompt, and\nthat **breaks the agent's turn**. Historically the most common failure here.\n\nA narrow suffix list routes only the model call to Apigee, so the trap mostly\ndoes not fire. Broaden the policy and it fires at once: the ext-proc then sees\nevery agent egress — session create\u002Fread, `appendEvent`, OpenTelemetry exports —\nmeasured at 12 calls for one single-question turn, of which one was the model\ncall. Gate the steps regardless; a condition is cheap and it is what makes the\npolicy safe to widen later. Full measurement: `references\u002Fgotchas.md`.\n\n-   model call: `(request.uri Like \"*generateContent*\")`\n\n**Never fail-close on your own metering.** Keep `continueOnError=\"true\"` on the\nJavaScript and quota policies so a metering bug cannot break LLM traffic.\n\n**This skill meters by default and can enforce on request.** Enforcement is\n`assets\u002Ffeature-token-enforce.yaml`: request-flow `EnforceOnly` policies sharing\neach `SharedName` with the response-flow `CountOnly` half. Add it to the\ntemplate's feature list. Verified live on ext-proc traffic — the over-budget\ncaller gets a **429**, model never called.\n\nNever offer `CountOnly=false` as a substitute: in the response flow it cannot\ngate (the answer is already on the wire) and it freezes the counter, losing the\nmetering too.\n\nState two things whenever you offer it. It is **one request late** — the turn\nthat crosses the limit completes, because a token cost is not known until the\nmodel answers. And it buckets on the **requested** model, which under an OpenAI\nalias can differ from the resolved model the counting side bills. The two traps\nthat both present as \"it never refuses\": `references\u002Fgotchas.md`.\n\n**Normalize the model to a bucket.** With an OpenAI-compatible endpoint the\nmodel is an alias in the request body and the *resolved* name is in the response\nbody (`google\u002Fgemini-2.5-pro`). Exact-matching a raw name fails on the response\npath.\n\n**`jsonPath` needs its third argument `true`** so it returns a scalar. Without\nit the quota policy receives a node it cannot parse.\n\n## References\n\n-   `references\u002Fmodes.md` — **read first.** How to answer \"what can you do\", dev\n    vs prod, the defaults, durations, cost disclosure, the interlocks, and\n    reading an assess report\n-   `scripts\u002Fassess_integration.sh` — **read-only.** Setup Status Summary then a\n    per-check table; `--json` carries both. Lead with the summary and read out\n    the **Enforcement** row. Run it before planning: it decides CREATE vs REUSE.\n    `TODO` is a build step, not a failure; only `FAIL` exits non-zero\n-   `references\u002Fnetworking-psc.md` — both network legs, PSC, DNS, acceptance\n-   `references\u002Frecipes.md` — Model Armor and semantic cache\n-   `references\u002Fverify.md` — debug sessions and the checklist script\n-   `references\u002Fsetup.md` — SDK, project, billing, APIs, region, auth, IAM\n-   `references\u002Fgotchas.md` — 63 failure modes and their symptoms\n-   `assets\u002Fapigee-agent-gateway-governance.example.yaml` — the input contract\n-   `assets\u002Fapigee-proxy-template.yaml` — the AFT template. Features merge by\n    `priority` and all sit beside it: `feature-identity.yaml` (10),\n    `feature-normalize.yaml` (15), `feature-model-armor.yaml` (20),\n    `feature-semantic-cache.yaml` (30), `feature-token-metering.yaml` (50).\n-   `assets\u002Fauthz-extension.example.yaml`, `assets\u002Fauthz-policy.example.yaml` —\n    the step-6 bodies. Copy and substitute; paths take the project **number**\n\n    **Anything read out of the request body is normalised once, at priority 15,\n    not inside whichever feature needs it first.** `LLM_URI_MATCH` is shared, so\n    widening it arms every feature gated on it simultaneously — including Model\n    Armor, which fails closed. Model Armor and the cache both read\n    `agw.user_prompt`; the quotas read `agw.total_tokens`, set on the response\n    path by the metering feature's own JS for the same reason.\n-   `scripts\u002Fensure_apigee_environment.sh` — create the environment, attach it\n    to the instance, create and attach the envgroup. Idempotent, `--yes` gated\n-   `scripts\u002Fensure_gateway_connectivity.sh` — route the gateway into the VPC. Read-modify-write; `--yes` gated\n-   `scripts\u002Fensure_vector_search.sh` — only when `semanticCache` is enabled:\n    build index and endpoint, deploy one on the other, read back the domain.\n    `--yes` gated, ~34 min, bills continuously\n-   `scripts\u002Fteardown_vector_search.sh` — the only thing that STOPS that charge. Refuses until the revision drops `SCL-lookup`. `--yes` gated\n-   `scripts\u002Fdeploy_proxy.sh` — the Apigee-side sequence, idempotent, `--dry-run`\n-   `scripts\u002Fread_debug_session.sh` — `--arm` a session (printing when it\n    actually closes) and read it back as the `verify.md` checklist, instead of\n    parsing the transaction JSON by hand\n-   `scripts\u002Fcompile_check.py` — compile the template offline, no org needed\n",{"name":5,"metadata":52,"description":7},{"version":53,"category":54},"1.0.2","AiAndMachineLearning","main","bb9b3f464aae37af360d079c1d863524955b0363","skills\u002Fcloud\u002Fapigee-agent-gateway-governance",2005,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills","https:\u002F\u002Fgithub.com\u002Fgoogle","https:\u002F\u002Fopensource.google\u002F","Google ❤️ Open Source","2026-11-14 01:51:35",{"source":8,"skillName":5,"commands":65,"prompt":75,"zipAvailable":76},[66,69,72],{"tool":67,"command":68},"npx","npx skills add google\u002Fskills --skill apigee-agent-gateway-governance",{"tool":70,"command":71},"bunx","bunx skills add google\u002Fskills --skill apigee-agent-gateway-governance",{"tool":73,"command":74},"pnpm","pnpm dlx skills add google\u002Fskills --skill apigee-agent-gateway-governance","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002FSKILL.md",true,[78,84,88,92,96,100,104,108,112,116,120,126,130,134,138,142,146,150,156,160,164,168,172,176,180],{"path":79,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":82,"sourceHtmlUrl":83},"assets\u002Fapigee-agent-gateway-governance.example.yaml","ASSET","资产文件",21328,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fassets\u002Fapigee-agent-gateway-governance.example.yaml",{"path":85,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":86,"sourceHtmlUrl":87},"assets\u002Fapigee-proxy-template.yaml",9575,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fassets\u002Fapigee-proxy-template.yaml",{"path":89,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":90,"sourceHtmlUrl":91},"assets\u002Fauthz-extension.example.yaml",1729,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fassets\u002Fauthz-extension.example.yaml",{"path":93,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":94,"sourceHtmlUrl":95},"assets\u002Fauthz-policy.example.yaml",2105,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fassets\u002Fauthz-policy.example.yaml",{"path":97,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":98,"sourceHtmlUrl":99},"assets\u002Ffeature-identity.yaml",6150,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fassets\u002Ffeature-identity.yaml",{"path":101,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":102,"sourceHtmlUrl":103},"assets\u002Ffeature-model-armor.yaml",3562,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fassets\u002Ffeature-model-armor.yaml",{"path":105,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":106,"sourceHtmlUrl":107},"assets\u002Ffeature-normalize.yaml",4131,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fassets\u002Ffeature-normalize.yaml",{"path":109,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":110,"sourceHtmlUrl":111},"assets\u002Ffeature-semantic-cache.yaml",8109,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fassets\u002Ffeature-semantic-cache.yaml",{"path":113,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":114,"sourceHtmlUrl":115},"assets\u002Ffeature-token-enforce.yaml",16726,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fassets\u002Ffeature-token-enforce.yaml",{"path":117,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":118,"sourceHtmlUrl":119},"assets\u002Ffeature-token-metering.yaml",15242,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fassets\u002Ffeature-token-metering.yaml",{"path":121,"resourceType":122,"resourceTypeDesc":123,"sizeBytes":124,"sourceHtmlUrl":125},"references\u002Fattribution.md","REFERENCE","参考文档",1945,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Freferences\u002Fattribution.md",{"path":127,"resourceType":122,"resourceTypeDesc":123,"sizeBytes":128,"sourceHtmlUrl":129},"references\u002Fgotchas.md",44755,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Freferences\u002Fgotchas.md",{"path":131,"resourceType":122,"resourceTypeDesc":123,"sizeBytes":132,"sourceHtmlUrl":133},"references\u002Fmodes.md",47566,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Freferences\u002Fmodes.md",{"path":135,"resourceType":122,"resourceTypeDesc":123,"sizeBytes":136,"sourceHtmlUrl":137},"references\u002Fnetworking-psc.md",10244,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Freferences\u002Fnetworking-psc.md",{"path":139,"resourceType":122,"resourceTypeDesc":123,"sizeBytes":140,"sourceHtmlUrl":141},"references\u002Frecipes.md",9640,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Freferences\u002Frecipes.md",{"path":143,"resourceType":122,"resourceTypeDesc":123,"sizeBytes":144,"sourceHtmlUrl":145},"references\u002Fsetup.md",7365,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Freferences\u002Fsetup.md",{"path":147,"resourceType":122,"resourceTypeDesc":123,"sizeBytes":148,"sourceHtmlUrl":149},"references\u002Fverify.md",14693,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Freferences\u002Fverify.md",{"path":151,"resourceType":152,"resourceTypeDesc":153,"sizeBytes":154,"sourceHtmlUrl":155},"scripts\u002Fassess_integration.sh","SCRIPT","脚本",36324,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fscripts\u002Fassess_integration.sh",{"path":157,"resourceType":152,"resourceTypeDesc":153,"sizeBytes":158,"sourceHtmlUrl":159},"scripts\u002Fcompile_check.py",10143,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fscripts\u002Fcompile_check.py",{"path":161,"resourceType":152,"resourceTypeDesc":153,"sizeBytes":162,"sourceHtmlUrl":163},"scripts\u002Fdeploy_proxy.sh",24261,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fscripts\u002Fdeploy_proxy.sh",{"path":165,"resourceType":152,"resourceTypeDesc":153,"sizeBytes":166,"sourceHtmlUrl":167},"scripts\u002Fensure_apigee_environment.sh",18249,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fscripts\u002Fensure_apigee_environment.sh",{"path":169,"resourceType":152,"resourceTypeDesc":153,"sizeBytes":170,"sourceHtmlUrl":171},"scripts\u002Fensure_gateway_connectivity.sh",21665,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fscripts\u002Fensure_gateway_connectivity.sh",{"path":173,"resourceType":152,"resourceTypeDesc":153,"sizeBytes":174,"sourceHtmlUrl":175},"scripts\u002Fensure_vector_search.sh",19407,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fscripts\u002Fensure_vector_search.sh",{"path":177,"resourceType":152,"resourceTypeDesc":153,"sizeBytes":178,"sourceHtmlUrl":179},"scripts\u002Fread_debug_session.sh",15845,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fscripts\u002Fread_debug_session.sh",{"path":181,"resourceType":152,"resourceTypeDesc":153,"sizeBytes":182,"sourceHtmlUrl":183},"scripts\u002Fteardown_vector_search.sh",9242,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fapigee-agent-gateway-governance\u002Fscripts\u002Fteardown_vector_search.sh",[185,218,247,273,300,323],{"skillNo":186,"name":187,"nameZh":6,"description":188,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":189,"categories":190,"keywords":195,"publishedAt":47},"01M6998S2EZKD3GEJ7F943RZTE","sign-in-with-google-web","Implement, configure, and secure Sign In With Google (SiwG) using Google Identity Services (GIS \u002F `https:\u002F\u002Faccounts.google.com\u002Fgsi\u002Fclient`) across web architectures. Use when creating Google sign-in buttons, implementing Google One Tap with FedCM, integrating GIS in React\u002FNext.js\u002FAngular\u002FHTML, verifying ID tokens on backend runtimes (Python\u002FNode.js\u002FGo\u002FJava), enforcing Google Workspace domain restrictions (`hd`), securing client-side ID tokens via WebCrypto nonces or encrypted IndexedDB, embedding in cross-origin iframes via the Intermediate Iframe API (`https:\u002F\u002Faccounts.google.com\u002Fgsi\u002Fintermediate`), configuring Content Security Policy (CSP), COOP, and Permissions Policy (`identity-credentials-get`) headers, or handling sign-out and revocation. Don't use for legacy Google Sign-In (`gapi.auth2`), Google Cloud IAM service accounts, or Android\u002FiOS native Credential Manager.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fidentity\u002Fsign-in-with-google-web\u002FSKILL.md",[191,194],{"categoryNo":192,"categoryName":193,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[9,196,197,198,199,200,201,202,203,46,204,205,206,207,208,209,210,211,212,213,214,215,216,217],"sign in with google web","sign","with","web","android","go","security","google workspace","implement","policy","python","implementing","react","iam","permissions","buttons","secure","manager","identity","architectures","domain","runtimes",{"skillNo":219,"name":220,"nameZh":6,"description":221,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":222,"categories":223,"keywords":231,"publishedAt":47},"01M6998PHPDCAD684VW5WRG83D","dpop-adoption","Implement and debug OAuth 2.0 DPoP (RFC 9449) refresh token sender-constraining for WebCrypto, Node.js ES6, and browser runtimes integrating with Google's OAuth platform. Use when configuring non-extractable asymmetric key pairs (P-256), generating DPoP Proof JWTs for authorization code exchange and token refresh, or handling 400 use_dpop_nonce challenge retry loops at oauth2.googleapis.com\u002Ftoken. Don't use for unconstrained OAuth 2.0 flows (where refresh tokens are not bound to a client key pair), or for Google Cloud IAM \u002F service account authentication.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fidentity\u002Fdpop-adoption\u002FSKILL.md",[224,225,226],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":227,"categoryName":228,"taxonomyType":229,"taxonomyTypeDesc":230},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[232,233,9,46,234,235,236,237,238,239,209,240,241,242,243,244,245,214,217,246,32],"dpop adoption","adoption","es6","retry","authentication","flows","challenge","code","browser","loops","at","node","token","pair","platform",{"skillNo":248,"name":249,"nameZh":6,"description":250,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":251,"categories":252,"keywords":259,"publishedAt":47},"01M6998M72TC73EEAQH4XC7EXW","retrieving-developer-knowledge","Searches, retrieves, and synthesizes official Google developer documentation across Google Cloud, AI\u002FGemini, Android, Chrome, Web, Flutter, Go, Firebase, and other Google developer platforms. Integrates with the Developer Knowledge MCP server (search_documents, get_documents, answer_query) or the Developer Knowledge REST API fallback. Use when searching for gcloud CLI commands, API syntax, IAM permissions, official documentation, architectural comparisons, or product choice overviews. Don't use for local filesystem lookups or non-Google documentation.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdevelopers\u002Fretrieving-developer-knowledge\u002FSKILL.md",[253,254,257,258],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":255,"categoryName":256,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000021","资料检索与研究",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":227,"categoryName":228,"taxonomyType":229,"taxonomyTypeDesc":230},[260,261,262,42,46,45,263,264,265,266,267,268,269,32,270,44,271,209,272,210],"retrieving developer knowledge","knowledge","developer","official","developer documentation","get","search","local","choice","documents","query","across","documentation",{"skillNo":274,"name":275,"nameZh":6,"description":276,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":277,"categories":278,"keywords":279,"publishedAt":47},"01M6998J2CZEXS9BES9S3WWFKC","migrate-to-rust","Clones, analyzes, plans, and migrates legacy codebases (C++, Fortran, Java, Python, C, Shell) from a GitHub URL or local directory to a modern, high-performance, deterministic, 100% Safe Rust architecture, tracking live telemetry across every task bucket, test failure\u002Ffix cycle, and Profile-Guided Optimization (PGO) stage, and producing a first-person AI storytelling report and interactive HTML infographic. Use when migrating a GitHub repository or local codebase to Rust, porting legacy C++, Fortran, Python, or Java workloads to memory-safe Rust, designing Struct-of-Arrays (SoA) or SIMD Rust architectures, or optimizing Rust ports with LLVM PGO. Don't use for unrelated non-Rust refactoring or single-line syntax questions.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdevelopers\u002Fmigrate-to-rust\u002FSKILL.md",[],[280,281,282,283,284,285,286,287,206,288,289,290,291,292,293,267,294,295,296,32,297,298,271,299],"migrate to rust","rust","migrate","github","memory","performance","directory","safe","ports","llvm","soa","cycle","porting","fix","arrays","infographic","high-performance","URL","architecture","unrelated",{"skillNo":301,"name":302,"nameZh":6,"description":303,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":304,"categories":305,"keywords":309,"publishedAt":47},"01M6998GCC3K29WZ82KXMB9E02","finding-google-skills","Google platform decision and setup guidance, loaded on demand from Google's skill catalog. Use when a developer is choosing or setting up part of their stack, such as where to run a service, a database, storage, messaging, authentication, analytics, ads, or AI model serving, and a Google product is a reasonable candidate - whether or not a vendor is named - or when a request names a Google product or API. Brings in the matching Google skill so the answer can weigh Google options, their trade-offs, and when they are not the right fit. Skip when the stack is already settled on another provider and no Google product is named, or the task involves no platform choice.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdevelopers\u002Ffinding-google-skills\u002FSKILL.md",[306,307,308],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":227,"categoryName":228,"taxonomyType":229,"taxonomyTypeDesc":230},[9,310,311,312,313,246,43,314,315,316,317,236,318,40,319,268,320,321,32,322,44],"finding google skills","storage","as","messaging","trade-offs","provider","answer","fit","catalog","decision","use","database","candidate",{"skillNo":324,"name":325,"nameZh":6,"description":326,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":327,"categories":328,"keywords":331,"publishedAt":47},"01M6998DWNHTH0BS6W1CY8ZXMP","workload-manager-basics","Use this skill to manage Google Cloud Workload Manager evaluations, rules, scanned resources, and validation results by using public client libraries and the REST API. Use when you need to inspect workload best-practice rules, create and run evaluations for Google Cloud general best practices, SAP, SQL Server, or custom organizational rules, review violations, export results to BigQuery, or automate Workload Manager through client libraries because no service-specific public CLI or MCP server is available. Don't use for general Google Compute Engine instance management, VPC configuration, or standard IAM auditing.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fworkload-manager-basics\u002FSKILL.md",[329,330],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},[332,333,213,334,9,42,335,46,45,336,337,338,34,32,37,44,209,339,340,341,342],"workload manager basics","workload","basics","cli","review","vpc","resources","create","libraries","server","run"]