For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Add a skill to an agent
Package instructions and a script as a skill, publish it as an OCI image, and attach it to an AgentTemplate.
A skillSkillA packaged piece of know-how that an agent can pick up: a directory holding a SKILL.md file of instructions, plus any scripts or reference files those instructions use. An AgentTemplate attaches skills by naming where each one comes from.Learn more packages know-how that an agent picks up at run time: a SKILL.md file of instructions, together with the scripts and reference files those instructions depend on. This example builds a skill that turns raw commit subjects into release notes, publishes it as an OCI image, and attaches it to an AgentTemplateAgentTemplateA Kubernetes custom resource defining what an agent does: its model, system prompt, tools, skills, and plugins. It runs only once a Harness accepts it.Learn more.
For the fields that attach a skill and the rules that govern their names, see Skills. For the format of a multi-skill package, see Plugins.
About skills at run time
kagent does not fetch a skill when you apply an AgentTemplate. The compiled revision records where each skill comes from, and the ActorActorThe sandboxed unit of compute, provided by Agent Substrate, that runs an AgentInstance's conversation loop. Every AgentInstance is backed by one.Learn more fetches it when the agent starts. Every artifact is unpacked under /plugins, whether it holds one skill or a package of them, and each enabled skill is then copied to /skills/<skill-name>. The agent reads skills only from /skills.
Because kagent fetches a skill this late, review the following considerations.
- A wrong source still compiles. kagent validates skill names before it accepts an AgentTemplate, but it never checks that the artifact exists or that it holds a
SKILL.mdfile. A bad digest produces a revision that reportsReady, and the agent then fails to start. - Scripts run in the runtime image. A skill’s scripts get whatever the Harness image provides. The kagent runtime image is Alpine Linux with
bash,git, and the standard Alpine utilities, and it does not include Python.
Skill tools
On a kagent Harness, attaching a skill adds seven tools to the agent, whether or not the skill ships a script. The first three read skills, and the rest let the agent act on their files. The claude and codex Harness runtimes take the same skills and expose them through their own coding agent’s tools instead.
| Tool | What it does |
|---|---|
list_skills | Lists the attached skills with their names and descriptions. |
load_skill | Reads a skill’s full SKILL.md instructions. |
load_skill_resource | Reads one file inside a skill directory, such as a reference document. |
read_file | Reads a file from the skills directory or the session directory. |
write_file | Writes a file to the session directory. |
edit_file | Replaces an exact string in a file that the agent has already read. |
bash | Runs a shell command in the session directory /tmp/kagent/<session-id>/. Commands time out after 30 seconds. |
Attaching a skill also changes what the agent is told. The runtime appends the name and description of every attached skill to the model request, along with an instruction to call load_skill before acting on one, so a skill reaches the model even before any tool is called.
Important
The bash tool gives the agent shell access inside its own Actor sandbox, and the sandbox is the boundary that contains it. Review a skill before you attach it, and treat the egress that the Actor is granted as the reach that the skill has. Writes are confined to the session directory, so a skill cannot modify /skills or another skill’s files.
Before you begin
Create your first agent, so that you have a Harness and an AgentTemplate to attach a skill to.
Install Docker to build the skill image.
Choose a container registry that your cluster can reach over HTTPS, and set it as an environment variable. Replace the example value with your own repository.
export SKILL_REPO=ghcr.io/<your-org>/release-notesWarning
kagent pulls a skill image over HTTPS with certificate verification, and v1alpha3 has no option to disable it. kagent 0.x accepted an
insecureSkipVerifyflag for a local registry, but that field does not exist in 1.x. A plain HTTP registry, and alocalhostregistry that only the host can reach, both fail at agent startup.
Build the skill
A skill is a directory whose root holds a SKILL.md file. Everything else in the directory is available to the agent through the skill tools.
Create the skill directory.
mkdir -p release-notes/scripts cd release-notesWrite
SKILL.md. The YAML front matter must carry anameand adescription, and the body holds the instructions the agent follows.cat > SKILL.md <<'EOF' --- name: release-notes description: Group a list of conventional commit subjects into release notes with Added, Fixed, and Changed sections. Use this skill whenever the user supplies raw commit subjects and wants them turned into release notes. --- # Release notes Turn raw commit subjects into release notes grouped by change type. ## Instructions 1. Ask the user for the commit subjects if they have not supplied them. One subject per line. 2. Write the subjects to `commits.txt` in your working directory with the `write_file` tool. 3. Run `bash /skills/release-notes/scripts/group.sh commits.txt` with the `bash` tool. 4. Return the script's output unchanged. Do not re-order or re-word the entries. ## Notes - The script reads conventional commit prefixes: `feat:` becomes Added, `fix:` becomes Fixed, and everything else becomes Changed. - A section with no entries is omitted. EOFThe
descriptiondecides whether the skill is ever used. The agent sees every attached skill’s name and description, and chooses among them the same way it chooses any other tool, so state plainly when the skill applies. The instructions in the body are only read after the agent callsload_skill.Add the script that the instructions call. The script runs in the agent’s runtime image, so it uses
bashrather than Python.cat > scripts/group.sh <<'EOF' #!/usr/bin/env bash # Group conventional commit subjects into release note sections. set -euo pipefail input="${1:?usage: group.sh <file>}" added="^feat(\([^)]*\))?!?:" fixed="^fix(\([^)]*\))?!?:" section() { local heading="$1" body="$2" [ -n "$body" ] || return 0 printf '### %s\n%s\n\n' "$heading" "$body" } strip() { sed -E 's/^[a-z]+(\([^)]*\))?!?: *//; s/^/- /' } section Added "$(grep -E "$added" "$input" | strip || true)" section Fixed "$(grep -E "$fixed" "$input" | strip || true)" section Changed "$(grep -Ev "$added|$fixed" "$input" | strip || true)" EOF chmod +x scripts/group.shConfirm that the script works before you publish it. The
bashtool returns a failed command’s error to the model rather than to you, so a broken script produces an unreliable answer rather than a failed resource.printf 'feat: add checkpoint API\nfix: correct revision digest\ndocs: update install guide\n' > /tmp/commits.txt bash scripts/group.sh /tmp/commits.txtExample output:
### Added - add checkpoint API ### Fixed - correct revision digest ### Changed - update install guide
Publish the skill as an OCI image
kagent pulls an oci source as a container image and unpacks its flattened filesystem, so the image holds the skill directory and nothing else. Build it from scratch, which produces an image whose root is the skill root.
Create the Dockerfile.
cat > Dockerfile <<'EOF' FROM scratch COPY . / EOFBuild and push the image. Build for the architecture that your worker nodes run, because kagent pulls the
linux/amd64orlinux/arm64manifest that matches the node.docker buildx build --push --platform linux/amd64 -t "$SKILL_REPO:1.0.0" .Read the image digest and save the pinned reference. An
ocisource must be pinned to a digest, and a tag alone is rejected.export SKILL_OCI="$SKILL_REPO@$(docker buildx imagetools inspect --format '{{.Manifest.Digest}}' "$SKILL_REPO:1.0.0")" echo "$SKILL_OCI"Example output:
ghcr.io/example-org/release-notes@sha256:3091b917d23de93c40e38a574aea1e5615989ca4d7b38f79431c87e04adfa58a
Attach the skill to an AgentTemplate
Add a
skillsentry to the AgentTemplate that your Harness admits. Keep the labels and the model configuration that your existing template uses, and change only the name and the skill.kubectl apply -f - <<EOF apiVersion: kagent.dev/v1alpha3 kind: AgentTemplate metadata: name: release-writer namespace: kagent labels: kagent.dev/harness: my-first-harness spec: modelConfig: name: default-model-config description: Writes release notes from commit subjects. systemPrompt: You help maintainers turn commit history into release notes. skills: - name: release-notes source: oci: ${SKILL_OCI} EOFTwo fields carry the skill, and each is checked at a different time.
Field Description skills[].nameThe directory the skill is mounted under, and the name the agent sees. It must match no other skill on the template. skills[].source.ociThe digest-pinned image reference, in the form <repository>@sha256:<digest>.Confirm that the template compiled. The revision is ready when
desiredRevisionandlatestSuccessfulRevisionhold the same value.kubectl get agenttemplate release-writer -n kagent \ -o jsonpath='{range .status.harnesses[*]}{.harness}{"\t"}{.desiredRevision}{"\t"}{.latestSuccessfulRevision}{"\n"}{end}'Note
A ready revision means that kagent accepted the reference, not that the image exists. kagent fetches the skill when the agent starts, so a wrong digest surfaces in the next step rather than this one.
Create an AgentInstance. An AgentInstance pins the revision that it was created on, so an instance that already exists does not pick up the skill.
kagent create agent-instance --harness my-first-harness --agent-template release-writerSave the AgentInstance’s ID to an environment variable.
export INSTANCE_ID=$(kagent get agent-instance -o json \ | jq -r '[.agentInstances[] | select(.agentTemplate.name == "release-writer")] | sort_by(.createdAt) | last | .id') echo $INSTANCE_ID
Ask the agent to use the skill
Send the agent a request that matches the skill’s description.
kagent invoke --agent-instance $INSTANCE_ID \ --task "Turn these commit subjects into release notes. feat: add checkpoint API. fix: correct revision digest. docs: update install guide."Read the reply. The agent calls
load_skillto read the instructions,write_fileto stage the commit subjects, andbashto run the script, then returns the script’s output.Example output:
### Added - add checkpoint API ### Fixed - correct revision digest ### Changed - update install guideAsk the agent what skills it holds, to confirm the attachment from the agent’s own side.
kagent invoke --agent-instance $INSTANCE_ID --task "What skills do you have?"
Publish a new version of the skill
A source is immutable, so changing a skill is a two-step change: publish new content, then point the AgentTemplate at it.
Edit the skill.
Build and push the skill under a new tag and read the new digest.
docker buildx build --push --platform linux/amd64 -t "$SKILL_REPO:1.1.0" . export SKILL_OCI="$SKILL_REPO@$(docker buildx imagetools inspect --format '{{.Manifest.Digest}}' "$SKILL_REPO:1.1.0")"Update
skills[].source.ocion the AgentTemplate with the new digest, which compiles a new revision.Create a new AgentInstance. Agents that are already running keep the skill content they started with, because their revision is pinned.
Bundle the skill in a plugin package
A standalone source carries one skill. A plugin packagePlugin packageA bundle that an AgentTemplate attaches with spec.plugins, carrying skills and optionally declaring Model Context Protocol servers. Packages follow the Agent Plugins format, which kagent consumes rather than defines.Learn more carries several skills, and an AgentTemplate attaches the package once and names the skills it wants. Use a package when you ship a set of skills together, or when you want the same artifact to contribute MCP servers as well.
Restructure the directory so that each skill sits under
skills/, and add the manifest that makes it a package.cd .. mkdir -p release-tools/skills mv release-notes release-tools/skills/release-notes cd release-tools cat > plugin.json <<'EOF' { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "release-tools" } EOFNote
kagent compares the
$schemavalue literally and rejects anything else, so copy it exactly. Of the remaining manifest fields, kagent reads onlyname.Build and push the package, and read its digest. A package image is built the same way as a single skill, from
scratch, so that the package root is the image root.export PLUGIN_REPO=ghcr.io/<your-org>/release-tools mv skills/release-notes/Dockerfile . docker buildx build --push --platform linux/amd64 -t "$PLUGIN_REPO:1.0.0" . export PLUGIN_OCI="$PLUGIN_REPO@$(docker buildx imagetools inspect --format '{{.Manifest.Digest}}' "$PLUGIN_REPO:1.0.0")"Attach the package with
pluginsinstead ofskills, and list the skills to enable.kubectl apply -f - <<EOF apiVersion: kagent.dev/v1alpha3 kind: AgentTemplate metadata: name: release-writer namespace: kagent labels: kagent.dev/harness: my-first-harness spec: modelConfig: name: default-model-config description: Writes release notes from commit subjects. systemPrompt: You help maintainers turn commit history into release notes. plugins: - source: oci: ${PLUGIN_OCI} skills: - release-notes EOFImportant
A package enables only the skills that you list. If you omit
plugins[].skills, or leave it empty, the agent gets none of them, and kagent accepts that rather than reporting an error. For why an explicit list is the safer default, see Skills.Create an AgentInstance on the new revision, and confirm that the agent still has the skill. The instance from the previous section is pinned to the revision that carried the standalone skill.
kagent create agent-instance --harness my-first-harness --agent-template release-writer export PLUGIN_INSTANCE_ID=$(kagent get agent-instance -o json \ | jq -r '[.agentInstances[] | select(.agentTemplate.name == "release-writer")] | sort_by(.createdAt) | last | .id') kagent invoke --agent-instance $PLUGIN_INSTANCE_ID --task "What skills do you have?"The agent reports
release-notesexactly as before. A skill behaves the same whether it arrives on its own or inside a package, because kagent copies both into/skillsbefore the agent starts.
Troubleshoot a skill that does not load
A skill that kagent cannot fetch stops the agent from starting at all, rather than producing an agent without that skill. The runtime logs the failure and exits, and the AgentTemplate never becomes ready.
Check the AgentTemplate’s
Readycondition. A skill that cannot be fetched leaves it waiting, because the Actor that builds the template’s golden snapshot is the Actor that fetches the skill.kubectl get agenttemplate <template-name> -n kagent \ -o jsonpath='{range .status.harnesses[0].conditions[?(@.type=="Ready")]}{.status} {.reason} {.message}{end}'Example output:
False ActorTemplatePending waiting for the ActorTemplate golden snapshotNote
This condition does not name the skill, and reports the same reason for any Actor that has not yet produced a snapshot. An agent that is merely still starting looks identical to one whose skill cannot be fetched.
Find the WorkerPool that the Harness runs on, and read its Workers’ logs. The Actor writes the failure there rather than to the AgentTemplate.
export WORKER_POOL=$(kubectl get harness my-first-harness -n kagent \ -o jsonpath='{.spec.substrate.workerPoolRef.name}') kubectl logs -n kagent -l ate.dev/worker-pool=$WORKER_POOL --tail=200 \ | grep -i "materialize"Example output:
{"error":"materialize agent plugins: materialize skill \"release-notes\": pull ghcr.io/example-org/release-notes@sha256:3091b91...: Get \"https://ghcr.io/v2/\": EOF","labels":{"ate.atespace":"ate-golden","ate.template.name":"release-writer-my-first-harness-23c20dcb296d"},"level":"ERROR","msg":"failed to materialize Agent Plugins"}A pool runs the Workers for every agent on it, so filter by the
ate.template.namelabel to find one agent. Its value is the AgentTemplate name, the Harness name, and the revision’s short form, joined by hyphens.Match the message to its cause.
Message Cause pull <image>: ... 401 UnauthorizedThe registry needs credentials that the cluster does not have. pull <image>: ... x509or a TLS errorThe registry does not serve HTTPS with a certificate the runtime trusts. SKILL.md is requiredThe artifact was fetched, but no SKILL.mdfile sits at the root thatsource.pathselects.symlink "..." escapes artifact rootA symlink in the artifact points outside it. artifact contains more than 10000 filesystem entries, orartifact exceeds 104857600 bytesThe artifact is over one of the package limits.
Tip
Build the skill image with --platform set to the architecture of your worker nodes. kagent asks the registry for the linux/amd64 or linux/arm64 manifest that matches the node it runs on, so an image published for one architecture alone fails on the other.
Clean up
Delete the AgentInstances that you created. Deleting the AgentTemplate does not remove them. Skip the second command if you did not complete the plugin package section.
kagent delete agent-instance $INSTANCE_ID kagent delete agent-instance $PLUGIN_INSTANCE_IDDelete the AgentTemplate.
kubectl delete agenttemplate release-writer -n kagentRemove the skill directory from your machine. The directory is
release-notes, orrelease-toolsif you completed the plugin package section.cd .. rm -rf release-notes release-toolsDelete the images that you pushed,
$SKILL_REPOand$PLUGIN_REPO, using your registry’s own tooling.