Bounded Autonomy
An autonomous patching agent should earn its reboot
Routine updates can be automated without treating a reboot as a harmless side effect: discover, bind approval, prove recovery readiness, change in a window, then verify the result.
Updating is an operation, not a calendar event
“Patch every night” sounds responsible until the job is reduced to one package command and a hopeful restart.
An update can fail before it reaches a package. A repository can be unavailable. A machine can be unable to recover its critical services after a reboot. A new kernel can be installed without the usual reboot marker. And a successful package-manager exit tells you little about whether the intended workload came back.
I built a maintenance controller around that reality. It handles routine, trusted updates on a schedule, but only after it has earned the right to make a change. The goal is not maximum unattended activity. The goal is a repeatable chain of evidence:
discover → bind approval → prove recovery readiness → change in a window → verify the running result
Discovery is read-only on purpose
The first scheduled stage only asks what trusted package sources offer. It does not install anything. It prepares an exact approval state from the candidate set so the later maintenance window is not acting on an ambiguous, drifting description such as “apply updates.”
This split matters because package discovery has its own failure modes. In one incident, a single obsolete third-party repository returned an error. The package-manager refresh failed as a whole, and the maintenance job correctly stopped before considering packages or a reboot.
The repair was to remove the unsupported source after confirming that its package was not in use. The lesson was broader: a controller should surface a broken source as a blocked maintenance state, not silently skip it and report that automatic patching is working.
Read-only discovery also provides a useful audit point. Before a write happens, there is a specific candidate set, a timestamp, and a known source boundary to review.
Approval should describe one change, not grant standing permission
The controller does not treat an informal message as authority to alter the host. It checks a separately recorded approval state that is bound to the discovered maintenance plan.
That boundary keeps approval from becoming a reusable “yes, update things” token. If the candidate set changes, the recorded state must no longer be treated as approval for the new set. If the state expires or cannot be read, the safe result is no change.
This is the smallest useful version of an autonomy control: an action should be specific enough that the reviewer can understand what they are approving, and the executor should reject an approval that no longer describes its work.
Recovery readiness is a precondition for change
The maintenance window does not begin with a reboot. It begins with recovery preflight.
Before routine package maintenance can proceed, the controller checks the system's recovery contract: the critical services and their dependencies are expected to be able to come back, and the recovery verification has to be in a healthy state. A failed preflight blocks the window instead of allowing an update to create a larger outage.
The window itself is finite. That gives the controller a defined period to perform routine trusted APT maintenance and, when the approval state permits a restart, reboot before the cutoff. A restart outside that boundary is not just a technical action; it is an interruption with a recovery obligation.
After a controlled reboot, the meaningful evidence is not merely that the reboot command returned. The controller checks that the boot identity changed as expected and that the declared service-recovery contract becomes healthy again. A running process is not enough if the service cannot reach its dependency, receive its secrets, or serve the path it exists to serve.
Livepatch changed the signal, not the responsibility
One patching incident exposed the difference between a signal and the state it is meant to describe.
On an Ubuntu system using Canonical Livepatch, installing a new kernel did not create the conventional reboot-required marker that the controller expected. Livepatch can reduce the urgency of rebooting the currently running kernel, but an installed new kernel still needs a restart before it becomes the kernel in use. Canonical's Livepatch and kernel-update documentation makes that restart distinction explicit.
The controller therefore gained an additive kernel-change detector rather than altering a distribution-owned update hook or disabling Livepatch. It is idempotent, it records only the relevant pending-kernel condition, and the existing approval, recovery preflight, and maintenance-window gates still decide whether a reboot occurs.
That fix is intentionally incomplete. The conventional marker can also be the only signal for some non-kernel restart cases, such as core library or service manager updates. That broader detection work remains open. Naming the gap is part of the design: a control with an unproven blind spot should not be marketed as comprehensive automation.
The agent's most important action is refusal
The controller can do a lot without becoming a general administrator:
- discover routine updates from trusted sources;
- prepare and check a specific approval state;
- block when recovery readiness is not proven;
- apply routine maintenance inside a defined window;
- perform an approved reboot before its cutoff; and
- verify that the system returned to its declared operating state.
It cannot treat a stale approval as current, turn an unverified repository into a package source, turn a failed recovery check into a warning, or use a model's suggestion as executable authority.
Those refusals are not a limitation of the automation. They are the reason I am willing to let it handle the repetitive part of maintenance.
Bounded autonomy is more useful than a stronger adjective
The phrase “fully autonomous patching” hides the decisions that matter: which updates qualify, when an interruption is acceptable, what recovery proof is enough, and who owns an unexpected result.
My system is deliberately narrower. It is autonomous about discovery, preflight, routine execution within a defined policy, verification, and notification. It is not autonomous about changing that policy, approving a novel change, bypassing a failed recovery check, or closing an unresolved signal gap by assumption.
That is the standard I would use for any maintenance agent: let it earn its reboot with evidence, and make it stop when the evidence runs out.
If this overlaps with something you are working on
Send me a short note describing the workflow, what is frustrating about it today, and any data, timing, or approval constraints that matter. Start a conversation.
Three commentaries
These are invited responses to the article's argument and its possible evolution. They are viewpoints, not independent reviews, endorsements, or evidence for the claims above.
Codex
This is a convincing autonomy pattern because it treats approval and recovery evidence as preconditions rather than decoration around a scheduled update. The explicitly open non-kernel reboot gap is part of its credibility. The next technical step is a second, marker-independent restart signal, tested against both ordinary maintenance and deliberately broken preflight conditions.
Qwen, local model
This bounded approach wisely treats reboots as high-stakes events rather than routine side effects. The explicit refusal to treat stale approvals as valid is a strong safety signal. However, the open gap in detecting non-kernel restart requirements remains a significant blind spot. The idea should evolve toward a more comprehensive state-detection mechanism that can reliably identify when core libraries or service managers require reboots, ensuring the recovery readiness gate is truly robust before any change is executed.
Claude
Binding approval to a specific discovered candidate set, rather than a standing yes, is the control I'd copy. The article names its own blind spot honestly, but the detector still sits alongside distribution-owned markers rather than replacing the assumption behind them. A marker-independent evolution: derive pending-restart state by comparing what is running against what is installed, then let the existing window and preflight gates decide.