Every configurable thing on a node is a module, the home included, and a module is whatever it declares (0173, extending 0040). A node varies a module only through a setting rendered into the file or a kept region, never an edit (0174, extending 0011; issue 168 first). One tool runtime per node serves every module's tools on the host side, never in a container; the console is its serving mode, renamed node-tools (0175, extending 0150; 0047/0150/0152 carry dated notes). The login shell is a node seat held by one shell module with `execute` as its contract (0176). A unit may be user-scoped and the service manager is a node seat held by systemd (0177). To-be 37 is handed off in-progress to mesh-host, mesh-controller, mesh-tools and mesh-catalog, with the build in order: the account on every node, the runtime, zsh, systemd, then the graphical stack. To-be 29 keeps ~/.ssh and points at 37; 33 §6 and 34 are amended; the glossary gains node tools, bundle, kept region, installed/holding, and retires flavor.
123 lines
8.0 KiB
Markdown
123 lines
8.0 KiB
Markdown
---
|
|
topic: what runs on it
|
|
status: accepted
|
|
date: 2026-10-02
|
|
deciders: jochen
|
|
reconstructed: false
|
|
extends: 02-DECISIONS/0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md
|
|
---
|
|
|
|
# 175. One tool runtime per node serves every module's tools, on the host side
|
|
|
|
## Context
|
|
|
|
A module's tools are code the module wrote, one function behind each verb, served on the subjects
|
|
the controller issues in the module's membership
|
|
([ADR 0160](0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)).
|
|
What *runs* that code is [ADR 0150](0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md):
|
|
a supervised process per module under the module's own account, and in the catalogue as built,
|
|
that process is a container per module per node, built on the tool runtime's base image.
|
|
|
|
Measured on the live mesh ([research 018](../01-RESEARCH/018-the-operators-machine-as-modules/03-one-tool-executor-per-node.md)):
|
|
67 module tools, each served from its module's container; the packet-filter seat's three verbs
|
|
served by a container with `NET_ADMIN` on every one of four machines, for a module that is
|
|
otherwise a package, three files and a service; and the console, a container per node, calling
|
|
everything and serving nothing. The operator's environment adds a dozen modules of the
|
|
packet-filter shape, and the operator's judgement is plain: *I would never run MCP tools inside
|
|
a container; that is a very bad design.* And: *I don't care about permissions or account per
|
|
module, that just complicates things for no good reason. Just a node-level tool executor. If a
|
|
command needs root, that's the module's concern.*
|
|
|
|
The tool runtime itself was written for this. Its own description: *the per-node process that
|
|
makes a module's tools actually serve — imports the assigned modules' compiled tool entrypoints,
|
|
each of which registers its tools as it loads; on a node the host resolves the list and starts it
|
|
like any other supervised workload.* What the catalogue did instead was build one image per module
|
|
around it.
|
|
|
|
## Considered Options
|
|
|
|
1. **Keep a process per module.** Rejected: one container per module per node for software that
|
|
is not a container, and the account-per-module invariant it exists to protect is one the
|
|
operator declines to pay for.
|
|
2. **The host executes tools itself.** Rejected: the host is a static Go binary that loads no
|
|
plugins; a module's tools are TypeScript on the SDK, and building a second SDK in Go for the
|
|
host's sake is the cost ADR 0039 refuses.
|
|
3. **One tool runtime per node, a sibling of the host, loading every assigned module's bundle.**
|
|
Chosen. It is what the runtime was written to be.
|
|
|
|
## Decision
|
|
|
|
**1. One tool runtime per node, supervised by the host, on the host side — never a container.**
|
|
The host starts it the way the launcher starts the host
|
|
([ADR 0005](0005-the-node-host.md)): a process on the machine, restarted when it dies. It holds one
|
|
bus credential, the node's. It is module-agnostic: it knows bundles and subjects, nothing of what
|
|
any module does.
|
|
|
|
**2. It serves every assigned module's tools and every held seat's verbs** on the subjects the
|
|
memberships issue. ADR 0159 and ADR 0160 are unchanged in what they say about subjects, grants
|
|
and memberships; what changes is that one process on the node subscribes to all of them instead of
|
|
one process per module. A module that runs a long-lived service of its own — a daemon, a
|
|
container — keeps it; this record is about tools.
|
|
|
|
**3. A module brings its tools as a bundle**, the artifact kind the catalogue already has for
|
|
interpreted code, built by the pipeline and delivered to the node by the host as it delivers any
|
|
artifact. Never an image. The runtime loads each bundle as the membership names it, and a push
|
|
that adds or replaces a bundle reaches a running runtime as a reload.
|
|
|
|
**4. Root is the module's concern.** A tool that must change the packet filter or rebuild boot
|
|
images escalates itself. The runtime does not run as root for everyone's sake; the caller does not
|
|
know and need not.
|
|
|
|
**5. Any node may call any tool on any node.** The runtime's credential may call everything, as
|
|
the console's already may. A per-module calling grant is not kept.
|
|
|
|
**6. The console is this runtime's serving mode, renamed.** [ADR 0152](0152-the-operators-surface-is-a-module-the-console.md)
|
|
stands in substance — a module assigned per node, MCP on the machine's loopback, the machine's
|
|
login is the authority — and changes in form: host-side, serving as well as calling, and named for
|
|
what it is: **node tools**. The mesh's own verbs stay with the controller
|
|
([ADR 0154](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md)); a mesh-scoped seat's verbs
|
|
run on the node that holds it ([ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md)).
|
|
|
|
**Where ADR 0047 and ADR 0150 say a module's tools are served by the module's own process under
|
|
the module's own account, read this record.** Everything else they decided stands: a tool is served
|
|
on its own subject, only the module that serves it answers, a module's long-lived processes are the
|
|
machine's to supervise. The invariant 0150 kept — one account per module — no longer holds for
|
|
tools, and the reason is stated above: every tool is callable from everywhere by decision 5, so the
|
|
account no longer scopes anything a caller cannot already reach.
|
|
|
|
## Consequences
|
|
|
|
- The packet-filter module's container goes; its verbs run on the host side and escalate as they
|
|
need. [ADR 0170](0170-the-firewall-seat-serves-its-verbs.md) §3's container capability is moot
|
|
for it.
|
|
- The tool runtime's base image stays the way a module's *service* may be built; it is no longer
|
|
the way tools reach a node.
|
|
- The node tools runtime needs an interpreter on the machine. The module that is the runtime
|
|
declares it as a package.
|
|
- The container-runtime seat proposed in an open change says its holder *runs as a supervised
|
|
process and serves the verbs locally to the host and on the bus*. A supervised process serving
|
|
verbs is what this runtime is; whether that holder keeps a process of its own or serves through
|
|
the runtime is for that record's build to say.
|
|
- What got harder: one process carries every module's tool code on a node, so one module's
|
|
faulty bundle can take down the node's tools. The runtime loads each bundle guarded and names
|
|
the one that failed; the others serve.
|
|
|
|
## How it is checked
|
|
|
|
| Rule | Checked by |
|
|
|---|---|
|
|
| The runtime loads every bundle its memberships name and serves each tool on its subject | the runtime's tests against a real bus: two bundles, three tools, each answers |
|
|
| A bundle that fails to load is named and the others serve | the same tests, with one bundle that throws on load |
|
|
| The host supervises the runtime and restarts it | the host's tests over the launcher's shape |
|
|
| A push that replaces a bundle reloads it without a restart | the runtime's tests: a bundle replaced on disk, the membership re-read, the new tool answers |
|
|
| No module in the catalogue declares a container whose only purpose is tools | a catalogue check: a manifest with `tools` and an image artifact built on the tool runtime's base is refused once the runtime is live |
|
|
| Live | `login-shell.execute@<node>` answers on every node from the node tools runtime; `docker ps` shows no per-module tool container |
|
|
|
|
## References
|
|
|
|
- [Research 018](../01-RESEARCH/018-the-operators-machine-as-modules/03-one-tool-executor-per-node.md)
|
|
- [ADR 0150](0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md), [ADR 0047](0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md),
|
|
[ADR 0152](0152-the-operators-surface-is-a-module-the-console.md), [ADR 0159](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md),
|
|
[ADR 0160](0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md), [ADR 0154](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md)
|
|
- [To-be 33](../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md), [to-be 34](../03-DESIGN/01-to-be/34-the-console.md)
|