Skip to content

feat(meta): Agent Substrate ships inside the chart — the Substrate line's substrate and substrate-crds components on one pin, the bootstrap hook, Substrate's database on the platform CNPG Cluster, the snapshot location, atelet scheduling, PolicyExceptions and network policies for the worker pods and Substrate's hops - #359

Merged
teemow merged 3 commits into
mainfrom
substrate-in-chart
Sep 11, 2026

Conversation

@teemow

@teemow teemow commented Sep 11, 2026

Copy link
Copy Markdown
Member

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 (bumblebee-plans#51 D3) decided that Substrate ships inside meta chart 4.0 and goes to every installation — installed, bootstrapped and upgraded from the same chart and GitOps chain as kagent, with no imperative step per installation. Until now Substrate was cluster infrastructure installed outside the chart (agentlab installs the charts and runs a Go port of kubectl-ate admin during up; kagent.controller.substrate.* points at ate-system).

Solution

Substrate is a component of the 4.0 chart. Base main (4.0 is on main since #341; #299 and #345 landed before this — the branch is rebased on them). Three commits: Substrate in the chart, the Substrate pin as the line's release range, the release history of a component with its own targetNamespace stored there. Merge order on the connectivity 4.0.x train: this → #361 (#344) → #354 (#346).

Delivery route — roster components, not the kagent charts' substrate subchart switches. components.substrate-crds (the three ate.dev CRDs as templates) and components.substrate (the control plane in ate-system, the podcertificate-controller in its namespace) from the Giant Swarm Substrate line (oci://ghcr.io/giantswarm/substrate/helm). Both follow components.kagent when unset (the render refuses kagent on with either off — agent-platform.validateSubstrate), both land in ate-system through the new roster key components.<name>.targetNamespace, both carry one version pin — the same range in both versionRanges, its floor the tag of kagent.substrateWorkerPool.workerImage, held to one by make verify-components. Why: each release has its own Flux status on an installation, the order is explicit, the repository is a value (a mirror for a proxied installation, with substrate.image.registry), Substrate's namespace is not kagent's, and no fork stamping of SUBSTRATE_REPO into the kagent charts is needed. The pin is the line's release range >=0.0.27-gs.2 <0.0.28-0 (the kagent entry's shape), its floor 0.0.27-gs.2 the BOM pin and the worker image's tag. v0.0.27-gs.2 is upstream's anticipated 0.0.27 with the carried patches this chart forwards values for: the egress gateway authorizes a resuming actor at CONNECT time as a frontend policy of its agentgateway v1.5.1-gs.2 dataplane (giantswarm/substrate#7, #9), ate-api-server reads its connection string from a Secret and the atelet DaemonSet takes scheduling knobs (#8).

Install order (implements the program's decision on the cut-over sequencing): substrate-crds, kagent-crdsagent-platform-connectivitysubstratekagent → the managers. The connectivity release dependsOn the CRD components and never kagent or substrate; those two dependsOn connectivity, whose hooks mint what their pods start against — so nothing waits on a pod that waits on a later release, and components.kagent.installDisableWait goes.

Bootstrap — the connectivity release's pre-install,pre-upgrade hook Job <release>-substrate-bootstrap (templates/substrate/bootstrap.yaml; the agent-platform.hooks.job include in templates/_hooks.tpl, identity <release>-hooks): mints the CA pools service-dns-ca-pool, pod-identity-ca-pool, the JWT authority pool actor-id-jwt-pool, the CA pool actor-id-ca-pool, the trust anchor actor-id-ca-certs and the ConfigMap ate-api-authentication with the apiserver's own issuer (from its OpenID discovery document — the FQDN on kubeadm, the IRSA issuer on CAPA) with openssl in an init container and kubectl, in the pools' wire format; creates the two namespaces bare when missing (the substrate release adopts them); never touches a pool that exists — every upgrade logs present. Neither line publishes a kubectl-ate image, and kubectl-ate admin make-*-pool is a plain Create (not idempotent), so the Job is a shell port of the same key material rather than a wrapper around it.

Database on CNPGpostgres.databases (a map: Helm merges maps, so the chart ships defaults and an installation adds entries; 1b's #346 adds kagent-v2 to the same map): one CNPG Database <clusterName>-<key> per entry plus a derived connection Secret <clusterName>-<key>-app written by the post-install,post-upgrade hook <release>-postgres-databases into postgres.namespace and every secretNamespaces entry (CNPG mints <clusterName>-app for the initdb database only). The shipped substrate entry is ate-api-server's database; substrate.postgres.enabled: auto resolves in the meta chart's shape pass (the Cluster's Database with postgres.enabled and a derived substrate.postgres.connectionStringSecretRef; the bundled StatefulSet without the Cluster — a lab). The substrate chart's postgres.connectionStringSecretRef is the carried patch giantswarm/substrate#8 (cherry-pick of the open upstream kagent-dev/substrate#32).

Snapshot storekagent.harness.snapshotLocation (the contract name; #344 renders it into the Harness) is required whenever kagent is on; S3 via IRSA on CAPA (bucket provisioning documented, not rendered), an S3-compatible store through substrate.atelet.extraEnv, a lab's in-cluster store.

Schedulingsubstrate.atelet.{nodeSelector,tolerations,affinity} (carried patch, giantswarm/substrate#8, in v0.0.27-gs.2; default: every node the DaemonSet controller admits — the worker nodes on a cluster with tainted control planes); the WorkerPool pinned to amd64 (kagent.substrateWorkerPool.template.nodeSelector).

Kyverno — one PolicyException per workload (substrate-atelet, substrate-workers on ate.dev/worker-pool, substrate-control-plane, substrate-podcertificate-controller), each naming exactly the rules its pod spec violates with their autogen- copies; kyvernoPolicies.rules (rule → ClusterPolicy) replaces the four seccomp*/volumeTypes* keys; the kagent-declarative-seccomp exception (app: kagent) is gone. New make verify-kyverno computes every workload's violations from the rendered pod specs (and the worker pod from ate-controller's template) and holds the exceptions to them.

Network policies (both flavours, templates/substrate/netpol.yaml) — Substrate's hops and the actors' destinations on the egress gateway atenet-egress, where an actor's outbound connections leave (muster, the kagent controller, the LLM listener/provider, DNS); the worker pods reach only the egress gateway, the dns and the cluster DNS; the app: kagent agent-pod rules go; the kagent controller opens ate-api and the router and admits the egress gateway.

Prerequisites — README "Agent Substrate", UPGRADE.md: Kubernetes 1.35, the three gates on the three components, giantswarm/cluster#1005 and the cluster chart's bridging knob, the gVisor asset for proxied installations, one architecture per pool. A live render refuses a cluster without certificates.k8s.io/v1beta1/PodCertificateRequest, naming the gates (kubelet's undetectable — the message says so); helm template/CI never refused. Security write-up: docs/substrate-security.md.

Verification

Not lab-verified, and why

Kyverno admission (the lab has no Kyverno), Cilium enforcement of the network policies (no CNI policy enforcement in kind), the CNPG database and derived Secret for Substrate (the lab runs kagent's bundled Postgres, no CNPG operator — render-proven by verify-postgres; the derived-Secret hook was proven against a CNPG-shaped Secret by #354's lab run), IRSA against S3 (the lab's store is the bundled in-cluster one). The runtime proof of the policies is the first Giant Swarm installation of the rollout. Two lab-tooling gaps met on the way, worked around in the window's overlay and left to giantswarm/agentlab#138 (the lab consuming Substrate from the chart as an installation will): agentlab platform hard-fails its post-install wait on the agent-manager MCPServer when components.agent-manager is legitimately off, and its chartPath-shaped values set kagent.serviceMonitor.enabled: true for a kagent line that serves no /metrics. agents-test / toolsets-test on agent-manager 1.x's composed release are giantswarm/agentlab#140.

Teammate alignment

Fixes #342

@teemow
teemow requested a review from a team as a code owner September 11, 2026 00:43
@teemow
teemow changed the base branch from kagent-line-4-0 to v1alpha3-templates-shared-muster-retired September 11, 2026 00:51
@circleci-architect

circleci-architect Bot commented Sep 11, 2026

Copy link
Copy Markdown

Published Helm chart agent-platform

4.2.1-dev.substrate-in-chart.2026-09-11.03-54-03.ha38b66c
Chart agent-platform
Version 4.2.1-dev.substrate-in-chart.2026-09-11.03-54-03.ha38b66c
OCI reference oci://gsoci.azurecr.io/charts/giantswarm/agent-platform:4.2.1-dev.substrate-in-chart.2026-09-11.03-54-03.ha38b66c
Digest sha256:307cad0cffa4fdd203a4c9efdbdcbabe2575c57c256d95358b04f128dac68218
Registry public — gsoci.azurecr.io
Git catalog giantswarm-test-catalog (index)
Pull this chart
helm pull oci://gsoci.azurecr.io/charts/giantswarm/agent-platform --version 4.2.1-dev.substrate-in-chart.2026-09-11.03-54-03.ha38b66c

Posted by architect-orb · build 5445 · commit a38b66c · updated in place on every push

@circleci-architect

circleci-architect Bot commented Sep 11, 2026

Copy link
Copy Markdown

Published Helm chart agent-platform-connectivity

4.2.1-dev.substrate-in-chart.2026-09-11.03-54-03.ha38b66c
Chart agent-platform-connectivity
Version 4.2.1-dev.substrate-in-chart.2026-09-11.03-54-03.ha38b66c
OCI reference oci://gsoci.azurecr.io/charts/giantswarm/agent-platform-connectivity:4.2.1-dev.substrate-in-chart.2026-09-11.03-54-03.ha38b66c
Digest sha256:79fdcad2f559a83d9f7a903f5b926cf063130b04837650829b01c27aec95385e
Registry public — gsoci.azurecr.io
Git catalog giantswarm-test-catalog (index)
Pull this chart
helm pull oci://gsoci.azurecr.io/charts/giantswarm/agent-platform-connectivity --version 4.2.1-dev.substrate-in-chart.2026-09-11.03-54-03.ha38b66c

Posted by architect-orb · build 5444 · commit a38b66c · updated in place on every push

@teemow
teemow changed the base branch from v1alpha3-templates-shared-muster-retired to main September 11, 2026 02:35
@teemow
teemow force-pushed the substrate-in-chart branch 2 times, most recently from 9fbaaf6 to 968194b Compare September 11, 2026 02:58
…rate + substrate-crds from the Giant Swarm Substrate line on one pin, the connectivity release's bootstrap hook (CA/JWT pools, trust anchor, ate-api authentication), Substrate's database on the platform CNPG Cluster (postgres.databases), kagent.harness.snapshotLocation, atelet scheduling and the WorkerPool architecture pin, PolicyExceptions per workload, network policies of Substrate's hops in both flavours, the 1.35 + feature-gates guard
…s.2 <0.0.28-0)

components.substrate and components.substrate-crds move from the exact dev
build the kagent line was proven with to the Giant Swarm Substrate line's
release range, the kagent entry's shape; the range's floor 0.0.27-gs.2 is
the customer BOM's pin and the tag of kagent.substrateWorkerPool.workerImage.

v0.0.27-gs.2 is upstream's anticipated 0.0.27 with the carried patches the
chart forwards values for: the egress gateway authorizes a resuming actor at
CONNECT time as a frontend policy of its agentgateway dataplane (v1.5.1-gs.2),
ate-api-server reads its PostgreSQL connection string from a Secret
(postgres.connectionStringSecretRef — the CNPG mode's derived Secret) and the
atelet DaemonSet takes nodeSelector, tolerations and affinity.

tests: verify-components holds the floor to the BOM pin and the worker image;
verify-components-charts drops the Substrate UNRELEASED entries and the
worker-image fallback (the release resolves); verify-kyverno renders the
release. Docs and the helm-docs README follow.
… history there

components.<name>.targetNamespace now also sets the HelmRelease's
storageNamespace, so the Helm release history lives where the release
installs — where `helm -n <namespace>` stores it. Without it helm-controller
kept the history in the platform namespace, found no release there and
installed a second `substrate` release beside one installed by hand in
ate-system (the lab's), instead of adopting it by name. Components without
their own targetNamespace are unchanged (the fleet's flux-giantswarm history
stays where it is). verify-components asserts it for substrate and
substrate-crds.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment