Against 46ff6ee1db2cae1b52b7730ae75ba781e0bb6605.
st2 doctor has no retirement filter, so a deliberately-retired agent is reported as a fleet health problem. Two code paths disagree about what a retired agent means:
reconcile treats retired as an explicit teardown — src/reconcile.rs:119 returns the live task ids to kill, and tests/reconcile.rs:122 (retired_with_live_sessions_is_torn_down) asserts that as intended behaviour. materialize_catalog skips retired specs outright (src/materialize.rs:704, "materialize every active agent"), and select_target excludes them from shepherd root selection (src/shepherd.rs:189).
doctor_cmd (src/main.rs:887) iterates found.specs and filters on resolved_host only (src/main.rs:933). It then asserts every task is in the live-session set (src/main.rs:943) and that presence has not rotted (src/main.rs:961).
So the runner kills a retired agent's tasks by design, and then doctor reports those same tasks as session dead/missing. retired appears in main.rs only at 246 (a doc comment), 1041/1047 (agents rendering) and 1785/1787 (ls rendering) — nowhere in doctor's filtering.
Reproduction
Read-only; no st2 up, nothing launched.
mkdir -p /tmp/scratch/only/h/principal /tmp/scratch/empty
cat > /tmp/scratch/only/h/principal/agent.kdl <<'EOF'
agent "principal" {
identity "principal"
host "h"
type "service"
retired #true
pty "agent" { command "exec true" }
}
EOF
st2 doctor /tmp/scratch/empty --host h
st2 doctor /tmp/scratch/only --host h
st2 doctor — catalog /tmp/scratch/empty, host 'h'
✓ `pty` on PATH
✗ supervisor (st2 up) running — no live host-lock — run `st2 up`
Error: 1 problem(s) found
st2 doctor — catalog /tmp/scratch/only, host 'h'
✓ `pty` on PATH
✗ supervisor (st2 up) running — no live host-lock — run `st2 up`
✗ h.principal task 'agent' alive — session dead/missing
✗ h.principal presence missing — no status file — is its ding refreshing?
Error: 3 problem(s) found
The supervisor line is in both runs because this was run read-only without st2 up; the delta is what matters — the two extra problems are attributable solely to the retired agent. That agent passes st2 validate with 0 errors, so nothing else flags it as malformed.
Of the two, the task-alive check is the structural one: reconcile guarantees those sessions are gone, so it is red for as long as the agent stays retired. The presence check is time-dependent (read_state only derives unknown after staleness), so a freshly-retired agent can still be green there and go red later.
Why it matters for an adopter
doctor is the natural health gate — the thing an operator runs before/after a change, or that a soak test asserts on. A catalog containing any retired agent is permanently red and exits non-zero, so "doctor is green" stops being a usable signal. The practical outcome is that people stop reading doctor at all, which costs you the checks that are genuinely valuable.
Possible directions (not a prescription)
- Treat a retired agent's stopped tasks as the expected state — skip it in the loop the same way
materialize/shepherd do, or invert the check to "retired ⇒ no live sessions".
- Or keep it visible but out of the verdict: report retired agents in a distinct section that does not count toward
problems or affect exit status.
Happy to send a PR if you'd like a particular shape. You may well have this in flight already — the rev I pinned is Gate hook renders and expose retired agents, so you are clearly in this area right now — please close this if it duplicates unpushed work.
Against
46ff6ee1db2cae1b52b7730ae75ba781e0bb6605.st2 doctorhas no retirement filter, so a deliberately-retired agent is reported as a fleet health problem. Two code paths disagree about what a retired agent means:reconciletreatsretiredas an explicit teardown —src/reconcile.rs:119returns the live task ids to kill, andtests/reconcile.rs:122(retired_with_live_sessions_is_torn_down) asserts that as intended behaviour.materialize_catalogskips retired specs outright (src/materialize.rs:704, "materialize every active agent"), andselect_targetexcludes them from shepherd root selection (src/shepherd.rs:189).doctor_cmd(src/main.rs:887) iteratesfound.specsand filters onresolved_hostonly (src/main.rs:933). It then asserts every task is in the live-session set (src/main.rs:943) and that presence has not rotted (src/main.rs:961).So the runner kills a retired agent's tasks by design, and then doctor reports those same tasks as
session dead/missing.retiredappears inmain.rsonly at 246 (a doc comment), 1041/1047 (agentsrendering) and 1785/1787 (lsrendering) — nowhere in doctor's filtering.Reproduction
Read-only; no
st2 up, nothing launched.The
supervisorline is in both runs because this was run read-only withoutst2 up; the delta is what matters — the two extra problems are attributable solely to the retired agent. That agent passesst2 validatewith 0 errors, so nothing else flags it as malformed.Of the two, the task-alive check is the structural one: reconcile guarantees those sessions are gone, so it is red for as long as the agent stays retired. The presence check is time-dependent (
read_stateonly derivesunknownafter staleness), so a freshly-retired agent can still be green there and go red later.Why it matters for an adopter
doctoris the natural health gate — the thing an operator runs before/after a change, or that a soak test asserts on. A catalog containing any retired agent is permanently red and exits non-zero, so "doctor is green" stops being a usable signal. The practical outcome is that people stop reading doctor at all, which costs you the checks that are genuinely valuable.Possible directions (not a prescription)
materialize/shepherddo, or invert the check to "retired ⇒ no live sessions".problemsor affect exit status.Happy to send a PR if you'd like a particular shape. You may well have this in flight already — the rev I pinned is
Gate hook renders and expose retired agents, so you are clearly in this area right now — please close this if it duplicates unpushed work.