Skip to main content

How It Works

The runtime mechanics behind Deploy V2 — detection, execution, recovery.

Detection & entry​

When a deploy is requested for a delivered release, the agent checks the release's artifacts directory for install.yaml:

  • present → the Deploy V2 engine takes over. It runs its own device-type, policy, and rule checks internally.
  • absent → the classic V1 deploy path runs, unchanged.

The V2 deploy is launched asynchronously (it does not block the request) and the deploy record's status is set to Start immediately, so a UI reflects progress right away.

A normal deploy runs the manifest's install action. Any other lifecycle action is started by name — from the action API, from a Deploy/v2 / FleetDeploy/v2 task, or from the orchestrator. Every start goes through the same entry, which checks for loops, takes the action's single-run lock (a second start of a running action is rejected with 409), and then validates and runs that action's task tree. These start checks run before the action's status is reset to Start, so a rejected start never overwrites it.

Per-task flow​

Each task is one unit of work — install a file, verify a step succeeded, undo one (revert), pull in a dependency, or group several child tasks. A task carries its own:

  • Type (what kind of task) and DeployType (the method that runs it) — see Action
  • its input — an ExeFile (a delivered artifact) or a Target (an endpoint URL or removal handle)
  • optional Rule (whether this device qualifies)
  • optional Arguments (with runtime placeholders)
  • optional LaunchTimeoutSec / ExecutionTimeoutMin
  • optionally its own nested Tasks — children it runs and waits for — see Orchestrator

That's what makes "a dedicated deploy flow per artifact" possible: each file, check, or step is its own independently configured task, and related tasks can be grouped so they succeed or fail together.

Task lifecycle & states​

Tasks run in manifest order (by index). A task that groups child tasks runs those children and only finishes once they do (see Orchestrator); leaf tasks run one at a time.

StatusMeaning
PendingNot started yet.
RunningCurrently executing.
DoneCompleted successfully.
FailedErrored, timed out, or its rule was not satisfied. Fails the whole deploy.
CancelledSkipped because the deploy was cancelled before it started.
SkippedA terminal, successful state: a Revert task whose cleanup was not needed (its target succeeded and verified). It counts as complete for progress and status.
Fleet rollouts add per-device outcomes

A FleetDeploy task also classifies each target device — targeted, excluded, rejected, or deferred — on top of the task's own status above.

Installs are all-or-nothing

An install step never silently "skips ahead". A rule mismatch, error, or timeout fails the task and the deploy — it does not move on. If a step should only apply to some devices, express that with a Rule: on non-matching devices the deploy fails loudly rather than half-installing. The only task that ends Skipped is a Revert whose cleanup was not required.

What happens for one Execute task​

Each Execute task is two-phase and time-bounded:

  1. Launch phase — the process (or API request) must start within LaunchTimeoutSec.
  2. Execution phase — it must finish successfully (a process exit code 0, an API 2xx) within ExecutionTimeoutMin.

The input a task resolves depends on its DeployType: a file installer (MSI/RPM/DEB/Script) locates its delivered ExeFile; an API trigger or a *_Uninstall acts on a resolved Target (a *_Uninstall also accepts an ExeFile). If either phase times out, or the process exits non-zero / the API returns non-2xx, the task fails and the deploy stops.

Crash recovery & resume​

Every task's status and timestamps are persisted to the agent database, per action, as they change. If the agent restarts mid-deploy and the same action is requested again, the engine reloads saved state, skips tasks already Done, and resumes from the first unfinished task. Task definitions always come fresh from the manifest — only progress is restored. A repeatable action is the exception: it starts fresh on every run (except when resuming after a planned reboot).

Cancellation​

Each running action registers a cancellation token. Cancelling (pausing) an action marks all its not-yet-started tasks Cancelled and stops the chain. Actions it started through Deploy/v2 are cancelled with it.

Self-update (installing the agent itself)​

When the deployed release is the agent's own package, the MSI stops the service and replaces the running binary — the current process is killed mid-install. The engine handles this: it launches the installer detached and exits; on the next startup it sees a task stuck in Running and reconciles — if the running version now matches the target, the task is Done; otherwise it resets to Pending and retries. No configuration is needed; the engine detects a self-update by matching the release's project name against the agent's own name.

Why it's designed this way​

Understanding the rationale helps you predict behavior in new situations:

Design choiceWhy
Manifest-as-data (recipe ships with the release)One agent installs anything; releases evolve without agent releases.
Installs are all-or-nothingDeterministic, safe outcome — a mismatched device fails loudly instead of half-installing; the only planned skip is a revert that was not needed.
Strictly ordered, parent-awaits-childPredictable, resumable, easy to reason about; a group finishes only when its children do.
Per-task state persistedEnables resume after a mid-install restart (critical for self-update).
Dependencies are themselves full V2 manifestsRecursion reuses one engine — "a plan is a manifest of manifests".
Placeholders resolved on-device at execution timeOne manifest adapts per device instead of being pre-rendered per target.
Single UTF-16LE deploy logAgent lines and msiexec's own log read back as one consistent document.

Where it fits (architecture & dependencies)​

Call chain​

What Deploy V2 depends on​

DependencyRole
DeliveryArtifacts and dependent releases must be delivered and "ready" before deploy.
Rule engineEvaluates each task's Rule against device metadata/context.
SettingsIO / CONFIGBacks {DeviceConfig.*} placeholders, timeout defaults, and DEPLOY_MAX_DEPENDENCY_DEPTH.
GetConfig (synced config groups)Backs {Config.*}, {ConfigJson.*} and {ConfigPath.*} placeholders.
DeployersRuns each task by a method keyed on DeployType — MSI/RPM/DEB/Script installs, MSI/RPM/DEB uninstall, and API/SSE handlers.
DatabasePersists per-task state for resume.
SSEPushes live status on every task transition.

What uses it, and the alternative​

  • Consumers — the deploy request path, the UI (status/progress), and — in future — the orchestrator
  • Alternative / boundary — the classic V1 deploy (no install.yaml). Detection is automatic; the two never mix within one release

See also​