发布于 2026 年 10 月 12 日,作者:Antonio Berben(Solo.io)和 Felipe Vicens(Telefónica)
本文中重点介绍的 CNCF 项目
如果你看过无代码智能体构建器的演示,你就知道那个瞬间。有人输入一句话,等一分钟,然后它就在那里了:一个新的智能体,正在运行,带着工具。这很惊艳。大约十秒钟。
然后问题就来了。它在哪里运行?它能触碰什么?谁批准了它?
这篇文章讲的是第一部分,那令人印象深刻的十秒钟。我想说明这并非魔法,你可以自己构建它,在一个下午之内,用开源技术,花费最少的 token。这个工具是 kagent,一个 CNCF Sandbox 项目,它将 AI 智能体作为 Kubernetes(容器编排平台) 资源来运行。那些问题会在最后回来,因为它们才是真正的工作。
在 kagent 中,智能体不是你手动启动的进程。它是一个自定义资源。你编写一个 Agent(智能体),包含系统提示词、模型和工具列表。你应用它,控制器就会把它变成一个运行中的 pod,使用 A2A 通信。
一旦智能体只是 YAML,那么一个构建智能体的智能体就只是一个能编写某种 YAML 的智能体。不需要新框架,不需要插件。一个 Agent,三个工具,以及一个知道 Agent 长什么样的提示词。

图 1:一个构建智能体的智能体,运行在 kagent 上。
这里的目标是构建器。安全不在范围内,这是故意的,我想坦率地说明这意味着什么:
这对笔记本电脑来说没问题。但对共享集群来说就不行了,我稍后会回到为什么这个差距很重要。
你需要 Docker(容器化平台)、kind、kubectl、helm、jq 和 Ollama(或者,你可以使用 Claude、OpenAI 或任何其他提供商的 API Key,但我们希望尽可能降低成本)。无需克隆任何内容:你边做边创建每个文件。从一个空文件夹开始:
mkdir kagent-agent-builder && cd kagent-agent-builder
这是让我感到惊讶的部分。通过工具调用编写有效的 Kubernetes 清单比聊天更难,小型模型会暴露这一点。在我的运行中,qwen2.5:7b-instruct 发明了自己的工具块格式,而 qwen2.5:14b-instruct 有时会礼貌地回答却从未调用 apply 工具。qwen3-coder:30b-a3b 在每次运行中都做对了,所以这就是本文使用的模型:
ollama pull qwen3-coder:30b-a3b-q4_K_M # about 18 GB on disk
集群通过 host.docker.internal:11434 访问 Ollama,这在 Docker Desktop 上开箱即用。在 Linux 上,你必须将下面 values 文件中的该主机改为 kind 节点可以访问的地址。我尚未测试该路径。
如果你更喜欢托管模型,kagent 的 Helm chart 也有 OpenAI、Anthropic、Azure OpenAI 和 Gemini 的提供商配置块。当然,那样就不再免费了。
kind create cluster --name kagent-builder
kagent 附带一个带有 Kubernetes 工具的工具服务器,默认情况下该 chart 为其 ServiceAccount 授予 cluster-admin 权限。即使是在笔记本电脑上,由 cluster-admin 支持的构建器也是个坏主意,因此 values 文件改变了这一点。创建它:
providers:
default: ollama
ollama:
provider: Ollama
model: "qwen3-coder:30b-a3b-q4_K_M"
config:
host: host.docker.internal:11434
options:
num_ctx: "32768"
kagent-tools:
tools:
enabledTools:
- k8s
rbac:
readOnly: true
additionalRules:
- apiGroups: ["kagent.dev"]
resources: ["agents"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
grafana-mcp: {enabled: false}
k8s-agent: {enabled: false}
kgateway-agent: {enabled: false}
istio-agent: {enabled: false}
promql-agent: {enabled: false}
observability-agent: {enabled: false}
argo-rollouts-agent: {enabled: false}
helm-agent: {enabled: false}
cilium-policy-agent: {enabled: false}
cilium-manager-agent: {enabled: false}
cilium-debug-agent: {enabled: false}
该模型成为默认的 ModelConfig。工具服务器只加载其 Kubernetes 工具(共 22 个)。它获得一个只读角色外加一条额外规则:它可以写入 agents.kagent.dev,仅此而已。最后一块关闭了 chart 默认安装的示例 agents,因此 UI 只显示你的。
使用该文件安装 CRD 和 kagent 0.10.2,然后等待 Pod 就绪:
helm upgrade --install kagent-crds oci://ghcr.io/kagent-dev/kagent/helm/kagent-crds \
--kube-context kind-kagent-builder \
--version 0.10.2 --namespace kagent --create-namespace
helm upgrade --install kagent oci://ghcr.io/kagent-dev/kagent/helm/kagent \
--kube-context kind-kagent-builder \
--version 0.10.2 --namespace kagent --values kagent-values.yaml
for d in kagent-controller kagent-ui kagent-tools; do
kubectl --context=kind-kagent-builder -n kagent rollout status deploy/$d --timeout=300s
done
这就是整个构建器。创建该文件:
apiVersion: kagent.dev/v1alpha2
kind: Agent
metadata:
name: agent-builder
namespace: kagent
spec:
description: Builds new kagent agents from a plain-language description and deploys them.
type: Declarative
declarative:
modelConfig: default-model-config
stream: false
systemMessage: |
You build kagent agents. The user describes, in plain language, an agent
they want. You turn that into ONE Kubernetes manifest and apply it. Do not
ask follow-up questions: pick sensible defaults and go.
How you work, in this order:
1. List the existing agents with k8s_get_resources (resource_type
agents.kagent.dev, namespace kagent). Pick a name that is not taken;
never overwrite an existing agent.
2. Write the manifest in exactly the shape below and apply it with
k8s_apply_manifest. The user confirms the apply in the UI; that is
expected.
3. Read it back with k8s_get_resource_yaml (resource_type
agents.kagent.dev, namespace kagent) and look at the Accepted and
Ready conditions in its status. Ready can take a few seconds.
The manifest must be exactly this shape, and nothing else:
apiVersion: kagent.dev/v1alpha2
kind: Agent
metadata:
name: <short-kebab-case-name>
namespace: kagent
labels:
app.kubernetes.io/managed-by: agent-builder
spec:
description: |-
<one sentence>
type: Declarative
declarative:
modelConfig: default-model-config
stream: false
systemMessage: |
<a clear prompt for the new agent, written for its task>
tools:
- type: McpServer
mcpServer:
apiGroup: kagent.dev
kind: RemoteMCPServer
name: kagent-tool-server
toolNames: [<pick from the read-only list below>]
Read-only tools you may give it: k8s_get_resources, k8s_get_resource_yaml,
k8s_describe_resource, k8s_get_events, k8s_get_pod_logs.
Never another kind, never another namespace, never write or delete tools,
never more than one document.
YAML that does not break: free text always goes in a block scalar, as in
the shape above (description with |-, systemMessage with |). In a plain
scalar, a colon followed by a space makes the YAML invalid, and a space
followed by # silently cuts the value short.
When something goes wrong:
- If the user rejects the apply, do not retry it and do not read the
agent back. Say it was not applied, and stop.
- Never create a second resource with another name (-test, -check, -v2)
to find out what failed. It tells you nothing and leaves clutter.
- Do not name a cause you have not checked. An opaque error is not proof
of a name clash, a permission problem or a network issue.
- Report the error exactly as the tool returned it, and stop.
When it is done, answer in three short lines: the agent's name, what it
does, and which tools it can use. Then one more line with its real state
from step 3 (Accepted, Ready). Never claim it is ready if it is not.
tools:
- type: McpServer
mcpServer:
apiGroup: kagent.dev
kind: RemoteMCPServer
name: kagent-tool-server
toolNames:
- k8s_apply_manifest
- k8s_get_resources
- k8s_get_resource_yaml
requireApproval:
- k8s_apply_manifest
a2aConfig:
skills:
- id: build-agent
name: Build an agent
description: Turns a plain-language description into a deployed kagent agent.
tags: [builder, agents]
examples:
- "Create an agent for the support team that answers questions about the state of the services in the cluster"
应用它并等待 kagent 报告其状态为 Ready:
kubectl --context=kind-kagent-builder apply -f agent-builder.yaml
kubectl --context=kind-kagent-builder -n kagent wait --for=condition=Ready \
agent/agent-builder --timeout=300s
末尾的 tools 块是构建器获得其限制的地方。toolNames 是一个允许列表:在服务器上的 22 个工具中,模型只能看到三个。requireApproval 是 kagent 的人在回路开关。当智能体调用该列表中的工具时,运行会暂停,用户必须批准或拒绝该调用。
系统提示词完成其余工作。它告诉模型首先列出已有的智能体,这样它就不会覆盖任何一个,然后给出它可以生成的精确清单格式:一个 Agent,位于 kagent 命名空间中,带有一个标记其由构建器构建的标签,并且只使用只读工具。它还包含了我通过艰难方式学到的一些经验。自由文本放在 YAML 块标量中,因为普通字符串中的冒号加空格会破坏清单。当某些操作失败时,模型必须报告错误并停止,而不是创建 my-agent-v2 来看看会发生什么。
请记住,提示词是一个请求,而不是保证。保证来自允许列表、审批和 RBAC。
kubectl --context=kind-kagent-builder -n kagent port-forward \
--address 127.0.0.1 svc/kagent-ui 8080:8080
转到 http://localhost:8080,选择 agent-builder 并提出请求:
Create an agent for the support team that answers questions
about the state of the services in the cluster
大约半分钟后,运行停止。UI 显示模型想要应用的清单以及两个按钮。

图 2:UI 显示在应用前需要用户批准的清单。
在点击之前先阅读它。这是人类能够确切看到即将进入集群的内容的唯一时刻。批准后,构建器会应用它、读回它并报告真实状态:

图 3:UI 显示应用清单后的结果和真实状态。
新智能体会出现在智能体列表中,你可以立即与它聊天。问它 kagent 命名空间中存在哪些服务。它会使用只读工具回答,因为这是构建器被允许授予它的唯一工具。
每个 kagent 智能体都是控制器上的一个 A2A 端点,因此你可以编写脚本实现整个流程。转发控制器端口并发送请求:
kubectl --context=kind-kagent-builder -n kagent port-forward \
--address 127.0.0.1 svc/kagent-controller 8083:8083 &
sleep 3 # give the port-forward a moment to listen
curl -s http://127.0.0.1:8083/api/a2a/kagent/agent-builder/ \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":"1","method":"message/send","params":{"message":{"role":"user","messageId":"1","parts":[{"kind":"text","text":"Create an agent that lists the pods that are not running in the kagent namespace"}]}}}' \
> build.json
jq -r '.result.status.state' build.json # input-required
input-required 表示 apply 正在等待你的响应。待处理的 manifest 位于状态消息的 data 部分中。要回复,请在同一 task 和 context 上发送一条带有决策的消息:
TASK=$(jq -r .result.id build.json); CTX=$(jq -r .result.contextId build.json)
curl -s http://127.0.0.1:8083/api/a2a/kagent/agent-builder/ \
-H 'Content-Type: application/json' \
-d "{\"jsonrpc\":\"2.0\",\"id\":\"2\",\"method\":\"message/send\",\"params\":{\"message\":{\"role\":\"user\",\"messageId\":\"2\",\"taskId\":\"$TASK\",\"contextId\":\"$CTX\",\"parts\":[{\"kind\":\"data\",\"data\":{\"decision_type\":\"approve\"}}]}}}" \
| jq -r '.result.status.state' # completed
使用 “reject” 代替 “approve”,则不会创建任何内容。无论哪种方式,集群都会告诉你实际发生了什么:
kubectl --context=kind-kagent-builder -n kagent get agents

图 4:三道不依赖于提示词的护栏。
人们很容易认为提示词就足够了。事实并非如此,而且不是因为模型有恶意。模型会写错 YAML,以各种创造性的方式重试,并误解请求。
你可以自己验证最后一项:
SA=system:serviceaccount:kagent:kagent-tools
kubectl --context=kind-kagent-builder auth can-i create
agents.kagent.dev --as=$SA -n kagent # yes
kubectl --context=kind-kagent-builder auth can-i create
deployments --as=$SA -n kagent # no
kubectl --context=kind-kagent-builder auth can-i get secrets
--as=$SA -n kagent # no
所以构建这个智能体只用了一个 YAML 文件和一两次 Helm 安装。这是简单的部分,而这正是我认为它不再是有趣部分的原因。
再看一下图 4 底部的方框。在这台笔记本电脑上,没有人知道是谁请求了这个智能体,因为所有人都是同一个用户。没有人知道它的模型调用花了多少钱,或者属于哪个团队。也没有人决定这个智能体是否应该存在,除了碰巧在聊天中的某个人点了一下。现在想象一家公司,拥有这样的构建器、几个框架、一个托管智能体平台以及开发者机器上的几个助手。每一个都以不同的方式回答这些问题,如果它们能回答的话。
这就是为什么需要一个治理层,它位于智能体的前面,无论它们来自哪里,而不是位于每个智能体内部。在开源世界中,一个选择是 agentgateway,这是一个 Linux 基金会项目。它代理 LLM、MCP 和 A2A 流量,支持身份验证(JWT、API 密钥)、使用 CEL 的细粒度授权,以及为每次调用提供 OpenTelemetry 指标、日志和追踪。将它接入本实验的前面是一个很好的下一步练习,可能也是我的下一篇文章。
所有内容都存在于 kind 集群中,因此只需一条命令即可将其移除:
kind delete cluster --name kagent-builder
kagent 文档涵盖了更多工具服务器、其他模型提供商以及用代码编写的智能体,项目位于 github.com/kagent-dev/kagent。如果你在此基础上构建了某些东西,或者你找到了一个能很好处理构建器的更小的本地模型,我非常希望听到相关消息。
转载声明:本文为转载整理,原文版权归原作者及原站点所有。
原文来源:CNCF Blog
原创地址:https://www.cncf.io/blog/2026/10/12/from-zero-to-agent-builder-running-kagent-on-your-laptop/
原文作者:Antonio Berben(Solo.io) and Felipe Vicens(Telefóni;如有侵权,请联系管理员删除。