Your agent operator is staring at a node at 3am. The node says NotReady. Is it dying — kernel panic, kubelet wedged, disk gone — or is a teammate halfway through a planned drain ahead of a kernel upgrade? Today, Kubernetes cannot tell the agent the difference. NotReady is a symptom with no diagnosis attached, and every controller in the cluster reconstructs its own guess from side channels.
Kubernetes v1.37 ("Garhwal," released August 26, 2026 with 67 enhancements) starts fixing that with five well-known Node Lifecycle Conditions, introduced on the Kubernetes blog on September 9 by Ryan Hallisey (NVIDIA). Here is the whole vocabulary up front — this table is the post in miniature:
| Condition | What it reports |
|---|---|
DrainInProgress | The node is actively being drained under the administrator's chosen drain criteria |
Drained | The node has reached the administrator's drain criteria |
MaintenancePlanned | The node is expected to undergo a change in the future |
MaintenanceInProgress | The node is actively undergoing maintenance |
GracefulNodeShutdownInProgress | Graceful Node Shutdown is determined to be in progress on the node |
Status line: the names are reserved as well-known NodeConditionType constants behind the alpha NodeLifecycleConditions feature gate, which is off by default — and in v1.37 the gate is deliberately a no-op. No core component reads these conditions yet, and you do not need to enable the gate to start publishing them. That sounds like a letdown until you see what it unlocks: for the first time there is a Kubernetes-owned place to say "this node is draining" that isn't a taint, an annotation, or a guess.
Today: every controller guesses from side channels
The conditions kubelet already posts — Ready, MemoryPressure, DiskPressure, PIDPressure, NetworkUnavailable — describe resource health, not lifecycle. They answer "is this node usable right now," never "what is happening to this node and why." Lifecycle intent lives everywhere and nowhere: a cordon taint here, a provider-specific annotation there, a runbook in someone's head. That works until two components guess differently, which is exactly what the v1.37 announcement documents:
- A DaemonSet controller replaces a pod that the kubelet intentionally terminated during graceful shutdown, because it sees a missing pod, not a node shutting down gracefully.
- A Job controller waits indefinitely for a terminal pod phase on a node an administrator is actively removing, because nothing says "this node is leaving, stop waiting."
- A storage operator learns about maintenance only after the drain has already started, because the drain was announced in a channel the operator doesn't read.
Each component is behaving correctly given what it can see. The problem is what it can see. A taint can influence scheduling or eviction, but it does not attest that a drain is in progress or that drain criteria were met. A NotReady node does not explain whether the cause is unexpected failure, graceful shutdown, or planned maintenance.
And anything provider-specific — a cloud label here, a Cluster API annotation there — is by definition something a portable agent cannot rely on.
This is the gap that matters for agents-as-operators. A human on call can open three dashboards, read the deploy channel, and conclude "oh, that's the kernel rollout." An agent in a loop — read state, decide, act — needs the state to be readable in one place, in a stable vocabulary, with machine-readable cause attached. Scraping kubelet logs and controller-specific annotations is not a plan; it is five brittle integrations wearing a trench coat.
What v1.37 actually ships (and what it deliberately doesn't)
Precision matters here, because alpha features get over-claimed. Here is exactly what v1.37 delivers, per the announcement and KEP-5683:
Reserved names, not enforced behavior. The release reserves the five names as well-known NodeConditionType constants. The NodeLifecycleConditions gate exists so that future built-in behavior — controllers that consume these conditions — can be opted into when it arrives. In v1.37 it restricts nothing and no core workload controller changes behavior based on the conditions.
You publish without enabling anything. An administrator or an administrator-authorized controller sets and clears the conditions. No feature gate flip is required to start writing them today.
Report status; actuate elsewhere. The recommended pattern is explicit: keep using kubectl cordon, kubectl drain, taints, and workload-specific controls to change scheduling and eviction behavior. Use lifecycle conditions to make the state of that work visible to people, dashboards, alerts, and automation. Conditions are the status channel, not the actuator.
A strict status contract. Like other node conditions, each uses True (active), False (not observed), or Unknown (cannot determine), with a stable machine-readable reason and a human-readable message. A maintenance controller announcing a window looks like this:
# Node .status excerpt
status:
conditions:
- type: MaintenancePlanned
status: "True"
reason: MaintenanceWindow
lastTransitionTime: "2026-12-09T12:00:00Z"
message: "Hardware maintenance is scheduled for this Node"Maintenance itself is deliberately broad — hardware or software rollout, remediation, decommissioning, debugging — and the announcement is careful about the drain question: whether maintenance requires a drain depends on impact. A Kubernetes upgrade usually should follow a drain; a kernel live patch might not need one. The conditions model that distinction instead of collapsing it.
One operational rule comes with the territory: decide which component owns each condition. Two writers with different opinions about Drained will flap the signal and destroy exactly the trust that makes automation possible. Ownership is a policy decision for the cluster administrator, and it should be written down before the first prototype — more on that below.
The agent-operator payoff: one read instead of five scrapes
Here is the decision an agent (or any drain automation) makes today versus with lifecycle conditions published. The "before" column is the scrape list; the "after" column is a single condition read:
| Agent question | Today: infer from | With lifecycle conditions: read |
|---|---|---|
| Is this node mid-drain — wait, don't reschedule onto it? | Taints + terminating-pod watch + whoever ran drain's logs | DrainInProgress=True with a stable reason |
| Is the drain done — safe to reboot/replace? | Pod count hit zero (hopefully for the right reason) | Drained=True against declared criteria |
| Is work planned — avoid migrating critical pods here? | Out-of-band calendar, chat, tribal knowledge | MaintenancePlanned=True with window in message |
| Is the node dying or being serviced? | NotReady + log spelunking | MaintenanceInProgress / GracefulNodeShutdownInProgress disambiguates |
The read itself is one API call — the kind of thing an agent tool wraps trivially:
kubectl get node worker-3 -o jsonpath='{range .status.conditions[?(@.type=="DrainInProgress" || @.type=="Drained" || @.type=="MaintenancePlanned" || @.type=="MaintenanceInProgress" || @.type=="GracefulNodeShutdownInProgress")]}{.type}={.status} reason={.reason}{"\n"}{end}'Why does a stable vocabulary matter so much for model-driven operators specifically? Because for a model caller, the signal name is part of the interface. An agent taught "check DrainInProgress" works on every cluster that publishes the condition, on any infrastructure provider, under any drain tooling — the same way Ready=True works everywhere today.
An agent taught "check for the drain-tools.acme.io/state annotation, unless the cluster uses the other drainer, in which case grep the logs" works until the next tooling change. Well-known constants are what turn node lifecycle from folklore into an API an agent can be given once and trust everywhere. That is the quiet bet of KEP-5683: standardize the health signal first, and the self-healing automation everyone wants for 2026 stops being a per-cluster snowflake.
What to prototype now while it's alpha
Alpha-and-no-op is not a reason to wait — it is the cheapest possible time to build, because publishing is pure status with no core controller second-guessing you. Three prototypes, in increasing order of ambition, fit a self-hosted fleet (Cluster API on owned hardware, where you control the drain tooling end to end):
1. A condition-setting drain wrapper. Wrap your existing drain invocation — kubectl drain, Cluster API machine rollout, whatever actuates today — so it sets DrainInProgress=True (reason: AdminDrain, message: who/what/why) before the first eviction and flips to Drained=True when your declared criteria are met (zero non-DaemonSet pods, or your own bar). On abort or completion-plus-uncordon, set both back to False. The drain behavior doesn't change at all; what changes is that every consumer — human or agent — can now see it. Explicit NOT-to-do: don't gate the drain itself on the conditions. They report; kubectl drain still decides.
2. Fleet-health dashboards and alerts keyed on the new types. Add five panels (or one lifecycle strip) showing per-node lifecycle state next to Ready and pressure conditions, plus two alerts: MaintenanceInProgress=True for longer than your maintenance SLO (a stuck window), and DrainInProgress=True with no Drained after your drain timeout (a wedged eviction, e.g. a PodDisruptionBudget that can never be satisfied). This is also where the ownership rule gets enforced: exactly one writer per condition type per fleet, recorded in the runbook, so a dashboard reading Drained=True means one specific component certified it.
3. A maintenance-window publisher. A small controller or cron job that reads your maintenance schedule and publishes MaintenancePlanned=True ahead of the window, transitions to MaintenanceInProgress=True when work starts, and clears both after. This is the prototype that pays off the "avoid migrating critical pods here" row: a cluster-autoscaler policy, a descheduler, or an agent placing a stateful workload can all consult one field instead of your calendar. Start with human-driven transitions (a CLI that sets/clears) before automating the schedule feed — the failure mode you are avoiding is a stale MaintenancePlanned that silently pins capacity planning forever, so every publisher needs a TTL or a reconciling clear.
All three share one upgrade story: when future Kubernetes releases teach core controllers to consume these conditions, your fleet's publishers don't change — they just gain readers. The DaemonSet rollout edge case in the announcement is the clearest candidate for what "gain readers" looks like: a node under maintenance currently consumes the rollout's availability budget and can stall the whole rollout on healthy nodes, because the controller can't distinguish "new revision failed" from "admin took the node out of service." MaintenanceInProgress gives it the place to look. Prototypes you build now become the data source those controllers will trust later.
From vocabulary to lifecycle-aware controllers
Step back and the shape of the release is clear. Garhwal's headline themes are AI workloads and control-plane hardening — gang scheduling and DRA maturing to beta, Memory QoS to beta, 16 promotions to stable — but Node Lifecycle Conditions is infrastructure of a different kind: not a feature you turn on, but a word the whole ecosystem agrees to use. The Node Lifecycle Working Group, SIG Node, and SIG Apps are explicitly inviting maintenance, remediation, autoscaling, and fleet-management projects to bring use cases, because the vocabulary only works if drain tools, autoscalers, storage operators, and GitOps reconcilers all publish and read the same five words. Longer-term work — explicit ownership, locking, potentially a dedicated API — is already scoped as the follow-up once the shared signal beds in.
For a self-hosted PaaS, the calculus is simple. You own the machines, the drain tooling, and the agent surface — so you can publish these conditions end to end without waiting for any vendor. Start with the drain wrapper this month, while the gate is still a no-op and the only consumer is your own dashboard. By the time core controllers learn to read the vocabulary, your fleet will already be fluent.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Agents are first-class operators there: machine-readable infrastructure state is the whole point. Star the repo on GitHub or deploy your first app today.



