AI Agents

  • Fixed

    • Uninstalling the platform through a cluster’s own Flux no longer races the Substrate CRD chart (giantswarm/agent-platform#385). With the engine off nothing orders the component uninstalls — helm-controller finalizes the platform HelmReleases the chart rendered concurrently — and substrate-crds could go before substrate, whose uninstall then failed for good on its SandboxConfig (failed to delete release: substrate: Helm cannot delete an object whose kind is gone) and was retried until the meta HelmRelease’s finalizer timed out (measured on the ATS own-Flux scenario, PR #380). The Substrate line re-pins to v0.0.27-gs.3 (components.substrate/substrate-crds >=0.0.27-gs.3 <0.0.28-0, kagent.substrateWorkerPool.workerImage 0.0.27-gs.3, examples/customer-bom.yaml, tests/verify-components.py): its one new carried patch (giantswarm/substrate#12, upstream-first, prepared for kagent-dev/substrate in giantswarm/giantswarm#37742 row 28) puts helm.sh/resource-policy: keep on the three ate.dev CRD templates, set at the generator as the kagent line does (giantswarm/kagent-upstream#10) — so every CRD chart of the platform keeps its CRDs and a consumer’s uninstall can always delete its CRs, whatever order the releases go in. The ATS own-Flux scenario’s way back is a test now (test_the_way_back_uninstalls_through_the_clusters_flux): the meta HelmRelease deleted, every platform release uninstalled concurrently by the cluster’s Flux with no UninstallFailed, the eight kept CRDs in place, no SandboxConfig or WorkerPool left — the delete_platform_releases_in_waves workaround is gone; the smoke’s ordered teardown asserts the same keep policy on ate.dev. Rejected: rendering the teardown-releases hook with the engine off (a cluster-admin hook in the fleet render for an order that stops mattering) and waiting for a Flux-side reverse-dependsOn uninstall (upstream’s open fluxcd/flux2#1744). README “Clusters that run Flux”, “Uninstalling”, “CRD lifecycle” and UPGRADE.md describe the uninstall order and what a kept CRD implies. No installation acts — the release stays inert behind the fleet’s <4.0.0 bound.
  • Removed

    • The portal’s app-config carries no agentPlatform.musterMcpUrl (giantswarm/agent-platform#299): the connectivity chart rendered the platform’s muster MCP URL into the Backstage app-config next to fluxServiceAccountName, but the Dev Portal reads no such key — it creates agents through agent-manager’s tools, and create_agent takes no muster argument. Where muster is reaches agent-manager alone: the meta chart derives its chart value muster.url from the agent-platform.musterMcpUrl helper (unchanged), agent-manager composes it into every agent’s RemoteMCPServer and reports it in get_info. make verify-wiring asserts the key stays gone; verify-identity keeps asserting the agent-manager leg. No installation acts — nothing read the key.
  • Changed

    • The ATS smoke runs the agent path on kagent API v2 (giantswarm/agent-platform#343): tests/ats/values-kagent.yaml turns kagent and agent-manager back on; Agent Substrate comes from the chart under test (one gVisor worker, the bundled RustFS as the snapshot store, the bundled Postgres at CI requests) on the CI job’s kind cluster with the ClusterTrustBundle, ClusterTrustBundleProjection and PodCertificateRequest gates (.ats/kind-config.yaml, the large class). The assertions are kagent.dev/v1alpha3: a declarative AgentTemplate reaches Ready on the platform Harness (status.harnesses[]), agent-manager’s create_agent through muster as a Dex user ends in a Ready HelmRelease, a Ready AgentTemplate and an Accepted per-agent RemoteMCPServer carrying the toolset header, the own-Flux agent likewise; the ordered teardown uninstalls the kagent-crds release and asserts the CRDs and the templates survive (the line’s keep policy) and nothing else does. The KAGENT_ON/AGENT_MANAGER_ON skips are gone. tests/ats/README.md documents the cluster shape, the sizing and the measured timings.

    Fixed

    • The ordered teardown uninstalls the platform releases in reverse dependency order. The pre-delete hook deleted every platform HelmRelease at once, so helm-controller uninstalled them concurrently and a CRD chart (substrate-crds, kagent-crds) could go before the releases whose objects are its CRs — Helm then fails the consumer’s uninstall (failed to delete release: substrate: its SandboxConfig kind was gone), helm-controller retries it forever and the hook waits out its timeout, failing helm uninstall. Found by the ATS smoke the moment it ran kagent and Substrate (giantswarm/agent-platform#343). The hook now deletes in waves computed from the roster’s dependsOn (agent-platform.teardownWaves: each wave’s releases concurrently, the next wave once the last is gone), as a script in gitops.hooks.helmImage; make verify-engine asserts the wave order against the rendered dependsOn.
  • Changed

    • (deps) Update gsoci.azurecr.io/giantswarm/agent-manager docker tag to v1.1.1 in #363 by @renovate[bot]

    Full Changelog: https://github.com/giantswarm/agent-platform/compare/v4.7.3...v4.7.4

  • Changed

    • (ci) The test-ingress-modes tool-install step bounds and retries its network calls (apt with timeout, helm download with curl retries) so a stalled mirror fails fast instead of hanging into CircleCI’s 10-minute no-output timeout in #382 by @teemow

    Full Changelog: https://github.com/giantswarm/agent-platform/compare/v4.7.2...v4.7.3

  • 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.