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.
Per-host input
Section titled “Per-host input”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.
1. Rendered for every host
Section titled “1. Rendered for every host”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.
2. Compared as a whole
Section titled “2. Compared as a whole”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.
3. Sent per host
Section titled “3. Sent per host”Each host gets its own message, so each machine receives exactly its own input. Nothing is merged on the wire.
4. Shown once, with <per-host>
Section titled “4. Shown once, with <per-host>”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:
- masking — every sealed value (
vault(), a secret input, …) is replaced by the mask; - 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.
What is not kept
Section titled “What is not kept”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.