AI Agents

  • Fixed

    • muster list tool --server <name> finds the tools of an aggregated server. The flag matched the name against the start of the exposed tool name, which for a server registered as files is x_files_<tool>, so --server files printed “No tools found” for every aggregated server, and no spelling at all selected a server whose toolPrefix differs from its name. --server now matches the server a tool belongs to as list_tools reports it (files, core, workflow), independent of the tool prefix, and accepts the exposed prefix (x_files) as well; -o wide and -o json show that server instead of the first name segment (x). (#1248)
  • Changed

    • The Substrate line moves to v0.0.30-gs.2: the worker pool can be spread over nodes and zones (giantswarm/agent-platform#472, second half; the carried patch giantswarm/giantswarm#37797, #37742 row 46). v0.0.30-gs.2 is the first release whose WorkerPool CRD carries spec.template.topologySpreadConstraints (an atomic list of at most 8) and spec.template.podAntiAffinity, mapped onto the worker pods next to nodeAffinity; every release before it pruned both at admission. components.substrate / components.substrate-crds move to >=0.0.30-gs.2 <0.0.31-0 and agent-platform.substrate.workerPoolSpreadFloor names the release, so the render guard of 4.20.0 now forwards kagent.substrateWorkerPool.template.topologySpreadConstraints / podAntiAffinity verbatim to the kagent release and the WorkerPool, and refuses them — naming the key, the floor and the range — only on an installation whose Substrate range floor is pinned below it. The worker image the kagent chart stamps stays ateom-gvisor:0.0.30-gs.1 (kagent 0.11.0-gs.14 on) and runs under the gs.2 control plane: the release changes ate-controller, the CRD and ate-api only, so the Substrate pin and the kagent pin part ways for the first time (values.yaml says when they move together: a re-pin that changes the runtime). The release also carries giantswarm/substrate#35 (a paused actor’s resume fails fast when its node is gone, LOCAL_SNAPSHOT_GONE, #37742 row 44a). The example BOM, tests/verify-components.py and make verify-workerpool follow the pin: the spread now reaches the kagent release at the floor and is refused below it, the two branches the test exercised with a temporary floor before. Every installation with kagent on rolls the Substrate control plane once (the CRDs gain the two fields, ate-api-server / ate-controller / atenet / atelet restart); the worker pods, the WorkerPool template and the goldens are untouched — UPGRADE.md.
  • Added

    • muster test: the mock OAuth server takes the profile of a real authorization server. mock_oauth_servers[].profile: github | dex | pro bundles that server’s quirks instead of one flag per bug (omit_resource_metadata, omit_token_scope, pin_authorization_server, pin_endpoints_ref, authorize_accepts_any_client, …; the flags stay and each overrides the profile for itself). github: no RFC 8414 discovery document, no Client ID Metadata Documents and no RFC 7591 registration (a pre-registered client only), scope and expires_in absent from token responses; the MCP servers that reference it answer a bare 401 without RFC 9728 metadata, file their grants under the person and are pinned to it with explicit endpoints. dex: discovery, scope omitted, an id_token with every token, RFC 7591 registration. pro (the MCP TypeScript SDK’s authorization server): discovery, registration without registration_client_uri, a client it does not know answered directly at /authorize with invalid_client, the token endpoint refusing unregistered clients, registrations held in memory. Without a profile the mock now hands out the RFC 7592 pair (registration_client_uri, registration_access_token) and answers the client read, so muster’s spec-defined registration check runs against the well-behaved default and its authorization-endpoint probe against pro. test_restart_mock_oauth_server replaces a mock authorization server’s process behind its port and issuer; under pro the registrations go with the old process. The scenarios that reproduce a named server’s bug carry its profile and fail on the release that had the bug: a server’s grant surviving the login id_token mirror on a Dex (fails on v5.12.0, #1174), a registration a pro restart forgot detected and made again (fails on v5.7.8, #1128), a pinned bare-401 GitHub-style server connectable after a muster restart with no login in the new process (fails on v5.8.3, #1150). Documented in “Authorization-server profiles” of docs/contributing/testing/oauth-testing.md. (#1239)
  • Added

    • Documentation site. docs/ is built with MkDocs (Material) and published at https://giantswarm.github.io/muster/ by the Pages workflow; the site kept serving muster-agent.json, the muster agent’s OAuth client metadata document, at its URL. A pull request that touches the documentation builds the site in strict mode, so a broken link, a page missing from the navigation or an unknown anchor fails the check. make docs-serve renders the site locally.
    • The CLI reference is generated from the command tree. make generate-cli-docs renders docs/reference/cli/ from the Cobra commands (hack/gen-cli-docs); make verify-cli-docs, part of make test, fails when the committed pages are stale. The hand-written pages, which documented flags that never existed (--port, --mcp-port) and a REST API, are gone.

    Changed

    • README and documentation reworked. The README describes muster as it is today: an aggregating MCP server with meta-tool discovery, toolsets, OAuth 2.1 protection and single sign-on through Dex, MCPServer and Workflow custom resources, workflows and observability; installation is from the signed release binaries, go install or the Helm chart in the Giant Swarm catalog. The documentation lost its duplicated getting-started and IDE pages, the invented REST API, service resources, create service, port 8080 and Homebrew instructions, and gained a quick start whose every command is verified, a tutorial for servers and workflows, guides for connecting MCP clients and authenticating the CLI, an installation guide for the chart with Dex and Valkey, an HTTP endpoints reference including the admin listener, and a rewritten contributing guide. The product is spelled muster throughout. muster --help and muster serve --help describe the current product instead of a Giant Swarm port-forwarding helper.

    Fixed

    • muster start workflow <name> no longer passes the CLI’s own flags on as workflow input. --endpoint, --auth, --context, --config-path, --debug and the output flags given after the workflow name reached the workflow engine as arguments, were recorded in the WorkflowExecution (auth: none, endpoint: http://...) and would have collided with a workflow argument of the same name. muster start workflow and muster call now tell their own flags apart through cobra’s flag set – one grammar for both, no hand-kept list to drift – and forward every other --key=value pair. After -- every pair is forwarded as it is, so a workflow or tool argument named like a CLI flag can be passed against a non-default muster: muster start workflow w --endpoint http://muster:8090/mcp -- --endpoint=https://target. muster call did not leak the flags but discarded everything after --. (#1249)
  • Changed

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

    Full Changelog: https://github.com/giantswarm/agent-platform/compare/v4.20.0...v4.20.1

  • Added

    • muster test: faults are named steps and time is a clock the scenario moves. test_redeploy_mock_server replaces a mock backend’s process behind its port – every MCP session forgotten, tools kept, no refused connection in between, the way a rolled pod takes over behind its Service; test_set_mock_server_auth: {required: true|false} flips a running mock between answering anonymously and 401-with-metadata, the rollover of a backend to an OAuth resource server (a mock with a token validator and oauth.required: false starts anonymous). test_advance_clock: {duration} moves muster serve’s clock and every mock authorization server’s clock forward together: the reconnect backoff of a remote MCPServer, the orchestrator’s retry and health ticks and the age of the core catalogue now read a process clock (internal/clock) that the harness advances through a control socket MUSTER_TEST_CLOCK selects – a tick that has become due fires at once, nothing waits, and production binaries keep the system time. pre_configuration.intervals: production leaves those timers on their production defaults (30 s initial backoff, 2 min cap, 30 s ticks, 5 min catalogue age) for scenarios that assert the production schedule; the default short keeps the seconds-long environment knobs. The fault steps and the clock are documented together in “Faults and time” of docs/contributing/testing/scenarios.md. Four scenarios: a backend redeployed behind the same port has its session recovered on the next call, no health probe in between (fails on v5.15.0, #999); on the production schedule a remote server whose gateway answers 504 four times waits 30 s, 1 min, 2 min and again 2 min – never 4 – and is connected within the cap once the gateway serves (fails on v5.9.10 with the clock, #1163); the core catalogue found older than its five-minute age is served as it is and rebuilt once in the background (fails on v5.19.13 with the clock, #1231); a forwardToken backend rolled over from an anonymous pod to a 401 pod and back is never registered globally and keeps serving the signed-in session (fails on v5.7.17, #1135). (#1238)
  • Fixed

    Full Changelog: https://github.com/giantswarm/muster/compare/v5.21.0...v5.21.1

  • Fixed

    Full Changelog: https://github.com/giantswarm/klausctl/compare/v0.4.6...v0.4.7

  • Added

    • The Substrate worker pool gets a PodDisruptionBudget; the pool template’s Karpenter knobs are documented and verified; a spread value is refused until the Substrate line carries it (giantswarm/agent-platform#472). The platform runs every agent on the one WorkerPool (kagent.substrateWorkerPool, four gVisor workers, one actor each), and nothing guarded its workers against voluntary disruption: no budget selected them (the kagent namespace carried budgets for the controller and the database only), no annotation kept consolidation off their node — while on gazelle (2026-09-15) Karpenter had bin-packed all four onto one spot m6a.xlarge, so one consolidation or reclaim took the whole pool down: every turn in flight failed, every session waiting for a person was lost (its pause checkpoint is node-local, giantswarm/giantswarm#37795), new turns failed no free workers available. Decided 2026-09-15: the pool stays on spot, the workers get a budget only. New: kagent.substrateWorkerPool.podDisruptionBudget (enabled: true, maxUnavailable: 1, unhealthyPodEvictionPolicy: AlwaysAllow) — a PodDisruptionBudget named after the pool in the kagent namespace, rendered by the connectivity chart the way muster-valkey’s is (#439; the kagent chart has no budget template), selecting the worker pods by Substrate’s ate.dev/worker-pool: <pool> label, so a voluntary drain moves one worker at a time (ALLOWED DISRUPTIONS 1 with four Ready workers); the key never reaches the kagent release (components.kagent.omitKeys); the four budgets of the connectivity chart now share one spec helper and its guards (agent-platform.podDisruptionBudget.spec, the render unchanged). Documented in values.yaml, both READMEs and UPGRADE.md, unset by default: template.annotations karpenter.sh/do-not-disrupt: "true" (consolidation, drift and expiry never drain a worker node; a drift roll then waits on the NodePool’s terminationGracePeriod) and template.nodeSelector karpenter.sh/capacity-type: on-demand (the only knob that covers a spot interruption; Karpenter-only — never on CAPZ, on-prem or a cluster-aws node pool), and the roll every spec.template change causes (the Deployment once, one worker at a time under the budget, turns in flight on a replaced worker lost, goldens untouched). The render now refuses what WorkerPool.spec.template would prune silently (agent-platform.validateWorkerPool): topologySpreadConstraints and podAntiAffinity — carried by no published Substrate release yet (the carried patch giantswarm/giantswarm#37797) and pruned by the CRD’s structural schema, so a spread value looked applied and did nothing — are refused naming the key and the range until components.substrate.versionRange’s floor is the release that carries them (agent-platform.substrate.workerPoolSpreadFloor, empty until then) and forwarded verbatim from it on; any other key the template does not have is refused the same way (a typo was pruned in silence); the floor is read by the new agent-platform.semverRangeFloor. kagent.controller.substrate.defaultWorkerPool is marked inert (the kagent chart renders SUBSTRATE_DEFAULT_WORKERPOOL_*, nothing in the line reads them). make verify-workerpool asserts all of it: the budget from the connectivity chart of the working tree with the pool’s selector and maxUnavailable: 1, gone with enabled: false, never on the kagent release; the annotation and the capacity-type selector reaching the kagent release and the resolved kagent chart’s WorkerPool verbatim; the two spread keys and an unknown key refused; still exactly one WorkerPool. make verify-disruption counts the worker budget among the connectivity chart’s; the golden render leaves it off on both sides. Every installation with kagent on gets one new object (the budget); no pod rolls.
  • Added

    • muster test: scenarios can run their instance in Kubernetes mode. pre_configuration.mode: kubernetes starts one envtest control plane per run from the binaries KUBEBUILDER_ASSETS points at, installs the CRDs from helm/muster-crds, applies the scenario’s mcp_servers and workflows as CRs into a namespace of its own and runs muster serve with kubernetes: true against it – informers, reconciler, boot pass and CR status the way an installation has them. A TCP proxy the harness owns sits between muster and the API server: apiserver.reachable_after keeps it closed for that long after the process started (the API server late at start) and test_set_apiserver_reachable: false|true cuts and restores it mid-run. test_patch_cr applies a merge patch to a CR (spec.suspended, labels, spec.auth.authorizationServer) so the reconciler is driven by real CR updates; test_get_cr reads a CR with the status muster wrote; test_set_mcpserver_labels and test_pin_mcpserver_authorization_server update the CR in this mode. muster test --mode filesystem|kubernetes runs one definition source; without KUBEBUILDER_ASSETS the Kubernetes-mode scenarios are reported as skipped with the reason, never as passed, and make test-envtest (the test-envtest CI job) runs them against envtest. Four scenarios reproduce bugs the filesystem-mode suite could not express: a suspended server is not started by the boot pass (fails on v5.19.6, #1216), a suspended remote server is stopped once and not on every resync tick (fails on v5.19.0, #1212), the boot pass and the reconciler’s first pass register a dozen CRs without an ERROR (fails on v5.19.8, #1222), and an API server unreachable at start is waited for instead of swapped for the filesystem client (fails on v5.7.18, #1143); two more cover a boot from CRs and an API server gone mid-run. (#1237)

    Fixed

    • muster test: the process test_restart_instance starts no longer dies when the restart step returns. It was bound to the step’s context, so a restart step with a timeout killed the new muster serve right after it had passed readiness and the next step found the connection reset.