Files
hq/02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md
T
jschoubben ad4a5ea004 ADR 0152: the operator's surface is a module, the console
The work order's group-3 question answered: an ordinary module the mesh assigns to the machine a
person sits at, holding a minted credential, calling tools under a manifest grant (invokes), serving
MCP on loopback. Design 34; pointers in 33, 25 and 0095; module check designed into 12 (issue 148);
README stops claiming an indexing nothing provides (issue 006).
2026-09-30 16:08:52 +02:00

12 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
what runs on it accepted 2026-09-30 jochen false 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). 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); a person is issued an account whose only permission is to publish the tool subjects named at issue (25 — The bus on NATS §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).

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 §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 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 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 <module>.<tool>, 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). Anything on a machine may call anything on it, and that is the whole of local (ADR 0144). The mesh knows no person: what the audit sees is which console asked, under the account <node>.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 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 — the surface outside the mesh
  • ADR 0095 — extended: a grant to call, for a module as for a person
  • ADR 0132 — where a seat's tools live, and the sentence that named the surface
  • ADR 0035 — three surfaces over one implementation
  • ADR 0034, ADR 0144 — why loopback is the authority boundary
  • 33 — The tools the mesh answers §5, §6 — discovery, and what serves it to an agent
  • 34 — The console — the design this record authorises
  • mesh-tools src/client.ts, src/mcp.ts, src/mesh.ts — the client this makes a module of