Skip to content

Meta chart 4.0: Agent Substrate ships inside the chart — the substrate/substrate-crds charts at the version the kagent pin requires, an idempotent CA/JWT-pool bootstrap Job, Substrate's Postgres on CNPG, the snapshot location as a per-installation value, a node selector for atelet, PolicyExceptions and network policies for the worker pods; Kubernetes 1.35 and the three feature gates as documented prerequisites (bumblebee-plans#51 D3) #342

Description

@teemow

Problem

kagent API v2 has no runtime without Agent Substrate: a Harness requires spec.substrate.workerPoolRef and snapshotPolicy.location, and every agent runs as a Substrate actor in a gVisor worker pod. The plan decided (bumblebee-plans#51 D3, grill of 2026-09-10) that Substrate ships inside meta chart 4.0 and goes to every installation — the Giant Swarm installations in the plan's order, then the customer installations — installed, bootstrapped and upgraded from the same chart and GitOps chain as kagent, with no imperative step per installation. The dev line poc/kagent-main (#330) treats Substrate as cluster infrastructure installed outside the chart (kagent-crds.substrate.enabled: false, the kagent chart's substrate subchart off, controller.substrate.* pointing at ate-system, substrateWorkerPool kagent-default with four gVisor workers on the line's ateom-gvisor build, ghcr.io/giantswarm/substrate/ateom-gvisor:0.0.27-dev…); agentlab installs the charts and runs a Go port of the bootstrap during up. That was a POC-phase arrangement.

What Substrate needs on a Giant Swarm management cluster (the plan's research notes 2026-09-09-substrate.md and 2026-09-09-dev-channel.md):

  • The charts substrate-crds and substrate come from the Giant Swarm Substrate line (giantswarm/substrate, giantswarm/giantswarm#37757): oci://ghcr.io/giantswarm/substrate/helm, images under ghcr.io/giantswarm/substrate, at the line's build of the version the pinned kagent commit requires (kagent's go.mod replace directive → v0.0.26 today; the line publishes 0.0.27-dev.giantswarm.* builds and vX.Y.Z-gs.N releases, its FORK.md is the ledger). The kagent and kagent-crds charts carry both as switchable subcharts. Whichever delivery route is chosen (the kagent charts' substrate subchart switches — which then must be stamped with the line's repository and version by the fork, whose export still points at upstream's SUBSTRATE_REPO — or roster components), the installed Substrate is the line's.
  • The bootstrap objects the charts mount but do not create: the CA pools service-dns-ca-pool and pod-identity-ca-pool (namespace podcertificate-controller-system), actor-id-jwt-pool, actor-id-ca-pool and the derived actor-id-ca-certs Secret (namespace ate-system), and the ate-api-authentication ConfigMap naming the cluster's service-account issuer — the apiserver's FQDN issuer, not a short form. Upstream does this with four kubectl-ate admin invocations (kagent's scripts/setup-cluster/setup-cluster.sh); the pool commands are Go in kubectl-ate's admin packages, and agentlab carries an in-cluster Go port of them (substratepools.go).
  • Postgres for Substrate's control plane (a bundled StatefulSet by default; postgres.connectionString for an external database) and a snapshot object store (bundled RustFS with static credentials by default — a demo value; atelet uses the standard AWS credential chain, so S3 via IRSA works on CAPA; other providers' stores are open).
  • Privileges restricted PSS refuses: atelet is a privileged DaemonSet with hostPorts 8085/9090 and hostPath mounts and has no node selector in the chart (it lands on control-plane nodes too); worker pods run as root with SYS_ADMIN, SYS_PTRACE, NET_ADMIN and more, AppArmor and seccomp Unconfined, a hostPath with HostToContainer propagation; the other Substrate Deployments declare no securityContext at all. Kyverno enforces restricted on the fleet, so each needs a PolicyException — the platform already ships exceptions in the exception namespace (kyvernoPolicies.policyExceptionNamespace; templates/kagent/policy-exception.yaml, templates/postgres/policy-exception.yaml).
  • Cluster-scoped objects the chart brings: the SandboxConfig (per-architecture gVisor asset URL and sha256, fetched by atelet from gs://gvisor/releases/... at run time — not an OCI image, so a proxied installation needs it mirrored and re-pointed) and a ValidatingAdmissionPolicy.
  • Mixed-architecture WorkerPools do not work (the golden snapshot is architecture-specific); the fleet's management clusters are single-architecture, so pools are pinned to one.
  • Network policy: the connectivity chart's agent-pod CiliumNetworkPolicy and the kagent-declarative-seccomp PolicyException select app: kagent, the label of the v1alpha2 per-agent Deployments — nothing carries it on API v2. The worker pods (kagent's WorkerPool selector label kagent.dev/worker-pool) need egress to muster, the kagent controller, the LLM provider (or the agentgateway LLM listener) and DNS, and Substrate's own hops need rules: ateletate-api, the internal router → a worker's tunnel on :443, the bootstrap API → the node agent (an egress rule upstream lacks), atenet-egress. Found on a Cilium always-enforce cluster in the dev channel: an actor's DNS lookup from the nested network namespace is forwarded rather than translated by Cilium's socket load balancer unless socketLB.hostNamespaceOnly is on (the Giant Swarm Cilium app's default); the internal router's connect to a healthy actor timing out, mutual authentication suspected, is still open there; two hops fail silently (nothing is logged when the bootstrap tunnel refuses an unactivated connection or the router's connect fails).
  • Cluster prerequisites: Kubernetes 1.35 (Substrate uses certificates.k8s.io/v1beta1 PodCertificateRequest, which 1.34 serves only as v1alpha1) and the feature gates ClusterTrustBundle, ClusterTrustBundleProjection, PodCertificateRequest on kube-apiserver, kube-controller-manager and kubelet. feat: enable ClusterTrustBundle, ClusterTrustBundleProjection and PodCertificateRequest feature gates by default cluster#1005 turns them on by default; until that release reaches an installation the staff-only internal.advancedConfiguration.*.featureGates knob bridges (repeating the chart's default entries — Helm replaces lists). --runtime-config=api/all=true already serves the API group. Enabling them rolls the control plane and every node. The per-installation values are the fleet's work (giantswarm/shared-configs#732, extras/agent-platform under meta chart 4.0 (kagent API v2): per-installation cut-over pattern, the base README contract, and the final bound advance management-cluster-bases#738); this chart states the prerequisites.

Proposed solution

Substrate is a component of the 4.0 chart:

Acceptance criteria

Dependencies

Plan: bumblebee-plans#51 — PRD decisions D3, D5, D15; sections "The kagent line the platform runs" (the Substrate bullet), "Connectivity and meta chart", "Further Notes"; research 2026-09-09-substrate.md, 2026-09-10-cluster-chart-feature-gates.md. Epic: giantswarm/giantswarm#37705. Dev line: #330 (not modified by this issue). Related: #317 — the runtime slice for workload clusters includes Substrate under 4.0.

Implementation (PR #359, on main after #350, #360 and #353)

Decisions made while implementing, folded in here so the description stays the source of truth:

State: PR #359 lab-proven and CI-green, ready to merge. Lab proof (agentlab-dev2, under the lab lock, 03:14–03:52Z, three windows): with platform.chartPath on the branch and platform.substrate.enabled: false, helm-controller adopted agentlab's substrate/substrate-crds releases in ate-system by name ("found existing release in storage" → upgrade, revision 33 → 34 at 0.0.27-gs.2, one history); HelmReleases substrate-crds, substrate, kagent-crds, kagent, agent-platform-connectivity Ready with the dependsOn graph as designed; every ate-system pod Running on gs.2 (ate-api-server ×2, ate-controller, atelet on every node, atenet-egress 2/2 on agentgateway v1.5.1-gs.2, atenet-router 2/2, dns); WorkerPool kagent-default on ateom-gvisor:0.0.27-gs.2 ready 4/4; the bootstrap hook Job completed and a second agentlab platform left every pool at the same resourceVersion (the CA/JWT pools' uid/resourceVersion equal the BEFORE snapshot throughout — no kubectl-ate step); agentlab platform-test PASS; an AgentTemplate reached Ready on the platform Harness — an actor booted on the chart-installed WorkerPool through the chart-installed ate-api/atelet/atenet. The hand-back to agentlab (the lab's own Substrate install) worked without a helm uninstall; the lab was restored after each window. Two lab-tooling gaps for giantswarm/agentlab#138 (worked around in the window's overlay): agentlab platform hard-fails its wait on the agent-manager MCPServer when components.agent-manager is legitimately off; its chartPath-shaped values set kagent.serviceMonitor.enabled: true for a kagent line without /metrics. agents-test/toolsets-test against agent-manager 1.x's composed release are giantswarm/agentlab#140. helm uninstall tear-down order is not lab-proven (the window hands Substrate back instead of uninstalling it). Not lab-verifiable, as the acceptance criteria say: Kyverno admission, Cilium enforcement, CNPG for Substrate (render-proven; the derived-Secret hook proven against a CNPG-shaped Secret), IRSA — the first Giant Swarm installation of the rollout is the runtime proof.

Working rule: kagent, Substrate and related upstream projects

General rule of epic giantswarm/giantswarm#37705, carried on every sub-issue.

Implementing this epic will surface bugs and gaps in kagent, Substrate and the projects around them (kmcp, agentgateway, the Go ADK, the harnesses). For each of them:

  1. Check upstream first. Search the upstream repository's issues and pull requests, open and closed (kagent-dev/kagent, kagent-dev/substrate, kagent-dev/kmcp, …), and the rows already collected in giantswarm/giantswarm#37742. If upstream tracks it or a pull request is in flight, we align with that work — test it, review it, add our evidence — rather than open a competing change.
  2. Change it in our fork. A change we need lands as a carried patch on the consumed branch of our line — giantswarm/kagent-upstream for kagent, giantswarm/substrate for Substrate, the giantswarm/ fork of any other upstream project (created on the same pattern when there is none). One commit per patch, written upstream-ready (upstream's conventions and tests, DCO sign-off, nothing Giant Swarm-specific in it), recorded in the fork's ledger (FORK.md).
  3. Prepare the upstream pull request in the fork; do not open it yet. The upstream-facing branch and pull-request text are prepared in the fork and listed as a row in giantswarm/giantswarm#37742 — that list is the review queue. Nothing is opened against upstream before the team has reviewed it there. We send in considered batches and only what is useful to upstream, never a stream of Giant Swarm-specific changes.
  4. Giant Swarm-specific behaviour never goes upstream. It lives in the meta chart, the connectivity chart, or in a patch the ledger marks as ours to keep.

Upstream issues we want to file follow the same path: collected in giantswarm/giantswarm#37742, reviewed by the team, then filed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions