Deploy V2 — Manifest-Driven Deployment
Deploy V2 lets a release describe how it installs itself with a small YAML file — install.yaml —
that ships inside the release. Instead of the agent applying one fixed recipe per package type
("this is an MSI, so run msiexec"), the release now carries an ordered list of tasks: install
this file, then run that script, but first make sure the device qualifies, and only after a required
companion release is installed. The agent reads the manifest, validates it end-to-end, and executes
the tasks one by one.
This page has two tracks:
- Part A — Product overview: what Deploy V2 is and why it matters, in plain terms.
- Part B — Developer guide: everything you need to author, validate, and debug a real
install.yaml.
It starts broad and shallow, then goes progressively deeper.
Deploy V2 turns on automatically the moment a delivered release contains an install.yaml in its
artifacts directory. Releases without one keep using the classic ("V1") deploy path, unchanged.
Part A — Product overview
What it is
Think of install.yaml as a short to-do list for one release. Each item on the list is a
task. Tasks run top to bottom, one at a time. If any task fails, the whole deploy fails and
stops.
The headline capability: each file/artifact in a release gets its own dedicated deploy task, with its own method, arguments, conditions, and timeouts — and the release author controls the order in which those tasks run.
Why it exists
The classic deploy path had one implicit, hard-coded recipe per package type. That works for a single file, but real installations often need more:
| Real-world need | Classic deploy (V1) | Deploy V2 |
|---|---|---|
| Install a package and then run a post-install script | Not possible in one deploy | Two ordered tasks |
| Give each file its own install behavior | One recipe for the whole release | One dedicated task per file |
| Only install on machines that match a condition | External policy only | Per-task rule, evaluated on the device |
| Pass install-time arguments that depend on the device | Static only | {placeholders} resolved at runtime |
| Require a companion release to be installed first | Manual, out-of-band | Declared dependency, driven automatically |
| Require a minimum agent version | Not enforced | MinAgentVersion gate |
| Survive an agent restart mid-install | Restarts from scratch | Resumes from where it stopped |
The core idea: "how to install this release" becomes data that lives with the release. One agent can then install anything a manifest describes, in the right order, with the right guardrails — without changing the agent.
What it gives you today
- Strict, up-front validation — the whole manifest is checked before any task runs: structure,
task ordering, per-method inputs, and cross-task rules. A bad manifest fails immediately with a
precise, path-addressed reason (e.g.
Task[1.0.2]: …) instead of half-installing. - A dedicated deploy flow per file/artifact — each task has its own deploy method, arguments, rule, and timeouts.
- Grouped, nested tasks — a task can bind a group of child tasks and only completes once they do, so related steps succeed or fail as one unit.
- Verify a step succeeded — a verification task (HTTP, script, or event stream) gates a group: the install is not accepted until the check passes.
- Undo on failure (revert) — a revert task nested in a group cleans up the steps it covers when they fail their check, so a failed group does not leave a half-applied install behind.
- Install and uninstall — dedicated removal methods (MSI / RPM / DEB uninstall, script, or API).
- Controllable task ordering — tasks run in the order written.
- Dependent-deployment control — a task can install and drive another release's full deployment, waiting for it to finish before continuing.
- Device-aware installs — per-task rules and
{placeholder}arguments adapt one manifest to many devices. - Reboot-aware — a task can declare that it reboots the device and resume correctly afterwards.
- Actionable status — a structured status document with per-task progress and machine-readable advisories (e.g. “reboot required”, “retry with force”) a UI can act on.
- Resilience — crash recovery/resume, cancellation, and safe agent self-update.
What's coming next
The manifest schema reserves a few fields and values for capabilities still on the roadmap, so today's manifests stay forward-compatible:
ConfigandMaptask types — apply a named configuration group or a map/data step instead of running a file.DockerComposeandHelmdeploy methods.- Orchestrated deployment across multiple devices — a master agent driving a fleet of child agents and tracking each one's task-level progress.
See Roadmap & the orchestrator for detail.
Part B — Developer guide
1. How it works (the mechanism)
1.1 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.
1.2 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) andDeployType(the method that runs it),- its input — an
ExeFile(a delivered artifact) or aTarget(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 §7.2).
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.
1.3 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 §7); leaf tasks run one at a time.
| Status | Meaning |
|---|---|
Pending | Not started yet. |
Running | Currently executing. |
Done | Completed successfully. |
Failed | Errored, timed out, or its rule was not satisfied. Fails the whole deploy. |
Cancelled | Skipped because the deploy was cancelled before it started. |
Skipped | A terminal, successful state: a Revert task whose cleanup was not needed (its target succeeded and verified). It counts as complete for progress and status. |
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.
1.4 What happens for one Execute task
Each Execute task is two-phase and time-bounded:
- Launch phase — the process (or API request) must start within
LaunchTimeoutSec. - Execution phase — it must finish successfully (a process exit code
0, an API2xx) withinExecutionTimeoutMin.
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.
1.5 Crash recovery & resume
Every task's status and timestamps are persisted to the agent database as they change. If the agent
restarts mid-deploy and the same deploy 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.
1.6 Cancellation
Active deploys register a cancellation token. Cancelling marks all not-yet-started tasks Cancelled
and stops the chain. For nested dependency deploys, cancelling the parent propagates to the children.
1.7 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.
2. Why it's designed this way
Understanding the rationale helps you predict behavior in new situations:
| Design choice | Why |
|---|---|
| Manifest-as-data (recipe ships with the release) | One agent installs anything; releases evolve without agent releases. |
| Installs are all-or-nothing | Deterministic, 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-child | Predictable, resumable, easy to reason about; a group finishes only when its children do. |
| Per-task state persisted | Enables resume after a mid-install restart (critical for self-update). |
| Dependencies are themselves full V2 manifests | Recursion reuses one engine — "a plan is a manifest of manifests". |
| Placeholders resolved on-device at execution time | One manifest adapts per device instead of being pre-rendered per target. |
| Single UTF-16LE deploy log | Agent lines and msiexec's own log read back as one consistent document. |
3. Where it fits (architecture & dependencies)
3.1 Call chain
3.2 What Deploy V2 depends on
| Dependency | Role |
|---|---|
| Delivery | Artifacts and dependent releases must be delivered and "ready" before deploy. |
| Rule engine | Evaluates each task's Rule against device metadata/context. |
| SettingsIO / CONFIG | Backs {Config.*} placeholders, timeout defaults, and DEPLOY_MAX_DEPENDENCY_DEPTH. |
| Deployers | Runs each task by a method keyed on DeployType — MSI/RPM/DEB/Script installs, MSI/RPM/DEB uninstall, and API/SSE handlers. |
| Database | Persists per-task state for resume. |
| SSE | Pushes live status on every task transition. |
3.3 What uses it & 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.
4. The manifest (YAML reference)
An install.yaml has two levels: the manifest (top-level) and its tasks.
4.1 Minimal example
The smallest valid manifest installs a single MSI:
ReleaseId: ID.MyApp@1.4.0
Type: Deploy/V2
Tasks:
- Type: Execute/v2
DeployType: MSI
ExeFile: my-app.msi
4.2 Manifest-level fields
ReleaseId: ID.MyApp@1.4.0 # required — must match the release being deployed
Type: Deploy/V2 # required — always exactly "Deploy/V2"
MinAgentVersion: 2.0.0 # optional — minimum agent semver required to run this manifest
Tasks: # required — ordered, non-empty list of tasks
- ...
| Field | Required | Description |
|---|---|---|
ReleaseId | ✅ | The release this manifest installs. Must equal the catalog id of the release being deployed (guards against a stale/mismatched install.yaml). |
Type | ✅ | The manifest kind. Must be the literal Deploy/V2. |
MinAgentVersion | ❌ | A semver string. If the running agent is older, the deploy fails before any task runs. Omit to skip. |
Tasks | ✅ | The ordered list of tasks. Must contain at least one. |
Manifest keys are PascalCase (ReleaseId, Type, Tasks). Placeholder sources (Device,
Env, Config, Release) are matched case-insensitively, but write manifest keys as shown.
4.3 Task-level fields
Tasks:
- Type: Execute/v2 # required — the kind of task
DeployType: MSI # required — the method used to run it
ExeFile: my-app.msi # the delivered artifact to run
Rule: { ... } # optional — device must satisfy this or the deploy fails
Arguments: "/qn PORT=8080" # optional — CLI arguments, may contain {placeholders}
Weight: 60 # optional — relative share of the progress bar
LaunchTimeoutSec: 60 # optional — seconds allowed to *start* the process
ExecutionTimeoutMin: 15 # optional — minutes allowed to *finish*
Task types
Type | What it does |
|---|---|
Execute/v2 | Run a thing — install a file, run a script, call an endpoint, or remove a package. The workhorse task. |
Verification/v2 | Check a step succeeded — an HTTP, script, or event-stream check that gates its group (see § Verifying a step). |
Revert/v2 | Undo a step — nested cleanup that runs when the step it covers fails (see § Reverting a step). |
Group/v2 | Bind child tasks — a container that runs no installer of its own; it groups several tasks (and their verification/revert) as one unit (see §7). |
Deploy/v2 | Sub-deploy a dependent release — delegates a full nested Deploy V2 of another release (see §7.3). |
Config/v2, Map/v2 | Reserved — fail as “unsupported” if used today. |
Deploy methods (DeployType)
| Group | Values | Used by |
|---|---|---|
| Install | MSI, RPM, DEB, Script, API | Execute |
| Uninstall | MSI_Uninstall, RPM_Uninstall, DEB_Uninstall | Execute, Revert |
| Verify | API (2xx = pass), Script (exit 0 = pass), SSE (wait for a matching frame) | Verification |
| Removal (revert) | MSI_Uninstall / RPM_Uninstall / DEB_Uninstall, Script, API | Revert |
| Reserved | DockerCompose, Helm | — |
A dependency task looks different — it has Type: Deploy/v2 and a ReleaseId, carrying no
ExeFile/DeployType (see §7.3). A group task has
Type: Group/v2 and a Tasks list, carrying no DeployType/ExeFile of its own.
Fields
| Field | Applies to | Description |
|---|---|---|
Type | all | The task kind (table above). |
DeployType | Execute, Verification, Revert | The method that runs the task (table above). Must be compatible with Type. |
ExeFile | Execute, Revert (file/uninstall methods) | The delivered artifact to run — an installer, a script, or an uninstaller/package. |
Target | Execute/Verification/Revert (API/SSE/uninstall) | A non-file handle: an endpoint URL (API/SSE) or a removal handle — product code / package name (uninstall). May contain {placeholders}. |
ReleaseId | Deploy | The dependent release a Deploy/v2 task sub-deploys. Required, and must differ from the manifest ReleaseId. |
Tasks | all | A nested list of child tasks this task binds and awaits (see §7). |
Rule | all | A rule-engine condition evaluated on the device right before the task runs. YAML object or inline JSON string. |
Arguments | Execute, Revert | A single CLI argument string for the process. May contain {placeholders}. |
Message | Verification (SSE) | The expected object a stream frame must match to pass; string values may be {placeholders}. |
Method | Execute/Verification (API) | HTTP method for an API call (default GET). |
Header | API / SSE | Request headers map. |
Body | API / SSE | Request body. |
Params | API / SSE | URL query parameters, percent-encoded; may contain {placeholders}. |
Weight | all | The task's share of the progress bar within its sibling list (see §8). |
CausesReboot | Execute | Declares the task reboots the device. Requires a nested Verification child (see § Reboot tasks). |
Force | Execute/Revert (uninstall methods) | Forces the removal — e.g. treat “not installed” as success, ignore dependencies. |
GraceTimeSec | Verification (and any retryable task) | Grace window within which a failed run is retried before the task fails. |
RetryCount | retryable tasks | Maximum attempts before failing. |
RetryBackoffSec | retryable tasks | Delay between retries. |
LaunchTimeoutSec | Execute | Time to launch before failing. Literal or {placeholder}. Default 60; falls back to Release.metadata.timeoutLaunch. |
ExecutionTimeoutMin | Execute + any parent/group | Time to complete before it's killed. Literal or {placeholder}. Default 15; falls back to Release.metadata.timeoutInstallation. On a parent it bounds the whole subtree. |
Reserved fields (parsed but ignored today):
| Field | Future task type | Purpose |
|---|---|---|
ConfigGroup | Config | The config group name to apply. |
TryRollbackToPrevious | Revert | Reinstall the previous version before uninstalling. |
5. Validation & requirements
Before any task runs, the whole manifest is validated. A failure at any stage marks the deploy
Error and writes the reason to the deploy log. Validation is thorough on purpose: a manifest either
runs cleanly or is rejected up front — it never half-applies.
| Stage | What it checks |
|---|---|
| read | install.yaml can be read from disk. |
| parse | Valid YAML that deserializes into a manifest. |
| structure + task rules | ReleaseId not empty, Type is Deploy/V2, Tasks non-empty, every task individually valid, task ordering valid (each Verification/Revert has a target), every Group non-empty, and every rebooting task has a nested Verification. |
| main-release | The manifest's ReleaseId equals the release being deployed. |
| agent-version | Running agent semver ≥ MinAgentVersion (skipped if absent/unparsable). |
| artifacts | Across the whole dependency tree: every release's delivery is Done (download status and lifecycle state) with all its artifacts present on the agent's disk; every task's ExeFile exists; every Deploy/v2 target is a registered dependency of its parent (declared in the component catalog), delivered and ready, and is itself a V2 component (ships its own install.yaml). |
| device context | Device metadata gathered for rule evaluation and placeholder resolution. |
| normalize weights | Weights scaled so each sibling list sums to 100. |
5.1 Per-task input rules
Each task must supply the inputs its method needs:
Execute— needs the input for itsDeployType:- a delivered
ExeFileforMSI/RPM/DEB/Script; - a
Target(endpoint URL) forAPI; - a removal handle —
ExeFileorTarget(product code / package name) — for*_Uninstall.
- a delivered
Verification— needs a verifyDeployType(API/Script/SSE). AnSSEverification additionally requires aMessageobject to match stream frames against. AnAPIcall'sMethod, if given, must be a valid HTTP verb.Revert— needs a removalDeployType(*_Uninstall/Script/API) and its handle.Deploy— needs aReleaseIdthat names the dependent release and is not the manifest's own.Group— must bind a non-emptyTaskslist.DeployTypemust be compatible withType(see the deploy-methods table in §4.3).- A
Rule, if present, must contain at least one condition (and/or/none).
5.2 Task-ordering rules (per sibling list)
A Verification or Revert only makes sense when it has something to act on. Validation checks this
within each Tasks list and reports the offending chain path (e.g. Task[1.0.2]):
- A
Verificationchild is valid after a precedingExecutesibling in the same list, or as a child of an actionable parent (anExecute— it verifies the parent). - A
Revertchild is valid after a precedingExecutesibling (or aVerificationthat follows one), or as a child of an actionable parent. - A
Verification/Revertwith no precedingExecutesibling and a pure-Group(or root) parent is rejected — it has no target. - A task that declares
CausesReboot: truemust bind a nestedVerificationchild (a sibling verification does not count): after the reboot, that nested check is how the agent confirms the step took effect.
Requirements checklist — for a manifest to deploy successfully on a device:
-
ReleaseIdmatches the deployed release exactly, andTypeisDeploy/V2. - Agent version ≥
MinAgentVersion(if set). - Every release in the tree has a
Donedelivery (status and state) with all its artifacts present on disk. - Every task supplies its method's input (
ExeFile/Target/ReleaseId/ non-emptyTasks). - Every
Verification/Reverthas a target; everyGroupis non-empty; every rebooting task has a nestedVerification. - Every
Deploy/v2target is a registered dependency of its parent, ready, and ships its owninstall.yaml. - Every task's
Rulepasses on the target device, and every{placeholder}resolves to a scalar.
6. Placeholders — dynamic values at runtime
Arguments and both timeout fields can contain placeholders of the form {Source.Path},
resolved on the device, just before the task runs.
Syntax
{Source.Path}
Source— one ofDevice,Env,Config,Release(case-insensitive).Path— a dotted path into that source.
The four sources
| Source | Resolves from | Example | Resolves to |
|---|---|---|---|
Device | The device metadata/context tree | {Device.type}, {Device.os.name} | Namer, windows |
Env | An OS environment variable | {Env.COMPUTERNAME} | the env value |
Config | The agent's runtime config.yaml (SettingsIO) | {Config.device.name} | the configured value |
Release | The release (component) record; metadata under Release.metadata.* | {Release.version}, {Release.metadata.timeoutInstallation} | 1.4.0, 20 |
Example
Tasks:
- Type: Execute/v2
DeployType: MSI
ExeFile: my-app.msi
Arguments: "/qn PORT=8080 DEVICE={Device.id} SITE={Config.device.site}"
On a device with id dev-42 and configured site north, the resolved command line becomes:
/qn PORT=8080 DEVICE=dev-42 SITE=north
Rules & gotchas
-
A placeholder must resolve to a scalar (string, number, bool). An object/array is an error.
-
If a placeholder cannot be resolved (missing key, unknown source), resolution fails and the task fails — placeholders are never left unresolved in the string.
-
Timeout fields accept a literal or a placeholder:
LaunchTimeoutSec: 90
ExecutionTimeoutMin: "{Release.metadata.timeoutInstallation}" -
When a timeout field is omitted, the engine uses, in order: the release-metadata default (
Release.metadata.timeoutLaunch/Release.metadata.timeoutInstallation), then the built-in default (60s to launch,15min to complete).
7. Task order, nesting & dependencies
7.1 Ordering within a list
Tasks in a list run in the order written. Task 2 starts only after Task 1 is Done.
Tasks:
- Type: Execute/v2 # runs first
DeployType: MSI
ExeFile: core.msi
- Type: Execute/v2 # runs only after core.msi succeeds
DeployType: Script
ExeFile: post-install.ps1
7.2 Grouping & nested tasks
Any task can carry its own Tasks list of children. The parent runs its children and only
finishes once all of them finish — a parent is never Done before its children are. This is how
you bind several related steps (and the check or cleanup that covers them) into one unit that
succeeds or fails together.
Use Type: Group/v2 when you want a container that runs nothing itself — it installs no file,
it just groups its children under one node (with one rolled-up status, one subtree timeout, and its
own share of the progress bar). A Group must have a non-empty Tasks list.
Tasks:
- Type: Group/v2 # a container: bundles the two steps below as one unit
Weight: 100
Tasks:
- Type: Execute/v2 # install
DeployType: MSI
ExeFile: app.msi
- Type: Verification/v2 # then confirm it took effect (gates the group)
DeployType: API
Target: "http://localhost:9000/health"
Children are addressed by a chain path — 1 is the second root task, 1.0 its first child,
1.0.2 the third grandchild — and that path appears in validation errors and the status trail so you
always know where in the tree something happened. A parent's ExecutionTimeoutMin bounds its
whole subtree: if the group runs past it, the engine stops the running children and fails the
group.
An Execute task can also bind children directly (not only a Group): it runs its own install
first, then its children — so a Verification/Revert child can target the parent install
itself.
7.3 Dependencies on other releases
Give a task Type: Deploy/v2 and set its ReleaseId to the release it depends on to make it a
dependency. The engine delegates a full, nested Deploy V2 for that release and waits for it to
finish before continuing. A Deploy/v2 task carries no ExeFile/DeployType — the dependent's own
install.yaml drives it.
ReleaseId: ID.MainApp@2.0.0
Type: Deploy/V2
Tasks:
- Type: Deploy/v2 # ← dependency: sub-deploy this release first
ReleaseId: ID.Runtime@1.1.0
- Type: Execute/v2
DeployType: MSI
ExeFile: main-app.msi # ← then install the main app
A dependency must be a registered dependency of the parent release (declared in the component
catalog — a manifest cannot pull in a release the server never registered as a dependency, whether
direct or transitive), delivered and ready (delivery Done with all its artifacts present), and
must itself be a Deploy V2 component (carry its own install.yaml). V2 releases can only depend on
V2 releases. The nested deploy is bounded by the task's ExecutionTimeoutMin, and its cancellation is
linked to the parent.
7.4 The dependency tree
Dependencies form a tree the engine walks depth-first and validates entirely up front:
7.5 Cycles, depth, and de-duplication
| Guard | Behavior |
|---|---|
| Cycle detection | A release appearing as its own ancestor (A → B → A) fails with "dependency cycle detected". |
| Depth limit | The tree may not go deeper than DEPLOY_MAX_DEPENDENCY_DEPTH (env var, default 10). |
| Idempotency / diamond de-dup | A release already deployed (Done) — e.g. Base reached via both Runtime and Plugin above — is not re-run; the second visit short-circuits. |
The whole tree logs into one shared deploy-log file, including which parent pulled in each dependency.
Verifying a step
A Verification/v2 task checks that a step actually worked, and gates the group it sits in:
the group's install is not accepted as Done until the check passes. Place a verification as the
last child of a group (or as a child of an Execute) so it covers the install(s) before it.
Three check methods:
DeployType | Passes when |
|---|---|
API | The HTTP call to Target returns a 2xx status. |
Script | The script (ExeFile) exits 0. |
SSE | A frame on the Target event stream matches the Message object (every key/value present). |
- Grace-time retry. A verification is retried within
GraceTimeSec(optionally capped byRetryCount, spaced byRetryBackoffSec) until it passes or the window elapses — then it fails. - Bounded wait. An
SSEverification waits for its signal but can never hang: the wait is bounded by the grace window / execution timeout, after which it fails. - On pass, the group can finish
Done; on failure the verification, its parent, and the deploy fail (and anyRevertin the group fires — see below).
- Type: Execute/v2
DeployType: MSI
ExeFile: app.msi
Tasks:
- Type: Verification/v2 # verifies the parent install
DeployType: SSE
Target: Radio
GraceTimeSec: 120
Message:
status: ready
Reverting a step (undo)
A Revert/v2 task undoes the install(s) it covers when they don't succeed — a per-step
cleanup, not a version rollback. Place it as the last child of a group; it targets the preceding
Execute sibling(s) in that group (or its actionable parent).
When it fires:
- Execute → Revert (no verification between): the revert runs only if the install failed.
- Execute → Verification → Revert: the revert runs only if the verification did not pass (a failed install before the verification also triggers it).
What happens:
- A revert runs a removal method —
MSI_Uninstall/RPM_Uninstall/DEB_Uninstall,Script, orAPI— reading its handle fromExeFileorTarget, with optionalForce. - A fired revert always ends the deploy in failure: it is cleanup for a step that already failed,
so even when the undo itself succeeds the parent is marked
Errorand the deploy stops. - If the covered step succeeded (and verified), the revert is not needed and ends
Skipped(a terminal, successful state that counts as complete) — a clean deploy still reaches 100%. - Tasks outside the group are untouched.
- Type: Group/v2
Tasks:
- Type: Execute/v2 # install
DeployType: MSI
ExeFile: app.msi
- Type: Verification/v2 # check it
DeployType: API
Target: "http://localhost:9000/health"
- Type: Revert/v2 # undo the install if the check fails
DeployType: MSI_Uninstall
Target: "{Msi.ProductCode}"
Reboot tasks
A task that restarts the device declares CausesReboot: true. Because the agent process may be
killed by the reboot, such a task must bind a nested Verification child — that check is how
the agent confirms, after coming back up, that the step took effect. (A manifest with a rebooting task
and no nested verification is rejected at validation.)
- Type: Execute/v2
DeployType: MSI
ExeFile: driver.msi
CausesReboot: true
Tasks:
- Type: Verification/v2 # re-checked after the reboot
DeployType: Script
ExeFile: check-driver.ps1
On restart, the agent resumes the deploy and re-checks the reboot task's nested verification instead
of re-installing: pass → the task is Done and the deploy continues; fail → the install did
not take, so the task (and deploy) fail, firing any revert. A non-reboot deploy interrupted by a
restart is not auto-resumed — it is marked Error and must be re-triggered.
8. Weights & progress
Each task can carry a Weight — its share of the progress bar. Weights are normalized per sibling
list, so each Tasks list sums to 100:
- If no task in a list sets a weight, that list's progress is distributed evenly.
- If weights are set but don't sum to 100, they are scaled proportionally (the last task absorbs rounding so the total is exactly 100).
- Nesting: a parent with children carries no weight of its own — its progress is the roll-up of its children. A leaf's real share of the whole bar is its weight × the weight of each ancestor (each as a fraction of its own list). Because every list sums to 100, the leaves across the tree still sum to 100.
Tasks:
- Type: Execute/v2
DeployType: MSI
ExeFile: big-package.msi
Weight: 80 # 80% of this list
- Type: Execute/v2
DeployType: Script
ExeFile: quick-config.ps1
Weight: 20 # 20%
Progress is the combined share of every leaf that reached a terminal-complete state — Done or
Skipped (a not-needed revert). Weights are cosmetic (progress reporting only) — they don't affect
ordering or success.