# 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](../../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md) 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](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md) says a runtime serves the subjects its membership issues. What *runs* that runtime is [ADR 0150](../../02-DECISIONS/0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md): 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](../../02-DECISIONS/0005-the-node-host.md)): 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](../../02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md) and [ADR 0160](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md) 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. `execute` on 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](../../02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md)), and a mesh-scoped seat's verbs run on the node that holds it ([ADR 0121](../../02-DECISIONS/0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md)). 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](../../02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md)). 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](../../02-DECISIONS/0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md), [ADR 0150](../../02-DECISIONS/0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md) | 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](../../02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md) | 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](../../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md) §6, [to-be 34](../../03-DESIGN/01-to-be/34-the-console.md) | amended the same way | | [ADR 0170](../../02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md) §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](../../04-ISSUES/160-a-machine-says-little-about-itself-and-only-when-asked/00-report.md) 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.