AI Agents

  • Fixed

    • The capability poll asks for what a server declared, one listing at a time. Every poll re-listed each connected server’s tools, resources and prompts – three requests, whether or not the server had declared resources or prompts at its handshake – and started every listing at the tick: every server and every pooled session, all at once, every interval. A backend with a per-caller rate limit answered a pooled session’s tools/list with rate_limit_exceeded, and the cached list stayed as designed. A re-listing, from the poll or after a list_changed, now asks for resources and prompts only when the server declared the capability, so a tools-only server costs one request; and a poll walks its listings over the interval, one every interval/n in the order of the server names and sessions, at most eight at a time, so each backend sees one listing per interval at a steady rate. The walk runs on the process clock: a poll’s listings are done before the next tick, and the harness’s test_advance_clock makes every remaining listing due at once. A connection the pool dropped between the tick and its slot is left alone instead of listed through a closed client. (#1307)
  • Added

    • The tool list follows a backend redeployed behind its Service. A server’s tools were read at registration and again only when the server sent notifications/tools/list_changed or its definition changed. A backend rolled to an image with another tool set does neither: the new process has never seen muster’s session, so it notifies nobody, and the session recovery and the health probe heal the connection without re-reading the tools. The old list stayed until a restart – a tool the new image dropped kept failing with tool not found, a tool it added stayed invisible. The aggregator now re-lists every connected server every MUSTER_AGGREGATOR_CAPABILITY_POLL_INTERVAL (default 5m) and updates what changed, through the same refresh and the same notifications to clients as after a list_changed: a server with a shared client once, a server served per session through each session’s own live connection into that session’s list. Nothing is polled that is not connected, a listing that fails leaves the cached list as it is, and the log names the trigger (Capability refresh (poll): updated 3 tools for <server>). The harness’s test_redeploy_mock_server gained add_tools and remove_tools – a new image with another tool set, announced to nobody – and two scenarios roll a backend that way and see the poll re-list it on the production schedule, for a shared connection and for a signed-in session’s pooled one. (#494)
  • Changed

    • The klaus-gateway ServiceMonitor carries the tenant label: klausGateway.serviceMonitor (giantswarm/giantswarm#36711, giantswarm/klaus-gateway#316). The chart’s monitor was on and carried the chart’s own labels only, so Mimir routed its scrape to no tenant and every klaus_gateway_* series was lost on every installation (seen on gazelle, chart 1.19.1). klausGateway.serviceMonitor.labels sets observability.giantswarm.io/tenant: giantswarm and klausGateway.serviceMonitor.enabled is auto, resolved to the boolean the chart takes from the same answer as every other monitor of this chart. Pin first: components.klaus-gateway.versionRange is >=1.20.0 <2.0.0 and examples/customer-bom.yaml pins 1.20.0, the release that opens the labels key; an older chart’s schema refuses it. verify-klausgateway-otlp asserts the resolution and the label; verify-components-charts names 1.20.0 as UNRELEASED until it exists.
  • Added

    • Four more boards in Shared Org / Agent Platform, from the connectivity chart’s dashboards/ directory (giantswarm/giantswarm#36711). LLM usage (uid agent-platform-llm-usage) and Usage by person (uid agent-platform-usage-by-person) move here from giantswarm/dashboards, unchanged, where they were loaded into the staff-only Giant Swarm organization; the agent_platform sub-chart there goes with them. LLM usage lives under dashboards/llm-routing/ and renders only with llmRouting.enabled: every panel of it reads a GenAI series the LLM listener alone produces. Usage by person reads the data plane’s request series by user and renders whenever the boards do. Two new boards: Klaus Gateway (uid agent-platform-klaus-gateway) over the klaus_gateway_* families the gateway’s admin port serves: requests by route and status, turns by outcome, the phases of a turn from the message’s arrival to the final flush, the Slack streaming lifecycle and rate limiting. And Valkey (uid agent-platform-valkey) over the redis_* series of the wrapper chart’s redis_exporter sidecar: memory against the limit, evictions, keys, commands, clients, hit ratio, network, rejected connections, for every instance in the platform’s namespaces. Both gate their rows on the series they read, the Overview pattern, so an installation without the component opens an empty board rather than one of “No data”. The Overview’s intro table names the four and its Valkey panels link to the Valkey board. verify-llm-routing asserts the LLM usage ConfigMap follows the switch and Usage by person does not.

    Changed

    • The agentgateway packaging chart’s own monitors and dashboard are on: agentgateway.monitoring.enabled (giantswarm/giantswarm#36711, giantswarm/agentgateway#60). It follows the resolved global.observability.metrics.serviceMonitor.enabled like every other monitor of this chart (auto | true | false), at the fleet’s 60s interval and with the observability.giantswarm.io/tenant label on both monitors. It turns on three objects the platform did not have: the controller ServiceMonitor, which nothing scraped, and with it the only view of the control plane that serves the data planes their config; upstream’s proxy PodMonitor; and upstream’s own Grafana board — Overview, Requests, LLM, MCP tool calls, Latency, XDS, Runtime — vendored in the packaging chart since 2.0.0 and never rendered. The MCP and runtime series it reads were already collected and had no consumer. The board is upstream’s to maintain, so a version bump carries its fixes; the boards the platform writes itself (LLM cost per agent and per person) ship in this chart’s dashboards/ directory.
  • Fixed

    • A 429 on a session’s initialize is a transient refusal, not a legacy SSE server. When a remote MCP server answered a person’s session initialize POST with 429 Too Many Requests – GitHub’s hosted MCP server does when many tool calls of one session are in flight – muster logged failed to initialize MCP protocol: ... server returned 4xx for initialize POST, likely a legacy SSE server and sent whoever read the log after a transport mismatch that was not there. mcp-go reduces every 4xx on the initialize POST to that one sentinel and drops the status; the per-session client (DynamicAuthClient, the person’s grant, a forwarded or exchanged token) never typed it, so the connect failed with the generic message while the tool-call path already named the status. Now a 429 or 503 to the initialize is classified as retry-later on both connect paths: the client records the status and the Retry-After header off the wire and returns a typed InitializeRefusedError carrying both, the log and the CR status name the status (endpoint answered the initialize POST with HTTP 429 Too Many Requests; retry after 1s), the reconnect is scheduled for the Retry-After the server asked for rather than muster’s doubling backoff, and the session’s grant is kept so the next call reconnects. The generic 4xx message no longer claims a legacy SSE server for a status that has its own meaning – likely a legacy SSE server is said only for the 405 that means it. The scenario oauth-session-initialize-429-is-retry-later answers one grant-adoption initialize with 429 and Retry-After: 1, and pins that the server stays connected, the log names the status without the legacy-SSE line, and the next tool call succeeds without a reconnect. (#1303)
  • Added

    • The three upstream lines at their decoupled releases: kagent 1.0.0, Substrate 1.0.0, agentgateway 2.0.0 under its nested names (giantswarm/agent-platform#608; epic giantswarm/giantswarm#37705). The lines release stable semver of their own, decoupled from the upstream versions their FORK.mds record, and publish nothing more under the former vX.Y.Z-gs.N scheme. components.kagent*.versionRange and components.substrate*.versionRange are >=1.0.0 <1.1.0; both charts name the agentgateway line’s 2.0.0 in full — agentgateway.controller.image giantswarm/agentgateway-upstream/controller:2.0.0, agentgateway.proxy.image giantswarm/agentgateway-upstream/agentgateway:2.0.0, substrate.images.agentgateway the same data plane for Substrate’s egress gateway — so the packaging chart’s defaults cannot move the images (a value wins over a chart default, and a default that moved alone to the nested names rendered a tag that existed only there); components.agentgateway.versionRange is >=2.2.2 <3.0.0, the packaging release that renders a bare tag as written. The range shape follows the scheme — a patch of a line never changes a runtime contract, a re-pin onto another upstream release is at least a minor — so agent-platform.substrate.validateRange admits an exact version or >=X.Y.Z <X.(Y+1).0 and refuses a -0 bound (Flux’s Masterminds semver skips prereleases while no bound of a range carries one and evaluates them all once one does, so <1.1.0-0 would have put every installation on the line’s newest dev build) and the former >=X.Y.Z-gs.N <X.Y.(Z+1)-0, agent-platform.substrate.workerPoolSpreadFloor is 1.0.0, examples/customer-bom.yaml pins 1.0.0 and 2.2.2, and the verify scripts’ fixtures move to the stable shapes. UPGRADE.md names the operator’s part.\n\n- The kagent line’s and the Substrate line’s charts come from gsoci; the lines move to v0.11.0-gs.22 and v0.0.30-gs.5 (giantswarm/agent-platform#580; epic giantswarm/giantswarm#37853). The two lines publish natively to the org’s registry from CircleCI — the multi-arch images and the charts, signed with the Giant Swarm identity, releases and dev builds alike — so components.kagent.repository and components.kagent-crds.repository are oci://gsoci.azurecr.io/giantswarm/kagent/helm, components.substrate.repository and components.substrate-crds.repository are oci://gsoci.azurecr.io/giantswarm/substrate/helm. The Substrate floor moves to 0.0.30-gs.5 — the line’s first release published there, the patches of gs.4 — in components.substrate*.versionRange (>=0.0.30-gs.5 <0.0.31-0), the BOM pin and, derived from it, the kagent WorkerPool’s worker image, so the atelet and the worker are one native, signed release and nothing of the line depends on a copy. The kagent floor moves to 0.11.0-gs.22 the same way (components.kagent*.versionRange >=0.11.0-gs.22 <0.11.1-0, the BOM pin): the chart paths on gsoci begin with each line’s first native release — the older releases exist there as retagger’s image copies only, no charts — and the kagent chart’s stamped digests of its Go ADK and Claude harness images resolve on gsoci from that release on. With the charts the last ghcr.io reference of the two lines goes: a default installation pulls everything of kagent and Substrate from gsoci, and the dev channel (a components.<name>.semverFilter on the lines’ -dev.giantswarm. builds) needs no registry override any more — kagent.registry and substrate.image.registry stay on their defaults (README “Dev channel”). make verify-images tolerates the two lines by name no longer; the upstream CloudNativePG chart is the one pending entry left, behind a Giant Swarm wrapper release on the same operator line. verify-components and verify-kagent-wiring assert the new sources; verify-target holds them on both sides of the golden comparison until origin/main carries them. UPGRADE.md.
    • An Overview board, the entry point of the platform’s Grafana folder (giantswarm/giantswarm#36711). dashboards in both charts renders one ConfigMap per JSON under the connectivity chart’s dashboards/ directory, discovered by observability-operator through app.giantswarm.io/kind: dashboard and placed by the organization and folder annotations (both values carry a space, so neither can be a label). It ships overview.json, uid agent-platform-overview, in Shared Org / Agent Platform: what the platform is and which board covers what; component health; what it is doing; and the Postgres and Valkey rows, which have no board of their own. Every health panel reads the same series as the alert that pages for it — gotk_resource_info, kube_deployment_status_replicas_available, muster_mcpserver_state, kube_cnpg_cluster_info, kube_pod_container_status_restarts_total, muster_session_store_backend, redis_memory_used_bytes — so a panel and a page cannot disagree. Today the six alerts of agent-platform.rules.yml hand on-call a runbook and no graph. The ConfigMaps render under the resolved global.observability.metrics.serviceMonitor.enabled: the operator that picks them up is part of the observability platform the monitors write to. A component’s own board still comes from its own chart. The Postgres and the Valkey row, and the six agentgateway panels of the “what it is doing” row, are gated on the series they read: each one repeats over a hidden label_values(<metric>, __name__) variable, which yields one value while the metric exists and none while it does not, so an installation that runs neither CloudNativePG nor Valkey nor the agentgateway data plane opens the board without a row of “No data”. A component that is on but not yet scraped is hidden the same way, which a Helm conditional on components.<name>.enabled could not do.

    Changed

    • The mcp-kubernetes chart’s own ServiceMonitor and its three Grafana boards are on (giantswarm/giantswarm#36711). The chart ships a ServiceMonitor over the dedicated metrics port and the boards administrator, security and cluster-operator, and this chart turned none of them on, so an installation that runs the Kubernetes MCP server collected nothing from it. mcp-kubernetes.mcpKubernetes.instrumentation.serviceMonitor.enabled and mcp-kubernetes.grafanaDashboards.enabled follow the resolved global.observability.metrics.serviceMonitor.enabled (auto | true | false), the monitor carries observability.giantswarm.io/tenant, and the boards land in Shared Org / Agent Platform beside the platform’s own. The chart’s prometheusRules stay off: its three alerts link to runbook pages that do not exist yet. The floor 1.1.1 already carries every key, so no range moves.
    • Every agent platform dashboard lands in one folder customers can reach: Shared Org / Agent Platform (giantswarm/giantswarm#36711). The muster board (Muster / MCP Gateway) was loaded into the staff-only Giant Swarm organization, in a folder named after the component, so the people who run agents on the platform could not see it: muster.observability.grafanaDashboard.folder is Agent Platform and .giantswarm.organization is Shared Org. Shared Org is the organization every logged-in customer reaches as a Viewer and it carries the observability data of Giant Swarm managed components, which is where these boards belong; the Giant Swarm organization is staff-only. The boards that follow — the agentgateway gateway board, the platform’s own overview — land in the same folder.
  • Added

    Full Changelog: https://github.com/giantswarm/agent-platform/compare/v4.51.0...v4.52.0

  • Added

    • (observability) Turn on the agentgateway chart’s own monitors and board in #616 by @QuentinBisson

    Full Changelog: https://github.com/giantswarm/agent-platform/compare/v4.50.0...v4.51.0

  • Added

    • (dashboards) Load the muster board into Shared Org / Agent Platform in #615 by @QuentinBisson

    Full Changelog: https://github.com/giantswarm/agent-platform/compare/v4.49.0...v4.50.0

  • Fixed

    • spec.headers reach a person’s session connection. An MCPServer’s spec.headers were sent by the server-level client only; the connection muster opens for a person’s session to an OAuth server (the person’s grant as bearer, a forwarded login token, a token exchanged for it) sent none of them. The hosted GitHub MCP server selects its toolsets per session from X-MCP-Toolsets, so with spec.headers: {X-MCP-Toolsets: default,git} on the github MCPServer the server-level probe saw the git toolset while every person’s session connected with the default 48 tools and x_github_get_repository_tree was “tool not found”. The headers now travel with the pending-auth registration into the aggregator’s registry, next to spec.meta and spec.timeout, and every session-scoped client sends them on the handshake and on every call; the session’s Authorization header stays its own, an entry of that name in the definition is left out. A change of spec.headers on a live definition closes the connections the sessions hold to the server the way an auth configuration change does (session_auth_reset, changed: headers), tools kept resolvable; a session’s next call reconnects with the grant it still holds, and that connection carries the new set. Scenario oauth-session-client-sends-definition-headers; the mock backend’s tools gained echo_headers and a pre-configured mock’s config a headers field for the definition. (#1304)