Skip to content

Agent Updates

Overview

The Durantic agent runs on every machine you manage, and it updates itself. This page covers how you control which machines get a new agent, when, and — the part most of this page is about — what to do when a new agent turns out to be bad.

Agent updates are staged along one axis only: Environments, the same ordered promotion tiers config releases use. They are deliberately not staged along Rings.

Rings partition blast radius and converge in parallel — that is what they are for. Parallel is the right shape for config, which reports its own failures: a ring's breaker counts apply failures and trips. An agent that its own update has broken cannot report anything, so parallel tiers with a failure-counting breaker would be counting a signal that, in exactly the case that matters, never arrives. Environments are ordered and gated on evidence of health, which is the shape this needs.

The Three Levers, In Order

Lever Scope What it does
Account.auto_update_enabled fleet Master switch. Off means no machine updates itself.
Machine.auto_update machine Per-machine override of the above.
Account.agent_updates_gated fleet Whether a machine may be offered the deployment's version when its environment names none.

agent_updates_gated is the one worth reading twice.

Off (the default): a machine whose environment names no agent version — including a machine in no environment at all — is offered the version the control-plane deployment blesses. This is the behaviour that existed before staged rollouts, and it is the default so that turning staging on changes nothing until you ask it to.

On: such a machine is offered nothing. The deployment's version stops reaching your fleet, and the only way a machine gets a new agent is that its environment names one. That is the point of the flag.

Turn it on only once every machine is in an environment

agent_updates_gated: true is a silent freeze for any machine that is not in an environment. Nothing errors, nothing warns; those machines simply stop being offered updates. Check for unplaced machines first:

GET /api/provisioning/machines/?environment=      # machines with no tier

A silent freeze is the failure mode slowest to diagnose, which is why this is a flag you turn on deliberately rather than a default.

The gate does not touch the manual path. POST /api/provisioning/machines/{uuid}/agent-update works regardless — that pairing (auto-update off, machines moved one at a time) is the supported way to roll a fleet that has no environments to stage across.

Running a Staged Rollout

POST /api/config/agent-rollouts/     {"target_version": "2026.09.05.220731"}

The rollout walks your environments in promotion_order. In each tier it writes the tier's agent_version, waits for the machines there to take it and stay healthy, and then moves to the next tier. One rollout per account at a time — a second start gets a 409.

Starting is refused if the version is not one this control plane can serve. An agent release declares a min-controlplane-version floor, and sending your fleet onto an agent whose protocol the control plane cannot speak is precisely the failure that check exists for — so it happens once, at start, rather than machine by machine.

Watching it

GET /api/config/agent-rollouts/{uuid}

Per tier, you get the target, whether the cursor is there, and counts for every category the promotion decision itself uses:

Category Meaning
on_target Running the target, past its soak window.
soaking Running the target, not long enough yet to count as evidence.
outstanding Not on the target yet. The tier holds while any remain.
ahead On a newer version than the target.
unserveable Never reported a version, or already stranded.
excluded Cannot be moved right now — a machine mid-rebuild is the usual one.
newly_silent Went quiet while this tier held it. Halt evidence.
stale_silent Was already quiet when the rollout arrived. Not evidence.
transitioned_off_target Took the target and is now running something else. Halt evidence.

These numbers are the promotion gate's own output, not a summary computed alongside it. If the report says a tier is holding on two outstanding machines, that is the same sentence the engine is acting on.

hold_reason is stored rather than computed, deliberately: it is how you tell "the rollout is waiting for something" apart from "nothing is evaluating this rollout at all".

The two halt signals, and why they are these two

A rollout halts on newly_silent or transitioned_off_target — liveness and version. Never on a reported failure, because the failure this exists to catch is the one the agent cannot report: a bad binary that will not start, or starts and cannot reach the control plane. The agent's own rollback marks the attempt failed in memory and exits; the restarted old binary reports idle, and the control plane sees no failure at all. The only trace on the wire is that the machine went quiet, or came back on a different version.

Three Things To Know Before You Need Them

1. You cannot roll back by starting a rollout to an older version

The agent does not downgrade itself. Start a rollout to an older build and machines already above it stay where they are — they are reported as ahead, and the rollout completes having moved nothing.

The rollback procedure is two calls:

PATCH /api/config/environments/{uuid}          {"agent_version": "<the good build>"}
POST  /api/config/environments/{uuid}/agent-updates/force   {"allow_downgrade": true}

force reaches online machines only. The command rides the agent's event stream, so a machine that is offline cannot be sent anything — and a machine the bad binary took down is, by definition, offline. It comes back on the bad binary. Its remedy is a rebuild. The response names every machine it did not reach, one hostname at a time, rather than a count that hides them:

{
  "target_version": "2026.09.03.132052",
  "sent": ["node-a", "node-b"],
  "skipped_offline": ["node-c"],
  "skipped_already_on_target": [],
  "skipped_would_downgrade": []
}

node-c in that response is the machine you now go and rebuild.

force ignores the tier's agent_updates_paused on purpose: a paused tier is the state you are trying to get out of, and a lever that respects the stop it exists to override is not a lever.

2. Do not rebuild machines in the cursor tier during a rollout

A machine mid-rebuild is excluded from the gate — it takes itself away and brings itself back on its own schedule, so counting it as evidence would let a routine rebuild halt your rollout. But there is a real window between issuing the reboot and the control plane observing needs_provisioning, and in that window a rebuilding machine looks like a machine that went quiet.

Rebuild in a tier the cursor is not in, or halt the rollout first.

3. After a halt, ahead machines are expected

A halt reverts the tier to its previous version (or, if there is none to revert to, pauses the tier). Machines that already took the halted version are now running something newer than the tier's target — that is what ahead means, and it is the correct state, not a fault. They are excluded from the gate rather than blocking it, which is what lets the tier converge again once you have decided what to do with them.

If you want them moved down, that is the rollback procedure above.

Stopping Things

Call Stops
POST /api/config/agent-rollouts/{uuid}/halt Promotion. Tiers keep the version they hold.
POST /api/config/environments/{uuid}/agent-updates/pause Offering any version to one tier.
Account.auto_update_enabled = false Every machine updating itself, fleet-wide.

Two notes that have cost people time:

Halting is not a rollback and does not become one. Every tier keeps the version it already holds, and machines below it keep converging up to it. Halting stops the walk.

The agent-update pause is not the config-management pause. agent-updates/pause and /pause are separate breakers on the same environment, deliberately: a tier can be converging config perfectly well and still be the wrong place to put a new binary. Conflating them would mean stopping an agent rollout also stops config convergence across the tier.

Resuming

POST /api/config/agent-rollouts/{uuid}/resume

Picks up from where the cursor stands. It does not re-stamp when the cursor entered the tier — the soak and silence windows are measured from that moment, and re-stamping would turn a machine that has gone quiet into one that has merely not been seen yet, so the halt would not fire again on the same evidence.

It also does not unpause a tier. A halt with nothing to revert to pauses the tier instead of blanking its target — blanking would re-inherit the deployment's version, which on a first rollout is the very version being halted. So check the tier's agent_version and agent_updates_paused before resuming, or the walk will hold rather than move.

Overriding One Tier By Hand

PATCH /api/config/environments/{uuid}
{"agent_version": "2026.09.05.220731", "agent_updates_paused": true}

Both fields in one call, deliberately. Setting the version and then pausing as two requests leaves a window in which the rollout engine can run and overwrite the first, and the engine refuses to write a paused tier — so the pair is what makes a manual override hold.

Setting agent_version here does the same bookkeeping the engine does: the current value rolls into agent_version_previous and agent_version_set_at is stamped. That is what a later halt reverts to, so a tier moved by hand has a way back just as one moved by the engine does.