The hub, the gate and the MCP server
An inventory per team, a catalogue compiled from the scripts, and an approval gate between a proposal and a run. imix serve runs any of it in one service.
These need more than instamix itself: pip install 'instamix[hub]'
(FastAPI, SQLModel, Jinja2 and, on Python 3.10 or newer, the MCP SDK).
Nothing a script needs to run.
$ imix serve hub mcp proxy --listen 127.0.0.1:8480
instamix: the hub at http://127.0.0.1:8480/admin, MCP at http://127.0.0.1:8480/mcp, the proxy at http://127.0.0.1:8480/LIST/
There are three apps, and imix serve runs any of them in one service:
imix serve hub, imix serve mcp, imix serve mcp proxy, all three.
They share a database (~/.instamix/hub.db, or DATABASE_URL), not a
process, so apart is as good as together. imix proxy serve is still the
proxy alone, without any of this installed.
The hub#
The hub keeps hosts and groups per team and serves them as an ansible
dynamic inventory at /api/v1/teams/TEAM/inventory. A group without a
team is global: every team has it, and a team’s own group of that name
goes on top, its vars winning key by key. The pages at /admin:
- Teams, Groups, Hosts: the inventory itself.
- Variables: as
group_varsandhost_vars. A table for every group the plays describe and one for every host, with a row for each variable described for it or for a group above it, showing the value the hosts get and where it is from: set here, a group above, the same group for every team, or the script’s default. A list or a dictionary is a table in the table; a list of dictionaries has a column for each key. Edit turns the table into a form of the same shape (a number box forint, a choice forOr("info", "debug")and forbool, rows to add and remove for a list), in which a value from above is only shown until Override is ticked. What is typed is held against the shape before it is saved. - Catalogue: the plays the hub knows and the recipes they amount to, and a button to read them again from the scripts.
- Templates: the job templates, a choice of the catalogue’s playbooks under a name each, to make, change and run.
- Actions: what was proposed, to allow or deny.
A script takes its hosts, and what groups and hosts set, from a hub:
$ INSTAMIX_HUB=http://127.0.0.1:8480 INSTAMIX_HUB_TEAM=lab python k3s.py run --tags k3s_server
INSTAMIX_HUB is a hub’s URL or the path of its database (settings
hub.url and hub.team). The script’s own hosts are then left out, and
the hub’s values go where a group(vars=...) would: over the defaults,
and through the shapes before anything runs.
The catalogue and the approval gate#
imix hub sync (or the button) compiles the plays into the catalogue:
each stage a script describes itself becomes a recipe. Its description is
the summary, the group its playbook targets is the group the recipe
applies to (a stage that targets all is refused), and the variables its
script describes are its parameters. Nothing else writes a recipe: there
is no API, page or tool for it.
Running one goes through the gate: something proposes an action, a person
allows it, and only then it runs, as imix SCRIPT run --tags STAGE with
the hub as its inventory. The run is confined to the approved hosts by
narrowing the recipe’s group in the inventory, not with --limit, which
would also take away the facts of hosts a play delegates to. An approval
is bound to the script as it was: when it has changed since, the action
is refused and has to be proposed again.
$ imix hub sync # the catalogue, from the repositories
$ imix hub actions # what waits
$ imix hub approve 3 # allow it, and run it here
$ imix hub deny 4
Running from the catalogue#
A person needs no proposal. On the Catalogue page each playbook (a script) and each stage of it is enabled or disabled – what is disabled can be neither proposed nor run, and stays so when the scripts are read again – and what is enabled is run from there: one stage, the ticked ones, or all, for a team, on the hosts of each stage’s group (or only the ones named). They run one after the other in the order of the script, and the first that fails ends the run: the rest are denied, saying why. Who presses the button is who allows it; each stage is still an action, with its output, on the Actions page.
A run can be given something of its own, from two folded forms: the playbook’s variables, as the Variables page has them, where a ticked one is an extra variable for this run and wins over the inventory (nothing is saved); and a few ansible options – check, diff, verbosity and the task to start at. Those are all the options there are: the inventory, the tags and the hosts of a run are not for a form to set. Both are part of what is allowed, and go to clockwork with the job.
Job templates#
Between the catalogue and a run is a job template: what the run is made of. There is always one, Everything, which is every enabled stage. On the Templates page another is made by giving it a name and ticking the playbooks, or the stages of them, it has. It keeps them by name and in the catalogue’s order, and says so when the scripts no longer have one. Running a template runs its stages that are enabled and have hosts of the team, as a run from the catalogue does.
The MCP server#
One tool per recipe, propose_STAGE, plus list_hosts and
get_action_status. A propose tool’s arguments are the hosts, as a choice
of the hosts in the recipe’s group, and the recipe’s variables with their
shapes as JSON Schema, so the protocol holds the caller to them. None of
the tools runs anything: a proposal waits at the gate.
$ imix serve mcp # over HTTP, at /mcp
$ claude mcp add instamix -- imix mcp --team lab # or on stdin and stdout
clockwork as the runner#
By default the hub runs what is approved itself, on its own machine, so it
holds the SSH keys. With INSTAMIX_CLOCKWORK it hands that to clockwork,
a service of its own:
$ clockwork serve -data ~/.clockwork -plays ~/plays -hub http://127.0.0.1:8480
$ INSTAMIX_CLOCKWORK=http://127.0.0.1:8490 imix serve hub mcp
clockwork has the keys, the jobs (objects in its own SQLite database) and
their output (a .log and the run’s events as .jsonl, in its own
directory). The hub is the frontend: on approval it submits a job – the
script, the stage, the targets and the values of the action, never a
command – and the Jobs pages show what clockwork has, the output of a
running job as it grows. Only how an action ended is kept on the action,
with the end of the output. clockwork refuses a script outside its
-plays directory, runs one job at a time, and takes its inventory from
the hub as any hub-fed script does.
| Setting | What |
|---|---|
INSTAMIX_CLOCKWORK | clockwork’s URL; unset, the hub runs things itself |
INSTAMIX_CLOCKWORK_TOKEN, ..._TOKEN_FILE | what the hub bears to it (clockwork’s -token-file) |
What did not come over from blue#
The hub and the MCP server came from the blue project. What did not come
over: the WordPress archive reader and its recipes, blue.yml manifests
(a script describes itself), risk tiers, and ansible-runner (a run is
the script’s own run).