AI Agents

  • Fixed

    • The two L4 presets fit the smallest L4 instance (giantswarm/agent-platform#502): qwen3-4b-instruct and qwen3-8b-fp8 request 2 vCPU / 10 GiB and are limited to 4 vCPU / 12 GiB (were 4 / 16Gi and 8 / 24Gi). A g6.xlarge has 4 vCPU and 16 GiB, of which a Giant Swarm node reports 3.4 vCPU / 13.4 GiB allocatable, and the daemonsets that follow a GPU pool’s taint request ~0.4 vCPU / 1.5 GiB more — on gazelle the predictor composed from qwen3-4b-instruct sat Pending for ten minutes on a pool of xlarge nodes (could not schedule pod … no instance type has enough resources … instance-size In [xlarge]) until the pool was widened to 2xlarge at +50 %/h (giantswarm/giantswarm#37714). The predictor’s host memory is the runtime and the weights passing through the page cache (the KV cache is on the GPU), so 10 GiB requested and 12 GiB limited cover an 8–9 GiB checkpoint. Each description names the instance it is sized for; tests/verify-serving-slice.py holds the requests within what a g6.xlarge leaves a predictor (the same node model cluster-manager’s create_node_pool answers with) and tests/verify-gpu-pool.py leaves the two preset ConfigMaps out of the byte-identity check against a golden from before.
  • Fixed

    • The Substrate worker image follows the chart’s Substrate pin, never the kagent build’s stamp (giantswarm/agent-platform#466). The meta chart pins Substrate and kagent as two independent ranges, and every kagent build stamps into its chart the ateom-gvisor worker of the Substrate it was published against; chart 4.15.2’s open kagent range (>=0.11.0-gs.12 <0.11.1-0) admitted kagent 0.11.0-gs.14, which stamped a 0.0.30-gs.1 worker under the chart’s 0.0.27-gs.9 atelet — Substrate 0.0.30 had renamed the pause bundle (bundles/pause → bundles/_pause), so every golden boot failed with reading …/bundles/_pause/config.json: no such file or directory, every AgentTemplate stayed Ready=False … compiling, and nothing on any hop named the skew (seen on a fresh agentlab on the default chart, giantswarm/agentlab#187; 4.15.5 re-pinned both ranges by hand). The chart now derives kagent.substrateWorkerPool.workerImage for the kagent release from components.substrate.versionRange’s floor — <substrate.image.registry>/ateom-gvisor:<floor> (agent-platform.substrate.workerImage, over the forwarded block like the other derived values) — so the atelet the substrate release installs and the worker the WorkerPool runs are one Substrate release whatever kagent build the kagent range admits; the same 4.15.2 shape now forwards the 0.0.27-gs.9 worker. An installation’s own workerImage stands while its tag is the pinned release (a mirror by another path; substrate.image.registry moves the derived image on its own) and fails the render otherwise, naming the key, the release and the derived image. The Substrate range must confine one release (agent-platform.substrate.validateRange): an exact version or >=X.Y.Z-gs.N <X.Y.(Z+1)-0 — the worker follows the floor and the atelet what Flux resolves, so a later gs.N of the pinned release may reach the control plane ahead of the worker and a 0.0.31 never; 0.x, ~, ^, a <= ceiling or a floor alone fail the render naming the range. make verify-worker-image (tests/verify-worker-image.py) holds the derivation, the mirror, the own-value rule, the 4.15.2 shape, the exact pin, the range guards, and — against the kagent chart the range resolves to — the rendered WorkerPool’s spec.workerImage and that the build was published against the pinned release’s X.Y.Z (its Chart.yaml substrate dependency), so the meta chart’s CI names the day the kagent line moves to another Substrate before the chart’s pin does; verify-components asserts the forwarded image, verify-workerpool the range guard’s precedence. components.substrate / components.substrate-crds move to >=0.0.30-gs.4 <0.0.31-0 (the bounded golden boot, giantswarm/substrate#39, giantswarm/giantswarm#37801): the worker the fleet already runs through kagent 0.11.0-gs.20’s stamp, so an installation following the current chart sees the WorkerPool’s spec.workerImage unchanged and the pool does not roll; the BOM pins it. Not taken: a kagent minor bump on every Substrate-line change (a convention every <0.11.1-0 range in the wild depends on, nothing in the chart holds it) and reading the resolved Substrate version at render time (Flux resolves after the render; the floor is the one version the chart knows, and the ceiling guard is what makes it the runtime’s).
  • Changed

    • (verify-target) Drop the golden-side klausGateway.crd hold — origin/main carries #497, so nulling the key there inserted crd: null and made the meta default render drift in #501 by @fiunchinho

    Full Changelog: https://github.com/giantswarm/agent-platform/compare/v4.28.6...v4.28.7

  • Changed

    • klaus-gateway’s crd.install is no longer set by the meta chart, and the connectivity chart’s Kubernetes API egress for the gateway covers the Secret link store only (giantswarm/klaus-gateway#271). klaus-gateway drops the crd and configmap routing stores, the ChannelRoute CRD and the embedded controller, and its chart’s values.schema.json is closed (additionalProperties: false), so it refuses crd.* and controller.*. The meta chart forwards the whole klausGateway block to the klaus-gateway release (valuesFrom: klausGateway) and its defaults set klausGateway.crd.install: true, so that key has to go before the klaus-gateway release that drops it rolls: the component’s range >=1.10.0 <2.0.0 takes it with no PR, and a values file the schema refuses fails the Helm upgrade — the release sticks (the pods stay up). The current klaus-gateway chart defaults crd.install to true, so the render on the installations does not change: every installation’s klaus-gateway release gets one Helm revision (a values change) and no pod rolls. All three installations run klausGateway.routing.store: valkey and none sets crd or controller in its own patch. The connectivity chart follows: agent-platform.klausGateway.apiServerEgress is now the Secret link store’s gate alone (klausGateway.obo.store: secret with OBO on), so the -klausgateway-store-egress policy renders byte-identically for every shape an installation runs, and make verify-klausgateway-netpol no longer asserts the configmap / crd / controller.enabled gates.
  • Fixed

    • The serving predictors pass the fleet’s restricted Pod Security Standard (giantswarm/agent-platform#498): a PolicyException model-serving-predictors in kyvernoPolicies.policyExceptionNamespace names the four rules the llmisvc controller’s predictor violates (the upstream vLLM image and its cache init container run as root: require-drop-all, privilege-escalation, run-as-non-root, check-seccomp-strict, with their autogen copies) for the serving namespace’s predictor Pods, ReplicaSets and Deployments — on gazelle the first LLMInferenceService’s Deployment was denied at admission and no model could be served.
  • Fixed

    • The agentgateway controller admits xDS from every data plane of its GatewayClass, in any namespace (giantswarm/agent-platform#495): its ingress policy (both flavours) selected only the platform edge’s data plane by Gateway name in the release namespace, so the serving slice’s models Gateway in org-<org> connected to nothing — on gazelle its data plane was denied at 9978 (Policy denied DROPPED) and restarted by its startup probe forever. A second peer selects gateway.networking.k8s.io/gateway-class-name with the namespace label present, on 9978 only.
  • Fixed

    • model-manager reaches the Hugging Face Hub for a kserve backend cluster-manager registers at runtime (giantswarm/agent-platform#494): the hub egress of its network policy rendered only for a static modelManager.backends kserve entry; with components.cluster-manager on it renders as well — on gazelle check_fit hung on the hub lookup until muster’s deadline.
    • The models Gateway’s data plane passes the fleet’s admission policies and gets its DNS record (giantswarm/agent-platform#492): the Gateway references a models-specific AgentgatewayParameters (the platform’s gateway.parameters.podSecurityContext / containerSecurityContext — seccomp RuntimeDefault, non-root, no capabilities —, dataPlaneResources, agentgateway.proxy.image, modelServing.modelsGateway.replicas, a LoadBalancer Service); on gazelle the controller’s default Deployment was denied by restrict-seccomp-strict and the Gateway never programmed. Its infrastructure annotations carry modelServing.modelsGateway.externalDns.annotations (default giantswarm.io/external-dns: managed, the filter Giant Swarm’s external-dns selects Services by) next to the hostname, so models.<domain> is created instead of falling through to the wildcard edge.
  • Fixed

    • No Warning MCPServerRecoveryFailed for the expected 401 of an OAuth-protected server. When an MCPServer whose callers bring their own credentials (auth.forwardToken, auth.tokenExchange, or an OAuth login through muster) came up together with muster, automatic recovery restarted it as soon as it answered and reported its 401 – the answer such a server is configured to give a token-less probe – as a failed recovery, one Warning per OAuth-protected server on every platform install, right after the same reconciler had put the server in Auth Required. Recovery now ends there with a Normal MCPServerRecoveryAwaitingAuth event (“automatic recovery reached the server; it waits for a signed-in caller”) and the server connects on the first call that carries a token, as before. A 401 from a machine identity (auth.type: sigv4), a 5xx and a refused connection stay MCPServerRecoveryFailed. (#1265)
  • Fixed

    • The serving slice renders without an edge Gateway (giantswarm/agent-platform#490): connectivity’s validateIngress required a public Gateway for the muster / route and a muster-direct-consistent components.agentgateway toggle in every release, muster on or off — so the <cluster>-agent-platform slice (examples/serving-slice.yaml: no muster, its own models Gateway; agentgateway on where the target runs no controller) failed its render on gazelle with no public Gateway for ingress.parentRefs. Both checks now apply only while components.muster is on; the mode enum, the agentgateway-direct refusal, the “agentgateway-* mode needs the component” rule and the per-component muster-direct guards are unchanged. tests/verify-serving-slice.py renders the slice without any ingress.parentRefs and holds the guard for a release with muster on.
  • Fixed

    • The Hugging Face cache claim no longer fails the connectivity release under a WaitForFirstConsumer StorageClass (giantswarm/agent-platform#483; follows #326; epic giantswarm/giantswarm#37639). With components.modelServing on, modelServing.cache.pvc (hf-cache) rendered as a release resource with no consumer of its own: the first predictor (or download Job) that mounts it is what Binds it, so under kind’s standard and the fleet’s default gp3 it stayed Pending, helm-controller’s wait counted it as not ready and the release failed on every install and upgrade (timeout waiting for: [PersistentVolumeClaim/model-serving/hf-cache status: 'InProgress'], five attempts, then stalled) — with kserve-resources before it and substrate, kagent, agent-manager and model-manager behind it. The claim is now applied by a post-install,post-upgrade hook Job (templates/model-serving/cache-pvc.yaml; the chart’s hook include and its identity <release>-hooks, whose ClusterRole carries get, create, patch on persistentvolumeclaims while the claim is the chart’s — never delete): kubectl apply --server-side under the field manager agent-platform-connectivity, the log says created or present and the phase, and nothing waits for the claim to Bind — it binds where its first consumer schedules, which is what a zonal volume needs (the GPU pool’s zone). The applied spec is the one the chart rendered (hf-cache in the serving namespace, helm.sh/resource-policy: keep, accessModes, size, storageClassName incl. "-", volumeName), and so is what a cache.enabled flip or an uninstall does — the claim stays, now because it is not Helm’s. An installation upgrading from a chart that rendered the claim keeps it (the keep policy) and the hook takes its fields over (--force-conflicts); a size change is applied by the next upgrade’s hook (the StorageClass has to allow expansion); an immutable field (storageClassName, accessModes, volumeName) fails the hook naming it. Installations without the switch render byte-identically (make verify-modes); make verify-serving-slice asserts the hook, its claim and identity, the knobs, and that cache.enabled: false or an existingClaim render none of it; make verify-wiring the serving shape without a PersistentVolumeClaim object; make verify-gpu-pool compares both sides without the claim. Not taken: a consumer pod of the chart’s own (it would pin a zonal volume to its own zone and hold an RWO claim), an Immediate-binding class (the same zone lottery, and not the fleet’s default), a release-wide disableWait (blind to the platform’s dependency chain).