--- topic: what runs on it status: accepted date: 2026-09-30 deciders: jochen reconstructed: false extends: 02-DECISIONS/0095-the-control-plane-is-the-way-to-ask-a-module.md --- # 152. The operator's surface is a module the mesh assigns: the console ## Context **Since the bus moved, nobody can ask the mesh anything without opening a shell on a machine.** Every tool call an operator's assistant makes fails, on every machine including the one the operator sits at, with *AMQP not connected* ([issue 147](../04-ISSUES/147-the-operators-tools-still-dial-the-bus-that-was-removed/00-report.md)). The program answering is the predecessor's tool server, started on the workstation by hand, with the predecessor's broker address written into the assistant's own configuration. It has no manifest, no assignment, no account on the bus, and the mesh has never known it exists. Nothing regressed: the mesh removed a transport that a program outside the mesh still dials. **The mesh has a tool model, and it works.** A module serves each tool on its own subject and its account may serve nothing else ([ADR 0047](0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md)); a person is issued an account whose only permission is to publish the tool subjects named at issue ([25 — The bus on NATS](../03-DESIGN/01-to-be/25-the-bus-on-nats.md) §7); a client on the runtime repository's main branch speaks that account as a command line and as an MCP server. Measured on the live mesh on 2026-09-28: a call to the forge's `gitea_list_repos` answered with real repositories; the tool list came back empty, because it asks the catalogue for a tool nothing serves ([ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md)). **Two records have already said where the surface belongs.** ADR 0132's consequences: *the MCP surface belongs inside the mesh — a module the mesh assigns to the machine where the agent sits, with a credential the mesh minted and authority derived from what it may call, not a program started by hand with a credential printed to a terminal.* Design [33](../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md) §6 says the same. Neither is a decision about the surface: 0132 decided where a seat's tools live, and named the surface in passing. **And one record says the opposite, in the letter.** [ADR 0095](0095-the-control-plane-is-the-way-to-ask-a-module.md) made the control plane *the* way to ask a module, deferred "calling is a grant" until something asked for it, and recorded that *nothing outside the control plane can*. A person's account (design 25 §7, built 2026-09-28) is exactly that grant, minted for a person. So 0095's exclusivity has already been widened once without a record saying so; a module that calls tools widens it a second time, and this record is where that is said. **What a module's account may do today, counted from the composition** (`internal/broker`): publish its own events, publish the accept subjects of seats it uses, subscribe its own tools and what it consumes. No module principal may publish another module's tool subject. Of 72 modules in the catalogue, 45 serve tools and 0 may call one. The question the work order asks before any code: **does the mesh grow its own operator surface, or is the surface an ordinary module that happens to serve tools?** ## Considered Options **1. The control plane serves the agent protocol itself** — a listener on the controller, or a verb its binary runs. Rejected. It puts a surface for tools the control plane does not implement on the one component that must stay answerable while it is itself being replaced, which is the reason 0132 rejected the control plane as the answer to discovery. A person on a workstation would reach it over the network, and the networked surface [ADR 0035](0035-one-implementation-several-surfaces.md) reserves for that authenticates through an OAuth2 provider that is not configured — so the controller's `api` verb correctly serves nothing today, and this option would either wait for it or bypass it. **2. A program a person installs and starts by hand with a printed credential** — what exists on the runtime repository's main. Rejected as the end state. It is outside the mesh in every way issue 147 names: no assignment, no declaration, no seat, no check that it reaches anything, revoked only by a person remembering to. It is the predecessor's arrangement one bus later, and it fails the same way the next time an address moves. It stays as the recovery path, the way the command line does (ADR 0035): a credential from `operator issue` and the `mesh` client work with no console assigned. **3. An ordinary module, assigned to the machine the person sits at, holding a credential the mesh minted, serving the mesh's tools on that machine's loopback.** Chosen. ## Decision **The console is a module.** `mesh-console` is built by the mesh, registered like any module, assigned to a machine, and given a bus credential sealed to that machine. It serves the mesh's tools to whoever is on that machine: to an agent over MCP, and to a person through the same endpoint. Assigning it to a machine is what makes the mesh reachable from there; unassigning it revokes that, at the next composition, with nothing on the machine to remember to remove. **A grant to call is a manifest word: `invokes`.** A module declares the tools it calls, each as `.`, or the single entry `*` for every tool on the mesh. The bus grants exactly that publish side and nothing beside it — no event, no subscription, no seat. This is ADR 0095's deferred first option, taken now that a consumer asks for it; a person's account already has this shape, and the same composition derives both. The control plane's `ask` stands, and 0095's audit point with it: every call still passes one account whose permission list says what it may ask. **Authority is the machine's login.** The console listens on the machine's loopback only, declared `from: machine`, so whoever can open a socket on the machine is whoever owns the machine, and *the account that installed the host owns the mesh on that node* ([ADR 0034](0034-the-local-account-owns-the-mesh.md)). Anything on a machine may call anything on it, and that is the whole of local ([ADR 0144](0144-anything-on-a-machine-may-call-anything-on-it.md)). The mesh knows no person: what the audit sees is which console asked, under the account `.mesh-console`. How a person's identity reaches a session is the question design 15 leaves open, and this record does not close it. **What the console lists is asked of the modules.** Design 33 §5: a module's own tools are answered by the module, from the code that defines them. The tool runtime therefore answers one reserved verb for every module it serves — `tools`, the module's tool names, descriptions and argument schemas — and the console assembles its list by asking the catalogue which modules the mesh holds and each module what it answers. A module that is not running is absent from the list and says so; a tool an agent already knows the name of can be called whether or not it was listed. A module may not name a tool of its own `tools`; the runtime refuses the collision at load rather than letting one shadow the other. A role's tools, and the mesh's own verbs, join the list when the `mesh-controller` seat serves them (design 33 §1, third family) — the console reads whatever the mesh can say about itself, and grows as that does. **The console holds one credential and one grant: `*`.** It is the operator's surface on a machine the operator owns; narrowing what it may call is a setting on its assignment, which [ADR 0046](0046-a-module-configuration-is-its-assignments-not-its-manifest.md) already provides for and nothing here builds. ## Consequences - **The way a person drives the mesh is inside the mesh.** It is declared, delivered, replaced and revoked by the same machinery as everything else, and `status` says whether the machine carrying it has applied. Issue 147's shape — a surface kept alive by an address in a file — cannot recur, because there is no file: the console's credential names the bus the mesh is on, and moves when it does. - **A module may now call tools, which ADR 0095 had reserved to the control plane.** The grant is explicit, per tool or `*`, and derived by the same composition that grants everything else. A module that declares no `invokes` gains nothing. What got harder: a manifest reviewer has one more field to read for authority, and `*` in it deserves the reader's attention every time. - **A new manifest word ships one release before any manifest uses it**, and must reach both parsers: the build machine's and the running controller's. The console's manifest cannot be registered until the controller and the builder that packages it have been rebuilt with the word. - **Discovery costs a fan-out per list.** One request per module the mesh holds, answered at once by the bus for every module nothing serves, so the cost is bounded by the modules that are up. The list is cached briefly in the console; a module assigned a moment ago appears at the next refresh. - **Every tool runtime must be rebuilt once** to answer `tools`. Until a module is, it is callable and not listed, and the console says which modules did not answer. - **The mesh's own verbs are not in the console yet.** `status`, `push`, `assign` are the `mesh-controller` seat's tools under 0132, and the three prerequisites 0132 names are still not in place. A person asking what a node runs still opens a shell for that question, and that gap is design 33's to close, not this record's — recorded here so nobody reads the console as the whole of 147. - **The person's credential is not retired.** `operator issue` and the `mesh` client remain the path when no console is assigned, and the path an operator uses to bring a mesh up far enough to assign one. ## How this is checked | Rule | Checked by | |---|---| | A module's `invokes` becomes exactly that publish grant, and nothing else | the bus user composition test: a module invoking `shop.price` may publish that subject and no other tool's; `*` may publish every tool subject; neither may publish an event or subscribe anything it did not consume | | A malformed `invokes` entry is refused at registration | a parser test: an entry naming no tool is a problem named in the manifest's words | | The runtime answers `tools` for every module it serves | the runtime's test: a module registering two tools answers three names, and a module naming one of its own `tools` is refused at load | | The console's list is what the modules answer | the client's test against a real bus: two modules up, a third the catalogue holds and nothing serves, and the list carries the two and names the third as not answering | | A call from the console reaches a module over the bus | the same test, and the live mesh: the console assigned to a workstation answers `tools/list` on its loopback and a call to the forge returns repositories | | The console listens on loopback and nowhere else | its manifest declares `from: machine`, and the filter composed for the machine opens nothing for it | ## References - [issue 147](../04-ISSUES/147-the-operators-tools-still-dial-the-bus-that-was-removed/00-report.md) — the surface outside the mesh - [ADR 0095](0095-the-control-plane-is-the-way-to-ask-a-module.md) — extended: a grant to call, for a module as for a person - [ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md) — where a seat's tools live, and the sentence that named the surface - [ADR 0035](0035-one-implementation-several-surfaces.md) — three surfaces over one implementation - [ADR 0034](0034-the-local-account-owns-the-mesh.md), [ADR 0144](0144-anything-on-a-machine-may-call-anything-on-it.md) — why loopback is the authority boundary - [33 — The tools the mesh answers](../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md) §5, §6 — discovery, and what serves it to an agent - [34 — The console](../03-DESIGN/01-to-be/34-the-console.md) — the design this record authorises - mesh-tools `src/client.ts`, `src/mcp.ts`, `src/mesh.ts` — the client this makes a module of