How the agent learns: from files to the model¶
The model behind the agent knows Kubernetes in general, but nothing about your platform, and it remembers nothing between requests. Everything it knows about your clusters reaches it as text, in every request. This page follows that text from the files in Git to the model, step by step, with example content and sizes measured on a test cluster.
How to write good knowledge and skills is in knowledge-and-skills.md; this page explains how it works.
The picture¶
Git (reviewed) agent's memory (DATA_DIR)
───────────── ─────────────────────────
charts/k8s-servant/knowledge/skills/*/SKILL.md notes.md ◀── save_note (the model)
knowledge-values.yaml (your platform) │
my-cluster-values.yaml (this cluster) │
│ │
│ 1. load once per process │ 3. read for every run
▼ ▼
Knowledge(environment, cluster_notes, skills) ──▶ 2. system prompt ──▶ model
│ (every model call)
│ │
└── skill bodies ◀── 4. load_skill(name)
(on demand, as a tool)
Four steps: load the knowledge, build the system prompt, add the learned notes, and load skills on demand during the run.
Step 1: loading the knowledge¶
knowledge.load_knowledge() runs once when the process starts (a CLI command, or
k8s-servant serve).
1a. The knowledge folder (KNOWLEDGE_DIR, default: the chart's
charts/k8s-servant/knowledge/). Two layouts are understood and can be mixed:
<dir>/environment.md environment description
<dir>/cluster.md cluster notes
<dir>/skills/<name>/SKILL.md one folder per skill (this repository)
<dir>/skill__<name>.md flat layout (the chart's ConfigMap mounted as files,
because ConfigMap keys cannot contain "/")
In this repository the folder holds only the five built-in, generic skills.
1b. Helm values files, applied in order (--values on the CLI; helm -f or the
Flux HelmRelease's valuesFrom in the cluster). Only the knowledge: section is read:
knowledge:
environment: | # replaces the environment description (if not empty)
# Example platform
...
clusterNotes: | # replaces the cluster notes (if not empty)
- dev also hosts the *-stage namespaces ...
extraSkills: # adds skills, or replaces a skill with the same name
service-mesh-troubleshooting: |
---
description: Use when ...
---
...
A typical setup passes the same two files to the CLI and to the cluster, in the same order:
knowledge-values.yaml the platform (e.g. 5,900 characters, 10 sections;
start from examples/knowledge-values.yaml)
my-cluster-values.yaml notes for this cluster (e.g. 270 characters)
So the laptop and the cluster see identical knowledge.
1c. Parsing skills. Each SKILL.md is split into YAML frontmatter (name,
description) and the Markdown body:
- the skill's identity is its folder name (or its
extraSkillskey); a differentnamein the frontmatter is logged as a warning; - Windows line endings and a BOM are accepted;
- broken YAML keeps the body and loses the description (warning); a skill without a description stays loadable but the model will hardly ever choose it.
The log shows the result:
k8s-servant tools lists every skill with the file it came from; k8s-servant doctor
reports the same counts.
When changes take effect. Knowledge is read at process start. Locally that is every command. In the cluster a change to the values is a Helm upgrade; the chart restarts the pod when the knowledge changes (a checksum annotation), so the next run uses it.
Step 2: building the system prompt¶
At the start of every run (a question, a health check, the morning report),
agent.build_system_prompt() assembles the system prompt in this order:
| # | Part | Source | Example size |
|---|---|---|---|
| 1 | Rules: read-only; evidence for every statement; copy names exactly; check the skill catalog first; use the environment description and notes; save lasting facts; work on your own, never end with "if you want…"; start health questions with the summary tools; UTC; answer format | BASE_PROMPT in agent.py |
~2,300 chars |
| 2 | Tools available in this session (and a note when MCP is down) | the tool registry | ~300 chars |
| 3 | Environment description | knowledge.environment |
5,900 chars |
| 4 | Cluster notes | knowledge.clusterNotes |
270 chars |
| 5 | Learned notes (step 3) | notes.md |
0–8,000 chars |
| 6 | Skill catalog: one line per skill, name and description only | the skills' frontmatter | ~1,500 chars |
Total in the measured example: about 10,000 characters, roughly 2,500 tokens. With the tool definitions (another ~3,000 tokens), the first model call of a run was measured at about 6,000 prompt tokens. Every following call resends the system prompt, so the provider's prompt cache serves most of it where it has one (Azure AI Foundry, OpenAI and Anthropic do; cached tokens are billed at a discount).
An excerpt (start of the environment description and the catalog):
You are an experienced site reliability engineer (SRE) acting as a read-only
cluster administrator for the Kubernetes cluster "my-cluster". ...
Tools available in this session: cluster_problems and warning_events (compact
summaries; start here for health questions), load_skill, kubectl (read-only),
Kubernetes MCP tools (preferred for reading the cluster), save_note, Prometheus/...
## Environment description
# Example platform
## Clusters
- Two clusters: dev (also hosts staging namespaces, *-stage) and prod. ...
## What is normal here (do not report it as an incident)
...
## Skill catalog
- cluster-health-check: Use for a scheduled or on-demand overall health sweep of the
cluster - nodes, unhealthy pods and workloads, disruption budgets, quotas, ...
- flux-gitops-status: Use when checking whether Flux GitOps is healthy - ...
- node-troubleshooting: Use when a node is NotReady, cordoned, under memory, disk or ...
- prometheus-queries: Use when metrics can confirm or time a problem - ...
- workload-troubleshooting: Use when a specific pod or workload is unhealthy - Pending, ...
To see the full prompt for your own values:
.venv/bin/python -c "
from k8s_servant.config import DEFAULT_KNOWLEDGE_DIR
from k8s_servant.knowledge import load_knowledge
from k8s_servant.agent import build_system_prompt
k = load_knowledge(DEFAULT_KNOWLEDGE_DIR, ['examples/knowledge-values.yaml', 'examples/values-azure.yaml'])
print(build_system_prompt('my-cluster', k, {'mcp', 'kubectl', 'cluster', 'knowledge', 'memory'}))"
Why the environment description is always in full and skills are not: the description says what is normal here ("the test cluster is stopped on weekday evenings", "the descheduler evicts crash-looping pods after 5 minutes"). The model needs that before it judges anything, in every run. Skills are procedures, needed only for some tasks.
Step 3: learned notes¶
Learned notes are the agent's own memory of lasting facts, kept in DATA_DIR/notes.md:
- Written by the model with the
save_notetool when it learns something that will matter later. One sentence, at most 500 characters, no secrets. People can edit the file too. - Read at the start of every run (unlike the environment description, which is read once per process), so a note saved in one run is used in the next.
- Size limit. Only the newest notes up to
NOTES_MAX_CHARS(default 8,000) go into the prompt, cut at a line boundary, with "(older notes omitted)" in front. - Visible to people. Every new note is logged at WARNING and listed in the next morning report, because a note changes what every future run treats as normal, and it could have been planted by text in a log line (prompt injection).
- Promote lasting facts to Git. A note that stays true belongs in the environment
description or cluster notes, where it is reviewed. Then delete it from
notes.md.
Details: memory.md.
Step 4: loading skills on demand¶
The model sees only the catalog. When a task matches a skill, it calls the
load_skill tool, and the agent returns the skill's full Markdown body as the tool
result. From then on the procedure is part of the conversation.
What this looked like in a health check on a test cluster (from the log):
tool load_skill({"name":"cluster-health-check"}) -> 5112 chars (the task said: follow it)
tool cluster_problems({}) -> 787 chars (step 1 of the skill)
tool warning_events({"since_minutes": 60}) -> 1251 chars
tool load_skill({"name":"workload-troubleshooting"}) -> 3525 chars (azure-cns looked unhealthy)
tool pods_get(... azure-cns-x7k2p) ...
tool load_skill({"name":"flux-gitops-status"}) -> 2170 chars (step 7: Flux exists)
tool resources_list(... GitRepository / Kustomization / HelmRelease)
- The description decides whether a skill is used. The model chooses by the catalog line alone, so it should start with "Use when ..." and name the symptoms people ask about.
- An unknown name returns
ERROR: no skill named 'x'. Available skills: ..., and the model corrects itself. - Why not put every skill in the prompt? It would grow with every skill and be resent on every call. The catalog keeps the prompt small however many skills there are; a skill costs tokens only in runs that need it.
Which knowledge wins¶
| Situation | Result |
|---|---|
A values file has environment / clusterNotes |
it replaces the folder's (or an earlier file's) text; an empty value changes nothing |
extraSkills has a new name |
the skill is added |
extraSkills has a built-in skill's name |
it replaces the built-in skill |
| A learned note contradicts the environment description | both are in the prompt; the model sees the conflict. Fix the note or the description |
How the pieces are meant to be used¶
| Kind | Who writes it | Reviewed | Scope | In the prompt |
|---|---|---|---|---|
Rules (BASE_PROMPT) |
developers | code review | every cluster, every site | always |
| Built-in skills | developers | code review, must stay generic (a test rejects site names) | every site | catalog always, body on demand |
| Environment description | platform team | Git (values) | all clusters of the platform | always |
| Cluster notes | platform team | Git (values) | one cluster | always |
| Extra skills | platform team | Git (values) | the platform | catalog always, body on demand |
| Learned notes | the model (and people) | no: shown in the morning report | one cluster | always (newest 8,000 chars) |
The improvement loop: the agent reports something that is normal here → add one line to the environment description or cluster notes (or let it save a note, then promote it) → the next run knows. No code change.
What the agent does not learn¶
- It does not fine-tune or train the model; nothing about your cluster leaves the request it was sent with. Whether the provider may keep or train on that request is set by its terms (model-providers.md); Azure AI Foundry does not train on your prompts.
- It does not learn from its findings: the findings history is used for new / still open / resolved, not as knowledge.
- It cannot see what nobody wrote down. If an important fact is only in people's heads, the agent will flag it as a problem until someone adds it.