Skip to content

Task

A task is one entry of a tasks: list in a Destiny or a scenario: a module: with its params: and the keys around it (register:, when:, loop:, …). Its full grammar is on the Destiny page. This page covers what happens to a task’s input — its rendered params: — when one task runs on many hosts.

The Keeper renders every task; a Soul receives values, never expressions. When a task targets several hosts, its input can differ from host to host: ${ soulprint.self.sid }, a host’s own vars, a loop: over host facts. The rule has four steps.

The Keeper renders the task’s params: once per targeted host, each in that host’s context: soulprint.self.*, the host’s vars, the loop variables. It does so for every task, including one whose params contain no expression at all.

After rendering, the Keeper compares each host’s params with the first host’s (hosts ordered by SID):

  • all equal — the task keeps one input, shared by every host. This is the common case, and nothing per host is kept;
  • at least one differs — the task keeps a complete input for every host, the ones that matched included. A partial map would send the hosts it leaves out the first host’s values, which is exactly the silent substitution this rule prevents.

A key present on one host and absent on another counts as a difference. One exception: for core.file.rendered, the template’s render_context (the facts a template reads through .self) travels per host on its own and is left out of the comparison.

Each host gets its own message, so each machine receives exactly its own input. Nothing is merged on the wire.

The run plan — what the web UI’s run detail and GET /v1/incarnations/{id}/runs/{apply_id}/tasks show — keeps one params map per task: the first host’s. Before it is stored, two passes run, in this order:

  1. masking — every sealed value (vault(), a secret input, …) is replaced by the mask;
  2. marking — every top-level key whose value differs between hosts is replaced by <per-host>.

So <per-host> is a display marker, not a mask: it says “this value is plural”, not “this value is secret”. Keys that agree across hosts stay visible. A masked value stays masked — the mask outranks the marker. The comparison is top-level only: if one field deep inside a map differs, the whole top-level key is marked.

Example — task write marker on three hosts:

- name: write marker
module: core.file.present
params:
path: /tmp/demo-marker
content: '${ soulprint.self.sid }'
host soul-1 receives: {path: /tmp/demo-marker, content: soul-1}
host soul-2 receives: {path: /tmp/demo-marker, content: soul-2}
host soul-3 receives: {path: /tmp/demo-marker, content: soul-3}
run detail shows: {path: /tmp/demo-marker, content: <per-host>}

A task such as cmd: hostname has the same input on every host and is shown as is. Its output differs per host, and the run detail lists each host’s output (stdout, stderr, exit_code) on that host’s own row.

The per-host inputs are not stored: after dispatch the run plan holds only the marked map, so a host’s actual input cannot be read back from the run. Check its effect on the host, or its output. Showing each host’s input in the run detail is planned.