The predecessor is retired on every node and what it still owned on the two workstations — some thirty modules of dotfiles, user units and /etc files — is owned by nothing. To-be 29 covers one directory under the home; the operator wants the whole machine, system folders and home alike, as modules: one default configuration each, varied per node by settings or a kept region, never an edit; roles the machine has once as node-scoped seats with tool contracts; the graphical stack gated by a capability so the same catalogue serves the servers. Four documents: the intended behaviour in the mesh's words; the predecessor's desktop measured (34 modules, one with 88 files, 4 flavors and ~90 theme variables) against what the records already give and what is missing (the account is empty on every node, no user-scoped units, settings leak, tools run in a container per module per node); the direction the operator set for where tools run — one executor per node, host-side, module-agnostic, the console renamed and moved out of its container, superseding 0047/0150 for tools; and the candidate seats of the environment with first verbs, the shell first.
7.1 KiB
03 — One tool executor per node
The direction the operator set on 2026-10-02, the evidence it rests on, and what it supersedes. A direction, not yet a decision: the record is written when this effort graduates.
Where tools are served today
To-be 33 names three families — a role's tools on the seat, a module's own tools on the module, the mesh's own verbs on the controller seat — and ADR 0160 says a runtime serves the subjects its membership issues. What runs that runtime is ADR 0150: one supervised process per module, under the module's own account, carrying that module's compiled tools. Measured on the live mesh:
| who answers | how it runs | count |
|---|---|---|
| the mesh's own verbs | the controller binary, on its node | 17 verbs |
| the store seat | the store's own runtime | 2 verbs |
| the packet-filter seat | a container per node, built on the tool-runtime base image, with the network namespace and NET_ADMIN, on all four nodes |
3 verbs and 1 own tool |
| every module's own tools | the module's container, one per node it runs on | 67 tools across the catalogue |
| the console | a container per node, loopback MCP, invokes: * |
serves none, calls all |
| the host | — | serves nothing; answers no question about the machine |
The packet-filter holder is the case to look at. The module is a package, three files and a system service. To serve three verbs it also declares a built image and a container on every node whose only job is to answer them. Scaled to the environment — a shell, a prompt, a launcher, a notifier, a compositor, a login manager, a service manager, a boot loader, a downloads folder — that is one container per module per node for software that is itself not a container, and the operator's judgement is that tools should not run inside a container at all.
The direction
One tool executor per node, on the host side. A process the host supervises, the way the launcher supervises the host (ADR 0005): not a container, one bus credential for the node, module-agnostic. It loads the tool code of every module assigned to the node and serves each module's tools and each held seat's verbs on the subjects the membership issues — nothing changes in what ADR 0159 and ADR 0160 say about subjects, grants and memberships; what changes is that one process subscribes for the node instead of one per module.
- A module brings its tools as a built artifact, a bundle the pipeline produces, never an image. The executor knows bundles and subjects; it knows nothing of zsh or nftables.
- A tool is code the module wrote, one function behind an MCP verb.
executeon the shell seat is a function with a string argument. The executor does not declare, template or interpret tools; it runs them. - Root is the module's concern. A tool that must change the packet filter or rebuild boot images escalates itself. The executor does not run as root for everyone, and the caller does not know.
- Any node may call any tool on any node. The executor's credential may call everything, as the console's already does; per-module grants on the calling side are not kept.
- The mesh's own verbs stay with the controller (ADR 0154), and a mesh-scoped seat's verbs run on the node that holds it (ADR 0121). No hub is added; the controller's node is already one.
The console is the executor, renamed. It already runs on every node with a credential that may call everything, and it already serves the mesh's tools to whoever is on the machine over MCP on loopback (ADR 0152). It moves out of its container into the host's process tree, gains the serving half, and takes a name that says what it is — the node's tool runtime or simply node tools; "console" names the operator's half only.
What it supersedes, and what it keeps
| record | effect |
|---|---|
| ADR 0047, ADR 0150 | superseded for tools: one process per node runs every module's tool code, under one account. A module's long-running service — a daemon, a container — is untouched; the executor runs tools, not services. The record must say why one account for every module's tools is acceptable: every tool may be called from every node anyway, and root is taken by the tool, not granted to the process |
| ADR 0152 | kept in substance — a module, assigned per node, loopback MCP, the machine's login is the authority — changed in form: host-side, not a container; serves as well as calls; renamed |
| to-be 33 §6, to-be 34 | amended the same way |
| ADR 0170 §3, a container may ask for a capability | moot for that holder: the verbs run on the host side and escalate as they need |
| the container-runtime seat, proposed in an open change: the holder runs as a supervised process and serves the verbs locally to the host and on the bus | consistent — a supervised process serving verbs is what the executor is; the open question is whether that holder keeps its own process or serves through the executor like everyone else |
| the tool-runtime base image | no longer the way tools reach a node; may remain the way a module's service is built |
What stays open
- The executor's language. The host is a static Go binary and loads no plugins, so the executor is a sibling process, and its language decides the language of every tool bundle. One decision, taken once.
- How a bundle reaches the node. An artifact of the module's build, delivered as the host delivers everything else; whether it is a file resource in the declaration or a thing the executor fetches by digest.
- Reload. A push that adds or upgrades a module's bundle reaches a running executor as a reload, not a restart, or every tool on the node blinks on every push.
- The host's own questions. Issue 160 wants a machine to say more about itself. With an executor on every node, "what is this machine made of" is a seat verb like any other, served there.