The generic Helm chart for creating kagent agents on the
Giant Swarm Agent Platform. One chart release = one agent: the chart
renders the agent's Agent (api.kagent.dev/v1alpha3) with its template
inline (spec.template: what the agent is: prompt, model, skills, plugins,
tools) and the platform Harness referenced by name
(spec.harnessRef: how it runs), and, unless the agent is chat-only, the
agent's own muster RemoteMCPServer, the carrier of its toolset.
The design: one release of this chart per agent, with the agent's own muster
RemoteMCPServer carrying its toolset. Charts 1.x and 2.x target the kagent API v2
shape, where how an agent runs is a separate Harness the agent references.
The chart renders, in the release namespace and named after the agent:
- the
Agent:spec.templatecarries the description, the system prompt, theModelConfigby name, the skills and plugins (immutable sources) and the tool bindings;spec.harnessRefnames the Harness that runs it (agent.harness, defaultkagent). The object carries the annotationsui.giantswarm.io/display-nameandui.giantswarm.io/icon-urlthe Dev Portal reads. No admission label: the Agent selects its Harness, the Harness admits nothing; - the
RemoteMCPServerpointing at muster's MCP endpoint (muster.url),STREAMABLE_HTTP, with the agent's toolset as the staticX-Muster-Toolsetheader and the labelkagent.dev/discovery: disabled(muster is an OAuth resource server; the controller has no user token to discover tools with). Never anAuthorizationheader: the Harness propagates the signed-in person's token, and a static header would override it. The Agent's template binds this server. Not rendered for a chat-only agent (toolset: ["preset:none"]) or withmuster.enabled: false.
It never touches:
- the
Harness— platform-owned (rendered by the agent-platform connectivity chart); it decides the runtime image, the token propagation, the Substrate worker pool, capacity and placement. An Agent whose Harness does not exist in its namespace reportsResolvedRefs=Falseand never becomes Ready; ModelConfigCRs and theSecrets they reference (LLM credentials) — platform-owned, provisioned per tenant namespace. The chart wires the agent to one by name (modelConfig.name, defaultdefault-model-config).
Install with whatever you already use for Helm charts (Argo CD, Flux
HelmRelease, helm install). A minimal values file yields a working agent:
agent:
displayName: "SRE Assistant" # Unicode, becomes the ui.giantswarm.io/display-name annotation
description: Helps the SRE team triage incidents.
systemMessage: |
You are an SRE assistant. Be concise.
skills:
- name: sre-runbooks
git:
url: https://github.com/example/agent-skills
commit: 0123456789abcdef0123456789abcdef01234567 # a full commit id, never a branch
path: sre-runbooks
labels:
giantswarm.io/owner: sre-teamThe technical resource name defaults to the Helm release name. By default the agent is wired to the platform's muster gateway with all of its (dynamic) tools — implicit full access to everything the gateway exposes to the invoking human. Declare a toolset to compose the agent with a subset:
toolset:
- preset:read-only # a preset muster knows (built in: read-only, none, full;
- workflow:incident-triage # the platform ships infrastructure and agent-platform)
- server:mcp-kubernetes # every tool of one MCP server
- tool:x_mcp-prometheus_query # one exposed toolThe chart renders the selectors as the static X-Muster-Toolset header on the
agent's RemoteMCPServer (spec.headersFrom, joined by ,); muster resolves
it on every request, so the agent's meta-tools list and call only what the
toolset selects. Exactly ["preset:none"] renders no RemoteMCPServer
and no muster binding at all — a chat-only agent. An empty list fails the
render (say preset:none), more than 32 inline selectors fail (define a
preset), and toolset:<name> is reserved. Composition, not authorization: the
human's identity and the backends' own authorization remain the boundary. The
agent gets no wider access of its own.
muster.tools narrows the binding to a subset of muster's meta-tools
(list_tools, call_tool, ...), not the tools behind the gateway — a toolset
is the way to narrow those. A Harness enforces a partial selection only when
it can discover the server's tools; with discovery off (the default) it may
expose the whole server and report a warning in the Agent's
status.warnings.
A skill source is immutable: a git repository at a full 40- or 64-hex commit
id, or an image at a @sha256: digest. A branch, a tag, a short SHA or an
image tag fails helm template with a message naming the field — the same
rule the Agent CRD enforces at admission, applied where the values are
written. The agent-creation flows (agent-manager, the Dev Portal) resolve
the branch a skill is picked from to its head commit and re-pin on request;
the chart itself carries no branch.
A plugin is an Agent Plugins bundle: one immutable source (pinned like a skill) and the names of the bundle's skills to enable. Nothing else in the bundle reaches the agent:
plugins:
- git:
url: https://github.com/example/agent-plugins
commit: 0123456789abcdef0123456789abcdef01234567
path: bundles/sre
skills: [triage, postmortem]
- oci: registry.example.io/plugins/sre@sha256:<digest>
skills: [k8s]Rendered into spec.template.plugins[] as {source: {git | oci, path}, skills}. The schema refuses an entry without a source, with two sources, with
a mutable source or without at least one skill name.
One chart value covers every private git skill and git plugin of an agent; authors do not pick a credential per skill:
skillsGitAuthSecretRef:
name: kagent-skills-token # a Secret in the agent's namespace, key `token`When set, every skills[] and plugins[] entry with git renders
source.git.credentialRef: {name, key: token}; OCI sources are unchanged.
Every git skill and plugin then needs an https:// URL: the CRD refuses a
credential on any other source and the chart fails the render first, naming
the entry. The
platform side of the contract (the egress gateway, the credential provider,
agent-manager's minted Secret) is agent-platform's
Private skill repositories
(under "The kagent line").
- Who provisions it, where. The tenant, in the agent's namespace (on the
platform: the
kagentnamespace, where the agents are compiled), labelledui.giantswarm.io/agent-skills-git-auth: "true"so the Dev Portal offers it; or agent-manager, which mints and refreshesagent-manager-skills-tokenfrom its skills GitHub App when the installation turns that on. The chart never creates it. - Shape. The key
tokenholdsbase64("<username>:<token>")— on GitHubx-access-token:<token>, a fine-grained PAT or App installation token with read access to the repositories — the value the egress gateway sends asAuthorization: Basic. - One credential per host. It goes with every request to that host, public repositories included; two Secrets for one host in an agent are refused.
- Rotation. Replace the Secret's contents, never its name. The egress gateway reads the Secret on every fetch, so a new token needs no restart; a new name is an edit of every referencing agent and a new revision of each.
- Where the token is visible. In the Secret, to whoever may read Secrets in
its namespace, and to the platform's credential provider and egress gateway,
which send it to the source's host over TLS. Not in the
Agent, the actor's environment (it holds a placeholder) or the snapshots.
What an author sees when it goes wrong (the Agent's conditions, see Readiness):
| Symptom | Cause |
|---|---|
Ready=False ActorTemplateRetrying quoting the git fetch's failure, then ActorTemplateFailed (golden boot 6 of 6 failed (…); no retries left) |
the Secret is missing, the token is wrong or lacks read access, or the namespace has no credential-provider policy (atespace "ate-golden" is not permitted to resolve secrets) |
Compatible=False UnsupportedConfiguration (conflicting credentials for <host> header authorization) |
two credentials for one host |
The field needs the kagent line 1.1.0 or later (giantswarm/kagent-upstream, the platform's line), where the egress gateway injects the credential.
The Agent reports its own readiness on the Harness it names:
kubectl -n <namespace> get agent.api.kagent.dev <name> -o jsonpath='{.status}'Conditions Accepted, ResolvedRefs, Compatible and Ready, with
desiredRevision against latestSuccessfulRevision and warnings.
ResolvedRefs=False naming the Harness means agent.harness names a Harness
that does not exist in the agent's namespace.
See the chart values reference for all available values and their defaults.
values.schema.json encodes the curated contract (additionalProperties: false), so bad values (including every 0.x value that has no place in 2.x)
fail with a legible error before anything hits the cluster.
Upgrades are values changes plus a re-apply; uninstalling the release removes the agent. Pin the chart version for reproducibility.
Chart 2.0 renders the kagent API that ships the Agent kind
(api.kagent.dev/v1alpha3, kagent-dev/kagent#2952): one Agent named after
the release, the template inline under spec.template, the Harness by name in
spec.harnessRef, plus the per-agent RemoteMCPServer in the new group. No
kagent.dev/v1alpha3 AgentTemplate is rendered, and the admission label is
gone with Harness.spec.allowedAgentTemplates. The chart installs only on a
platform that serves api.kagent.dev; an upgrade of a 1.x release replaces
the AgentTemplate with an Agent of the same name. The values contract is
the 1.x one plus plugins:
| 1.x value | 2.x | What changed |
|---|---|---|
agent.harness |
same name, default kagent |
now spec.harnessRef.name, a reference the Agent resolves in its namespace; the label agent-platform.giantswarm.io/harness is no longer rendered and no Harness selector is involved |
agent.name, agent.displayName, agent.description, agent.iconUrl, agent.systemMessage, modelConfig.name, skills, skillsGitAuthSecretRef, muster.*, toolset |
unchanged | the template fields move under spec.template; the annotations stay on the object |
| — | plugins[] (new, empty) |
{git | oci, path, skills[]} entries rendered into spec.template.plugins[]; same pinning rules and credential as skills |
extraTools |
same list | a sub-agent binding is {subAgent: {name, description, templateRef}} instead of {agent: {...}} |
extraAgentSpec |
unchanged | deep-merges into spec.template only, never into spec.harnessRef |
labels |
unchanged | an extra agent-platform.giantswarm.io/harness label is rendered as given and means nothing to the platform |
context.compaction |
unchanged | still accepted, still renders nothing |
Chart 1.0 renders the kagent API v2 shape (kagent.dev/v1alpha3): an
AgentTemplate plus a per-agent RemoteMCPServer instead of a
kagent.dev/v1alpha2 Agent. It installs only on a platform that serves that
API, and the values contract changes with it. Value by value:
| 0.x value | 1.x | What changed |
|---|---|---|
agent.name, agent.displayName, agent.description, agent.systemMessage |
unchanged | systemMessage renders into spec.systemPrompt; the display name stays the ui.giantswarm.io/display-name annotation |
agent.iconUrl |
unchanged | rendered as the ui.giantswarm.io/icon-url annotation (the template has no icon field) |
agent.runtime |
removed | the Harness is the runtime; the platform Harness runs the Go ADK |
| — | agent.harness (new, default kagent) |
the value of the admission label agent-platform.giantswarm.io/harness |
modelConfig.name |
unchanged | renders into spec.modelConfig.name |
skills.refs[] (image references) |
skills[].{name, oci} |
a digest-pinned reference <ref>@sha256:<digest>; a tag is refused |
skills.gitRefs[] ({url, ref, path}) |
skills[].{name, git: {url, commit}, path} |
a full commit id; a branch, tag or short SHA is refused; names are unique |
skills.gitAuthSecretRef ({name}) |
skillsGitAuthSecretRef.name (since 1.2) |
a sibling of the skills list; one Secret (key token) rendered as credentialRef on every git skill; every git skill needs an https URL |
muster.enabled |
unchanged | gates the RemoteMCPServer and the binding |
muster.serverRef.* |
removed | the chart renders the agent's own RemoteMCPServer, named after the agent, in its namespace |
muster.allowedHeaders |
removed | the Harness propagates the caller's token (KAGENT_PROPAGATE_TOKEN) |
muster.toolNames |
muster.tools |
the binding's tools (muster's meta-tools) |
muster.stsWellKnownUri |
removed | the platform's dex-only trust model has no token exchange |
| — | muster.url (new) |
muster's MCP endpoint; default http://muster.agent-platform.svc.cluster.local:8090/mcp |
| — | muster.discovery.enabled (new, default false) |
false labels the server kagent.dev/discovery: disabled |
toolset |
unchanged | the header moves to the RemoteMCPServer's spec.headersFrom; grammar and render failures unchanged |
extraTools |
same list, v1alpha3 entries | {mcp: {server: {kind: RemoteMCPServer, name}, tools, requireApproval}} or {agent: {name, description, templateRef}} instead of {type: McpServer, mcpServer: {...}} |
replicas, resources, nodeSelector, tolerations |
removed | capacity and placement are the Harness's worker pool — platform values |
labels |
unchanged | merged over the standard set on both objects |
annotations |
unchanged | merged next to the ui annotations on the AgentTemplate |
extraAgentSpec |
unchanged | deep-merges into the AgentTemplate spec (v1alpha3 fields) only — never into the RemoteMCPServer |
Every removed value is refused by the values schema, so a stale composer fails
the render instead of silently losing a field. The 0.x line lives on as chart
0.x (release-v0.x) for platforms that still run kagent 0.10.