Restart-rate baseline¶
kubeagent baseline capture records what each workload's restart rate
normally is on this cluster. kubeagent scan --baseline and kubeagent gate
--baseline compare a later run against that captured file and report
workloads that are restarting far more than their own normal — not more than
some fixed number picked in advance, but more than what this specific
workload, on this specific cluster, has actually been doing.
What it honestly measures¶
This measures restarts over the lifetimes of the pods present when the sample was taken. It is not long-term history. A workload whose pods were all recreated an hour before capture shows only what those pods have done since.
There is no database behind it, no ring buffer of yesterday's numbers — a
capture reads Pod objects that exist right now and reduces them to one rate
per workload. If every pod under a Deployment restarted five minutes before
you ran baseline capture, the baseline records exactly that: a few minutes
of near-zero pod-seconds and whatever restarts happened in them, not
"normal" in any deeper sense.
Capacity hints draws the same line for metrics-server: a GET
on /apis/metrics.k8s.io/v1beta1/pods returns one sample and keeps nothing
before or after it, so kubeagent never calls a request "over-requested" from
a single reading. The restart-rate baseline has the same shape of limit for
the same reason — a snapshot is a snapshot — so recapture periodically if you
want the learned rate to track how the cluster actually behaves over time.
Nothing does that for you automatically; see What it is not.
The workflow¶
# 1. Capture. Read-only — see Guarantees below.
kubeagent baseline capture > cluster-baseline.json
# 2. Review it. It names your namespaces and workloads in the clear.
cat cluster-baseline.json
# 3. Commit it, like any other file whose diffs you want to see.
git add cluster-baseline.json && git commit -m "capture restart-rate baseline"
# 4. Compare later runs against it.
kubeagent scan --baseline cluster-baseline.json
kubeagent gate --baseline cluster-baseline.json
Nothing captures automatically and nothing merges captures together — step 1 is something an operator runs and reviews, not a background job. See What it is not.
A capture looks like this:
{
"schemaVersion": "1.0",
"capturedAt": "2026-01-15T08:00:00Z",
"minPodAgeSeconds": 3600,
"workloads": [
{
"kind": "Deployment",
"namespace": "prod",
"name": "api",
"restartsPerHour": 0.4,
"pods": 3,
"observedSeconds": 45000
},
{
"kind": "StatefulSet",
"namespace": "prod",
"name": "cache",
"restartsPerHour": 0.1,
"pods": 2,
"observedSeconds": 72000
}
]
}
The maths¶
One rate per workload:
A pod counts only when it is at least --min-pod-age old (default 1h).
An excluded pod leaves both sides of that fraction — its restarts are not
added to the numerator, and its age is not added to the denominator — because
a pod that has existed for thirty seconds with two restarts implies 240
restarts/hour, and averaging that into an otherwise-quiet workload would
swamp every older pod's contribution with noise from one still starting up.
A workload with no counted pods gets no entry in the document. Zero
restarts and "not measured" are different facts, and collapsing them into a
rate of 0.0 would let a workload that was simply too young to sample at
capture time look, later, like it deviated from a normal that was never
actually observed.
Two thresholds, not one¶
A workload deviates only when both of these hold:
current >= baseline × factor (default factor: 3.0)
current − baseline >= floor (default floor: 0.5 restarts/hour)
Both conditions exist because each one alone breaks in a different direction:
- The factor test alone would flag
cachethe moment it had a single restart, because any positive rate clears "3× a baseline of zero" — the multiplicative test is trivially satisfied when the baseline is zero. The floor is what actually decides whether a previously silent workload's first restarts count as a deviation. - The floor test alone would flag a workload that already restarts often: one going from 20.0 to 20.5 restarts/hour clears a 0.5/hour floor while barely moving relative to its own history.
Requiring both means a deviation has to be large relative to the workload's own normal and large enough in absolute restarts/hour to be worth paging someone about.
Only increases deviate. A workload restarting less than its baseline is never reported — nobody is paged for a thing improving.
A workload the baseline has never seen, and a workload the baseline has seen
but the cluster no longer has, are both counted and neither is ever flagged:
there is no learned rate to compare a new workload against, and a gone
workload has no current rate to compare. scan's text output reports both
counts in its footer line, alongside how many workloads were actually
compared.
The flags and environment variables¶
| Flag | Env var | Default | Command | Meaning |
|---|---|---|---|---|
--baseline <file> |
— | (unset — comparison off) | scan, gate |
path to a captured baseline document |
--baseline-factor |
KUBEAGENT_BASELINE_FACTOR |
3.0 |
scan, gate |
the multiplicative threshold |
--baseline-floor |
KUBEAGENT_BASELINE_FLOOR |
0.5 |
scan, gate |
the absolute threshold, in restarts/hour |
--min-pod-age |
KUBEAGENT_BASELINE_MIN_POD_AGE |
1h |
baseline capture |
how old a pod must be to count toward the rate |
--baseline-factor and --baseline-floor only do anything when --baseline
is also set; without it, scan and gate run exactly as they did before
this feature existed.
In gate¶
A deviation becomes a Finding at Info — the level reserved for a signal
that is not a match on a concrete, named failure mode, only an inference from
a learned rate. It is reported at every --fail-on setting, but it fails the
gate only at --fail-on info, which is the operator opting in explicitly.
Because --fail-on defaults to critical, adding --baseline to an
existing gate invocation changes no pipeline's pass/fail behavior until
that operator asks for it.
Guarantees¶
kubeagent baseline capture is read-only toward the cluster — it issues
List calls only, the same calls scan already makes, nothing else — and it
makes no model call. Those are two separate promises: read-only describes
what it does to the cluster, no-model-call describes what it does with the
result, and neither implies the other.
It needs no RBAC grant beyond what scan already has — there is no
deploy/rbac-baseline.yaml, because there is nothing for one to grant.
It writes no file. The document goes to stdout, so the operator sees it, and decides where it goes, before it exists anywhere on disk.
What it is not¶
- It has no HTML, TUI, MCP, or dashboard surface. The document is JSON on stdout; there is no rendering of it anywhere else.
- It is not wired into the
watchdaemon.watchdoes not capture, does not compare, and carries no--baselineflag. - It tracks no inventory drift. A workload appearing or disappearing between a capture and a later run is counted (see Two thresholds), never reported as drift in its own right — that is a different question this feature does not answer.
- It has one dimension. Restart rate, and nothing else. No CPU, no memory, no latency, no error rate — those would each be their own learned normal, and none of them exist here.
- Nothing captures automatically. There is no scheduled job, no
in-cluster capture, no flag that captures on a timer. An operator runs
baseline captureand decides when. - There is no multi-baseline merge.
--baselinereads exactly one file. Combining multiple captures, or captures from multiple clusters, is a step an operator would take outside kubeagent, if at all.
The schema¶
The document is the seventh of kubeagent's versioned JSON
documents, published at
../schemas/baseline-v1.json.
prints the same schema straight from the running binary — no cluster, no kubeconfig, nothing else needed.