AI Agents

  • Fixed

    • Preset qwen3-8-27b declares the Hub’s weight size, 25 GiB (was 15), and names the hardware it is tuned for; every shipped preset’s weightsGiB is checked against the Hub in CI (giantswarm/agent-platform#535). Two components judge whether a GPU pool hosts a preset from two inputs: cluster-manager’s create_node_pool sizes the pool from the preset’s declared weightsGiB + overheadGiB, model-manager’s check_fit / load_model size the weights from the Hub (model.safetensors.index.json total_size, else the safetensors files) and add the preset’s overheadGiB. qwen3-8-27b declared 15 GiB for a 24.57 GiB repository (Inferact/Qwen3.8-27B-NVFP4: six shards plus nvfp4_experts_mtp.safetensors), so a pool of one 48 GiB GPU was judged to host it on the form (45 GiB) and refused at the serve step (54.6 GiB). The preset now declares 25 GiB and its description says that, as tuned (25 GiB of weights + 30 GiB of overhead, the CUTE_DSL_ARCH pin, --gpu-memory-utilization=0.60), no size of the curated AWS accelerator families hosts it — its target is a single 128 GB unified-memory Blackwell node; the flags are unchanged. devstral-small-2 declared 48 GiB “at BF16” for a 24.02 GiB FP8 checkpoint (mistralai/Devstral-Small-2-24B-Instruct-2512: quant_method: fp8; the index totals the six model-* shards, the consolidated-* files are a second copy of the same weights) and now declares 25 GiB. New make verify-preset-weights (tests/verify-preset-weights.py; collected by verify-all, so CI runs it for every PR) sizes each preset’s spec.model.id the way model-manager does and fails a preset below the Hub’s size, more than 15 % above it, or one the Hub cannot size — never skipped; tests/fixtures/serving-preset-weights-{understated,overstated}.yaml are its negative controls. Every other shipped preset is within tolerance (+0.3 % to +6.9 %).
  • Fixed

    • A live session follows its MCPServer’s auth configuration. When an MCPServer’s spec.auth changed underneath sessions connected to it – forwardToken replaced by a pinned authorizationServer, another issuer, expectedIssuer, grant scope or client Secret, a changed token exchange – every live session kept its state for the server from before the change: muster auth status said Connected, a tool call failed with the backend’s 401 as Tool execution failed: failed to call tool: transport error: authorization required, muster auth login --server <name> refused (already connected and does not require authentication), and nothing short of signing out of muster ended it, while the server itself read Auth Required all along. Three things changed. The aggregator puts the server back to auth_required for every live session when the changed configuration registers (session_auth_reset in the log; a restart or a retry on an unchanged configuration leaves the sessions be); the session’s cached tools stay resolvable, so its next call answers with the sign-in. A tool call the session cannot make – it is not signed in to the server, or the backend refuses its credential with a 401 during the call – answers the auth_required challenge with the sign-in link for that server, the answer core_auth_login gives (marked as an error: the tool did not run; structuredContent.authUrl carries the link), never user not authenticated to server or a transport error; the refused connection is retired on that call, without waiting for the transport’s three retries, and an SSO server’s answer says to sign in to muster again. The streamable HTTP transport’s bare 401 (authorization required) is now recognized as one wherever muster tests for a 401. And muster auth logout --server <name> signs the session out of that one server at the aggregator (core_auth_logout) and prints what happened to the person’s grant; it used to print guidance and change nothing, and --server next to --endpoint signed out of the aggregator instead. Scenarios oauth-auth-config-change-resets-live-sessions and oauth-backend-401-answers-auth-required; test_pin_mcpserver_authorization_server drops forwardToken/tokenExchange when it pins. (#1276)
  • Fixed

    • (connectivity) Presets quote their JSON arguments for the llm-d runtime’s shell re-parse; the chart refuses an argument the shell would re-split in #533 by @teemow

    Full Changelog: https://github.com/giantswarm/agent-platform/compare/v4.30.1...v4.30.2

  • Fixed

    Full Changelog: https://github.com/giantswarm/klaus-oci/compare/v0.0.74...v0.0.75

  • Added

    • spec.auth.authorizationServer.expectedIssuer: two clients of one authorization server. A pinned authorization server’s issuer was both the identity the grant is filed under and the value the RFC 9207 iss parameter of the authorization response is compared against. GitHub sends iss=https://github.com/login/oauth on every response, so a GitHub App pinned under its own identity (https://github.com/apps/<slug>, to keep its grant apart from another App’s) never completed its sign-in: iss mismatch: response carried "https://github.com/login/oauth", expected "https://github.com/apps/<slug>". The new optional field names the issuer identifier the server really publishes; a present iss is compared against it (a trailing slash tolerated) while issuer stays the grant key, so each App keeps its own grant, consent and core_auth_logout. It needs the pinned endpoints; with the field unset nothing changes. Carried through the CRD, the API, core_mcpserver_get and the aggregator’s pin; the how-to gained “Two clients of one authorization server” and the CRD reference the pin’s fields. (#1277, #1275)
  • Changed

    • (verify-target) Drop the golden-side controller VPA hold — origin/main carries #455, so deleting kagent.controller.vpa on one side is what makes the renders drift in #530 by @fiunchinho

    Full Changelog: https://github.com/giantswarm/agent-platform/compare/v4.30.0...v4.30.1

  • Added

    • The kagent controller gets a VerticalPodAutoscaler (giantswarm/agent-platform#455). New knob kagent.controller.vpa: enabled: auto renders it where the cluster serves autoscaling.k8s.io/v1 (an explicit true / false wins). The connectivity chart renders VerticalPodAutoscaler kagent-controller in the kagent namespace on the controller Deployment — updateMode: InPlaceOrRecreate (VPA 1.5+ on Kubernetes 1.33+: the running pod’s requests are resized in place, no eviction, no roll), controlledValues: RequestsOnly, minAllowed 100m / 128Mi, maxAllowed 1900m / 480Mi, minReplicas: 1. The key never reaches the kagent release. Every installation on a cluster with the VPA gets the object — UPGRADE.md.
  • Changed

    • (verify-target) Drop the golden-side metric-labels hold — origin/main carries #528, so nulling llmRouting.metricLabels there is refused by its schema and verify-target never renders in #529 by @fiunchinho

    Full Changelog: https://github.com/giantswarm/agent-platform/compare/v4.29.0...v4.29.1

  • Added

    • Per-person metrics on the agentgateway data plane: every series of a route that verifies a bearer — the kagent controller route first — carries the caller as a user label, so a Slack turn through klaus-gateway is counted, timed and error-rated per person (giantswarm/agent-platform#528). klaus-gateway makes every controller call as the linked person — the Dex id_token in authorization, never its own identity (its pkg/a2a client; Slack is a forwarded-only channel) — and the kagent-controller-jwt policy verifies that token on the GRPCRoute in Strict mode, so the verified claims are on every request the data plane logs; agentgateway (the CRD field since upstream v1.2.0; the GS line’s v1.5.1-gs.4 the fleet runs) turns a CEL expression into a Prometheus label on every metric that carries route labels — frontend.metrics.attributes.add, a Gateway-scoped AgentgatewayPolicy, the labels evaluated when the request completes against the request as the route policies left it, so a Gateway-scoped label reads a route-scoped policy’s jwt.<claim>. The connectivity chart renders one such policy, <release>-metrics (templates/agentgateway/metrics-policy.yaml), whenever the data plane is on and an entry is enabled, from gateway.metricLabels — a map keyed by Prometheus label name, not a list: Helm merges maps and replaces lists, so an installation turns one default entry off (gateway.metricLabels.user.enabled: false) or adds one (team: {expression: jwt.groups}) without restating the rest; each entry is {expression: <one-line CEL>, enabled: <bool, absent = true>}. Three by default: agent and agent_namespace (source.unverifiedWorkload.* — the entries that were llmRouting.metricLabels, unchanged in name and expression, so the LLM usage dashboard reads on) and user, whose expression goes through tpl: jwt.{{ .Values.kagent.controller.auth.userIdClaim }}, the identity claim the controller route verifies and writes into x-user-id (email), so the metrics attribute a turn to the same person the controller acts as and follow the claim knob. An entry that reads jwt — jwt.<claim> or jwt["<claim>"] — is rendered only while a route of the Gateway verifies a bearer (the controller route with its JWT policy, agent-manager’s, model-manager’s — agent-platform.jwtRouteRendered), since without one it would read unknown on every series. On the controller route a Slack turn is one lf.a2a.v1.A2AService/SendStreamingMessage stream: sum by (user) (rate(agentgateway_requests_total{route=~".*/kagent-controller"}[1h])) is turns per person (route is <namespace>/<name>), the duration histogram the turn’s length; the portal, a CLI and the other verifying routes are labelled the same way. What it does not give: a route without a JWT policy — the MCP path under oauthMode: passthrough, the LLM listener, whose calls carry the ModelConfig’s API key and no person — renders unknown, one extra label value there and no series per person, so tokens and cost stay attributed to the agent (the harness passes no caller identity to the provider; a per-person LLM label is a kagent change). The labels moved out of the LLM path’s policy because the data plane honours one metrics policy per Gateway (custom labels replace rather than merge; of two policies one is kept and the other dropped in silence, and which one has changed between agentgateway releases — arbitrary on v1.4, the lowest policy key on v1.5, the oldest on later builds) and the labels describe every route: the LLM policy (<release>-llm) keeps the route-type map alone, and the metrics render with or without llmRouting.enabled — before, an installation without LLM routing had no labels at all. Expect a one-time break in the data plane’s series at the upgrade: a new label set is a new series, so counters such as agentgateway_requests_total restart and dashboards and recording rules on them show a step there. The schema holds every entry to {expression, enabled}. The render fails, naming the entry, on a missing or empty expression (as written or after tpl), one spanning more than one line, a name that is not a Prometheus label name, one the data plane’s own series already carry (a duplicate label name fails the whole scrape), one the scrape adds (namespace, pod, job, …: Prometheus would store it as exported_<name>), one starting with __ — the name checks run on an entry the jwt gate holds, too — and more than 16 enabled entries (the CRD’s limit). kagent.controller.auth.userIdClaim must be a bare identifier wherever it is read as CEL — the controller route’s identity header, the user entry on any verifying route, kagent on or off — since an expression that does not compile takes the whole policy down; the guard now lives in the helper that returns the claim, so it fires exactly there and no longer only with the controller route. Two entries of one name cannot be written: the key is the name. Name and expression go through toYaml, so a name YAML reads as a boolean (on) is quoted. In muster-direct there is no data plane and the map is inert. make verify-metric-labels asserts the policy in every shape, that it is the only one with a frontend.metrics section (per render and per template), the map’s merge (one entry off, one more, a renamed person label), the tpl, the gate, the meta chart’s forwarding of the expression as written and the schema’s refusal of the retired key, and the guards; verify-llm-routing now asserts the LLM policy carries no metrics section. README: “Metric labels”. Know what the default costs: an email address in Mimir is personal data, and the label multiplies the verifying routes’ HTTP series by the number of people (tens to a few hundred on an installation); gateway.metricLabels.user.enabled: false drops it.

    Removed

    • Breaking: llmRouting.metricLabels (giantswarm/agent-platform#528): the data plane’s metric labels are gateway.metricLabels (above), the same two entries keyed by name rather than listed, rendered whether or not LLM routing is on. Not aliased — both charts’ schemas refuse the old key, naming it, so a values file that still sets it fails the render at upgrade rather than losing its labels in silence. Move the entries one block over, as map keys; a file that never set it needs nothing.
  • Fixed

    • Remove helm-docs version badges from chart README in #212 by @piontec

    Full Changelog: https://github.com/giantswarm/klaus-operator/compare/v0.0.161...v0.0.162