Running
What run passes to ansible-playbook, where it builds, how a run is recorded and shown again, and the output other programs read.
ansible-playbook arguments#
run [PLAYBOOK] builds the tree into a temporary directory and runs
ansible-playbook (default: site.yml). It takes the common arguments
directly, and anything after -- is passed through verbatim:
$ python site.py run -C -D -l web -t nginx -e env=prod -vv
$ python site.py run --install --keep
$ python site.py run -- --vault-id dev@prompt
-C/--check, -D/--diff, -l/--limit, -t/--tags, --skip-tags,
-b/--become, -u/--user, -c/--connection, -e/--extra-vars
(repeatable), -i/--inventory, --start-at-task, --step,
--list-tasks, --list-hosts, --syntax-check, --vault-password-file,
-v (repeat for -vvv).
run builds into a temporary directory that is removed afterwards;
--keep keeps it, --dir DIR builds somewhere durable. The exit code is
ansible’s.
Before it starts anything, run says where ansible comes from (and so do
install, lint and test):
instamix: ansible-core 2.21.3 on Python 3.13.13, from the environment ~/.cache/instamix/envs/b3a74bc5109a4ae4
instamix: galaxy and pip through http://127.0.0.1:8470/webserver2
instamix: settings from ~/.config/instamix/config
Recording and replaying runs#
run --record FILE (and container --record FILE) also writes the run’s
events to FILE, while ansible’s output stays on the terminal as it is: what
ran and when, where ansible came from, each play and task, every host’s
result, and the recap. imix replay FILE shows such a file again,
flattened for reading:
$ python site.py run --record run.jsonl
$ imix replay run.jsonl # --failed, --host HOST, --results
site.py run --record run.jsonl (2026-09-27T17:22:52+00:00)
ansible-core 2.21.4 on Python 3.13.13, from PATH (~/.local/bin/ansible-playbook)
PLAY Try things [localhost]
TASK Change something (site.yml:5)
localhost: changed
TASK Fail, and carry on (site.yml:7)
localhost: failed ignored
msg: The command exited with a non-zero return code.
RECAP
localhost: ok=3 changed=2 unreachable=0 failures=0 skipped=0 rescued=0 ignored=1
It reads saved --jsonl output as well (- for stdin). In a container,
the events come back through a file mounted into it, so container --jsonl reports results too, once the run is over.
Machine-readable output#
Every command takes --jsonl (before or after the command name), or reads
INSTAMIX_JSONL=1. stdout then carries one JSON object per line, each
with an event field; stderr keeps its human messages, and the exit code
is unchanged. Locally nothing changes unless you ask for it.
$ python site.py run --jsonl -C
{"event":"exec","cmd":["ansible-playbook","-i","inventory","--check","site.yml"],"cwd":"/tmp/instamix-..."}
{"event":"output","stream":"stdout","line":"PLAY [localhost] ****..."}
{"event":"exited","cmd":["ansible-playbook","-i","inventory","--check","site.yml"],"status":0}
Output of the programs instamix starts is wrapped line by line in output
events, so it cannot break the stream. generate - is refused under
--jsonl, since the events own stdout. lsp speaks its own protocol and
has no --jsonl.
Each event and its fields is in the reference: Events.
The contract#
imix spec prints an OpenAPI 3.2 document, checked in as
api/openapi.json (download it), describing instamix
to other programs:
- each command is an operation whose request body holds its options, with
an
x-clinote saying how each becomes argv, and whose response isapplication/jsonl, oneEventper line; - each declaration call (
file,role,requires_ansible, …) is aDeclarationschema, and aScriptis a list of them. A program can build a script from data, check it against the schema before anything runs, and write it out as a real instamix script that still runs locally.
The operations are read from the commands themselves; the tests check the
events every command emits, and the declarations against their Python
signatures, so the document cannot drift. Regenerate it with
imix spec -o api/openapi.json.