AI Agents

  • Added

    • gateway.jwksEgress.external.fqdns, .cidrs and .port (443), in both charts: the destinations the agentgateway controller may reach for a JWKS the routes do not name, and for an issuer reached by address rather than by name. fqdns takes Cilium FQDN selectors (matchName, matchPattern), read by the cilium flavour and behind the DNS proxy rule its policy renders; the kubernetes flavour selects addresses and never names, so it ignores them. cidrs takes IP blocks of either family in both flavours, and is also the only way to reach what the kubernetes flavour cannot reach by name — its wide rule for a name is 0.0.0.0/0 and covers public IPv4 only, so a private identity provider inside worldExcludedCIDRs and an issuer resolved over IPv6 belong there. Each list is its own rule on .port, independent of gateway.jwksEgress.enabled. Both lists are typed: a cidrs block of either family no API server accepts fails the render, and an fqdns item is a selector object, never a bare string the Cilium CRD refuses.

    Changed

    • jwtAuthentication.jwks.host is a hostname, and seven shapes that rendered green now fail the render (#312). Each reached runtime as 401 token uses the unknown key on every request, because the controller fetched no keys: an empty host; an empty jwks.port, which renders a backend the API server refuses; a host that carries a port (dex.example.com:5556), which belongs in jwks.port; an address literal of either family (198.51.100.7, 2001:db8::1, 1.2.3.999), which the controller cannot select by name — name its blocks in gateway.jwksEgress.external.cidrs instead; a host that is no hostname, a scheme or the JWKS path glued on (accounts.google.com/keys) and an empty or hyphen-terminated label (a..b.example.com, keys-.example.com) included, where the kubernetes flavour still opened its wide rule and hid the failure; and a host of fewer than three labels (dex, dex.giantswarm, okta.com), which is neither a qualified Service name nor a public issuer — a short Service name resolves through the pod’s search path, which the controller’s egress rule cannot follow. The route subtrees are open objects in values.schema.json, so these are template guards; each names the value to set. The last one follows networkPolicy.enabled: with no policy rendered every destination is reachable and the classification decides nothing.

    Fixed

    • The agentgateway controller’s network policy reaches every external JWKS host the jwtAuthentication routes name, in both flavours (#312). The controller fetches each policy’s JWKS and pushes the keys to the data plane over xDS, so this is the controller’s egress; the data-plane policies do not change. The policy reads kagent.controllerRoute, modelManager.route and agentManager.route’s jwtAuthentication.jwks and opens each host that is not in-cluster on its own port: a toFQDNs matchName in the cilium flavour, an address block minus networkPolicy.kubernetes.worldExcludedCIDRs in the kubernetes flavour. The kubernetes flavour selects addresses and never names, so one named external host widens the controller’s egress to every public IPv4 destination on that port, minus the excluded blocks — and only while networkPolicy.kubernetes.apiServerCIDR is a real API-server block, because its default 0.0.0.0/0 on every port already covers that. An installation that wants it narrow names the issuer’s blocks in gateway.jwksEgress.external.cidrs. gateway.jwksEgress.enabled is now demanded for an in-cluster jwks.host only. A host counts as in-cluster when its third dot-separated label is svc and the labels after it are empty, cluster or cluster.local — the forms the pod’s search path produces. A public host that carries an svc label elsewhere (a.b.svc.example.com) is external and gets its own rule. Every host is classified and selected in its normalized form — lower case, root dot removed — so dex.giantswarm.svc.cluster.local. names the Service it looks like and no longer widens the controller’s egress to every public destination on the JWKS port. A route whose jwks.port is 443 originates TLS to the issuer without jwks.tls.enabled: the port serves no plain HTTP, so the fetch no longer returns no keys. TLS implied by the port verifies against jwks.tls.caSecretName or the system trust, never global.identity.ca.secretName: that key is the platform identity provider’s own CA, it signed no public issuer’s certificate, and pinning it would fail the fetch with the same 401. jwks.tls.enabled: true names that provider deliberately and keeps the fallback. No installation needs to act: with in-cluster JWKS hosts only, the whole render is byte-identical to before in both flavours, which make verify-wiring asserts against origin/main.
  • Changed

    • (verify) Verify-migration reads the migrate Job’s image pin from the charts’ values (agentManager.migration.image.tag) instead of a literal, and asserts the meta chart’s BOM pin and the connectivity default agree — a Renovate bump of the pin no longer breaks the check in #379 by @teemow

    Full Changelog: https://github.com/giantswarm/agent-platform/compare/v4.7.0...v4.7.1

  • Added

    • llmRouting.modelConfigPolicy.enabled (default true): a MutatingAdmissionPolicy and its binding that point a ModelConfig at the LLM listener when the object names no baseUrl of its own. It reaches the ModelConfigs this chart does not render — the ones Backstage, agent-manager or kubectl create — which until now called the provider directly and produced no token, cost or latency series at all; the chart’s own entries already carry the value, so the policy never touches them. It writes only when the object’s provider matches llmRouting.backend.provider and the baseUrl of that provider’s block is absent, on CREATE and UPDATE, scoped to the kagent namespace, with failurePolicy: Ignore — an unmetered model costs money, a refused write costs an outage. An object that names its own baseUrl is never touched, which is how a model reaches the provider directly. Rendered only where the API server serves admissionregistration.k8s.io/v1/MutatingAdmissionPolicy, Kubernetes 1.36 and later: on 1.34 and 1.35 the API is beta and its own feature gate is off by default, so the object would exist and mutate nothing, and an absent object is honest where a silent one is not. The three management clusters run 1.35 and render nothing; agentlab boots kindest/node:v1.36.1 and gets the policy today. Measured on a throwaway 1.36 kind cluster: a ModelConfig with no baseUrl is routed, one naming the provider URL keeps it across a later update, a non-Anthropic one is untouched, and a sibling field under spec.anthropic survives the merge. The policy renders only where the routed provider’s ModelConfigSpec block carries a baseUrl — Anthropic, OpenAI and SAPAICore; the seven other providers name their upstream through their own fields, and a block the CRD does not know is pruned at admission. The meta chart declares the key with the same default, so an installation sets it through the umbrella. make verify-llm-routing asserts the render on both API-server shapes and the toggle, and make verify-meta’s schema-symmetry check asserts the key travels.

    Changed

    • A chart-owned ModelConfig rides the LLM listener by default. kagent.modelConfigs[].baseUrl used to be the only way to route an entry, so an entry added without it called the provider directly and its usage never reached the gateway’s metrics, with no error to say so. With llmRouting.enabled the chart now fills the key in from gateway.name, the release namespace and llmRouting.listener.port; an explicit baseUrl still wins and is how an entry reaches any other upstream, the provider’s own URL included. The baseUrl goes under the block of the entry’s own provider rather than under kagent.providers.default lower-cased, which is the same key for a single-provider Anthropic catalog and the correct one for a mixed catalog — the block name is anthropic, openAI or sapAICore (agent-platform.modelConfigBaseUrlKey), the three the CRD gives a baseUrl, and the render fails when an entry names a baseUrl for any other provider instead of rendering a block the API server prunes; an entry whose provider differs from llmRouting.backend.provider stays direct either way, because the listener forwards to one provider. No installation needs to act: every ModelConfig on the fleet already names the listener explicitly and renders byte-identically. make verify-llm-routing asserts the default, the explicit override, the foreign provider and the provider without a baseUrl.
  • Fixed

    • The kagent line re-pins to v0.11.0-gs.3 (giantswarm/giantswarm#37010): the line’s second automated re-pin (sync-upstream.yaml, 2026-09-11) moved the consumed branch onto upstream kagent-dev/kagent main @ 6a6b54ff — kagent-dev/kagent#2786 (the Go ADK exports a turn’s spans before its terminal event leaves the process), #2785 (the chart disables controller metrics when bindAddress is empty or 0), #2797 (the built-in tool server’s URL uses the kagent-tools namespace) — with the 21 carried patches replayed clean, none dropped. v0.11.0-gs.3 (commit ed07617b) is that head. kagent.tag moves to 0.11.0-gs.3 (the controller and UI image build) and kagent.harness.image to the gs.3 Go ADK index digest (meta and connectivity charts); components.kagent/components.kagent-crds keep the range >=0.11.0-gs.1 <0.11.1-0; the GRPCRoute service list is unchanged (no proto/ change at the pin). Lab-proven on agentlab against the same tree (dev build …hed07617, the re-pinned build as charts, images and both platform Harness digests): platform-test, agents-test, toolsets-test --skip-portal and skills-test all green — agents reach Ready on the new Go ADK Harness, an A2A turn through the edge and a git-skill golden boot included. examples/customer-bom.yaml and tests/verify-kagent-crds.py (KAGENT_LINE_REF) move with the pin. No installation acts — the release stays inert behind the fleet’s <4.0.0 bound.
  • Changed

    • The kagent controller GRPCRoute matches by gRPC service again — one service-only match per service of kagent.controllerRoute.grpc.services (the five services with empty method lists by default), which the agentgateway controller of chart 2.1.1 (the Giant Swarm line’s v1.5.1-gs.3, carrying the GRPCRoute translation fix) turns into the path prefix /<service>/; a service with RPCs listed keeps one exact service/method match per RPC, the fallback for an installation whose agentgateway chart is older and a way to expose a subset. The 39-RPC enumeration #345 shipped as a workaround is gone from the default; an RPC the kagent line adds is reachable without a values change. Needs the agentgateway component at chart ≥ 2.1.1 (the meta chart’s components.agentgateway.versionRange 2.x resolves it; the render cannot check the controller’s version). make verify-kagent-route asserts the default and the fallback.
  • Changed

    Full Changelog: https://github.com/giantswarm/agent-platform/compare/v4.5.3...v4.5.4

  • Changed

    • (ats) Kind cluster configuration for the chart tests (.ats/kind-config.yaml) in #365 by @teemow

    Full Changelog: https://github.com/giantswarm/agent-platform/compare/v4.5.2...v4.5.3

  • Fixed

    • components.backstage.versionRange is >=1.0.0 <3.0.0 (was 1.x). The complete kagent API v2 Dev Portal is backstage 2.0.0 — giantswarm/backstage#2344 (the backend’s Connect client against the controller’s gRPC origin, the portal’s sessions and chat) was released as a major, on top of 1.0.0 (the reader, #2343), 1.1.0 (create, #2296) and 1.2.0 (edit/delete, #2298). 1.x resolved 1.2.0, a portal without the gRPC backend that answers the 4.0 controller route with 404s; the range now admits 2.0.0 and stays bounded below the next major. tests/verify-components.py and UPGRADE.md move with it. No installation acts — the component is off by default and the release stays inert behind the fleet’s <4.0.0 bound.
  • Fixed

    • The kagent line re-pins to v0.11.0-gs.2 (giantswarm/giantswarm#37010, giantswarm/agent-platform#345 criterion 5). The line’s first release v0.11.0-gs.1 (commit 0ac5240) predated the two carried patches the platform depends on, so 4.0 shipped a controller image without them: the trusted-proxy authenticator (giantswarm/kagent-upstream#9 — the controller re-derives the caller from the bearer’s verified email claim instead of trusting X-User-Id) and helm.sh/resource-policy: keep on the five kagent-crds CRD templates (#10 — an uninstall no longer deletes the CRDs and every AgentTemplate/RemoteMCPServer with them). v0.11.0-gs.2 (commit 3bdff381) carries both. kagent.tag moves to 0.11.0-gs.2 (the controller and UI image build) and kagent.harness.image to the gs.2 Go ADK index digest (meta and connectivity charts); components.kagent/components.kagent-crds keep the range >=0.11.0-gs.1 <0.11.1-0 (a prerelease floor Masterminds admits gs.2 above). With the controller on gs.2, agent-platform#345 criterion 5 holds: reached directly, bypassing agentgateway, a request with a forged x-user-id is attributed to the bearer’s email, not the header (kagent.controller.auth.mode: trusted-proxy, userIdClaim: email, unchanged 4.0 defaults). Lab-proven on agentlab against the gs.2 build: a port-forward to kagent-controller, kagent.api.v1alpha1.SystemService/GetCurrentUser returned the admin bearer’s email while a forged x-user-id: someone-else@evil.test was ignored, a bearer-less call was refused (Unauthenticated), /health stayed 200, and the five CRDs carried helm.sh/resource-policy: keep. examples/customer-bom.yaml and tests/verify-kagent-crds.py (KAGENT_LINE_REF) move with the pin. No installation acts — the release stays inert behind the fleet’s <4.0.0 bound.
  • Changed

    Full Changelog: https://github.com/giantswarm/klaus/compare/v0.1.10...v0.1.11