Files
hq/03-DESIGN/00-as-is/13-the-console.md
T
jschoubben ea0c62355c
mesh/delivery merged: source.main moved to novox/hq@c2f4bb2d485d
mesh/delivery-group group words/catalog merged: every member is merged
mesh/check pass ea0c62355c1c on eec73b8af0d7 | carried from 292232dad226 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
The glossary's word is catalog: replace the spelling catalogue in running prose
2026-10-28 11:27:34 +01:00

9.8 KiB

layer, status, code, updated, decisions
layer status code updated decisions
as-is implemented
mesh-tools
mesh-host
mesh-controller
2026-10-26
ADR 0539
ADR 0360
ADR 0524
ADR 0525
ADR 0152
ADR 0175
ADR 0195
ADR 0197
ADR 0367
ADR 0154

The mesh MCP server, as it runs

The mesh-console container no longer exists. Read 2026-10-17 from mesh-tools (package node-tools), mesh-host and mesh-controller on their forges' main, and from the live mesh through mesh_overview, mesh_machine, mesh_runtimes and mesh_describe. Checked: the catalog (mesh-catalog) carries no module named mesh-console any more — the name survives only in the glossary's retired-words list and in a test fixture — and none of the four nodes answers a module of that name. How the tool runner reaches a node, its count of modules and tools and the controller's verbs were read again on 2026-10-26, after the delivery of release 6 (ADR 0539).

What it is now

The tool runner is one of the mesh's core components, named node-tools beside mesh-controller, mesh-host (the node-engine) and nats (the bus) — the mesh's own repositories' check treats the four the same way, as the core whose health the mesh itself defines rather than a module's own. Since release 6 it reaches each node the way every module does: the controller composes and signs its composition for that node, the assigner writes the node's want of it, and the node-engine's node agent applies it and writes its have. What stays particular to it is the account named <node>.node-tools on the bus (BusNodeTools in mesh-controller) and the node-engine's witness and lease code that puts back a tool runner that does not come up (ComponentNodeTools in mesh-host, ADR 0360 §9).

It still carries a manifest, node-tools/module.json in mesh-tools, and that manifest is where its grants live: invokes names *, so its account may call every tool on the mesh; waits names the five event families mesh_wait may subscribe to (the knowledge base's reviews, mesh-delivery's transitions, the forge's status, the controller's checked event, and the operator channel's decision for the agent module) — the bus refuses a subscription outside this list (ADR 0367 §2); and it provides one provision, the MCP endpoint, at node scope, and listens on the port it serves from, opened only to the machine itself. Its code lives under node-tools/ in the mesh-tools repository (internal/console, internal/announce, internal/bus).

One runtime per node, run by the node-engine as a sibling process, never a container (ADR 0175). It holds the node's one bus credential and may call every tool on the mesh; its account publishes nothing else. It binds 127.0.0.1:<port> and refuses to bind anything else, said before binding rather than after (node-tools/internal/console/http.go, citing ADR 0152): whoever is on the machine owns the mesh there, and nobody else may reach this port. An agent or a person on the node is pointed at http://127.0.0.1:<port>/mcp once; the serverInfo.name the handshake answers is mesh.

What it answers

initialize, notifications/initialized, ping, tools/list, tools/call, over HTTP, one JSON body per request, no session and no event stream (node-tools/internal/console/mcp.go).

It announces seven tools, not five (ADR 0195, extended by ADR 0367): mesh_overview, mesh_machine, mesh_search, mesh_describe, mesh_call, mesh_runtimes and mesh_wait. The handshake's instructions text names all seven and the four address forms. mesh_overview is where the mesh-wide count of modules and tools is read live — a number copied into this document would drift; asked on 2026-10-26 it answered 115 modules and 714 tools across the whole mesh, with two module instances (route-proxy on two of the nodes) named as not answering rather than silently absent.

Addresses, exactly as the handshake states them: <seat>.<verb> for a seat held once for the mesh (mesh-controller.status); <node>/<seat>.<verb> for a seat every machine holds (<node>/node-packet-filter.rules); <node>/<module>.<tool> for a module assigned to one machine; and <module>.<tool> also for a module whose instances are interchangeable. A tool not listed can still be called by address — listing is discovery, calling is the grant — and a module that is not interchangeable is refused with the machines it runs on rather than guessed at.

What exists is what announced itself (ADR 0197). Every node's tool runner answers the NATS services protocol's $SRV.PING, $SRV.INFO and $SRV.STATS, one service per process, naming in each endpoint's metadata which module, seat, scope and machine serves it. mesh_overview and mesh_machine gather one request's answers: at least 750ms so a runtime that only just answered $SRV.PING is still waited for, up to 5s for one that said it is there but has not yet sent its full announcement — a fixed shorter window lost the largest runtime's listing once a live measurement found how slow its round trip over the bus could be. An announcement unchanged since last asked (by its digest) is not re-fetched, which is why mesh_runtimes can show a runtime answering in under 100ms for hundreds of kilobytes of discovery.

mesh_wait

The seventh tool, added by ADR 0367, lets a session wait on the bus for an event instead of polling: a call names one or more granted event families and an optional filter, and either answers the first match or says it timed out. The tool runner's own bus account hears only the families its manifest's waits list names; an event it hears is never taken as the state — a session reads the state from the verb that owns it once woken. It is meant to be called in a loop from a background command, not inline, because one call blocks until it answers or times out.

The mesh's own verbs

These already answer directly, not as a stated future. Beside every assigned module's tools, the tool runner asks the mesh-controller seat for its own verbs the same way (ADR 0154): measured live on 2026-10-26, the mesh-controller runtime is one instance, mesh-wide, and it answers 65 verbs for that one seat (status, assign, modules, artifacts and the rest), of which 20 are refused by name with what replaces them: the delivery verbs release 6 removed (plans, plan, push, deliver among them) and the build verbs of release 4 (build, queue among them). The mesh-controller module answers none of its own — it serves no module tools, only its seat's, exactly as design 33 described while it was still only designed. The 56-to-93 seat-verb counts each node's tool runner answers (below) are a different thing: the tool runner serves the verbs of whichever node-scoped seats are held on that node (node-backup, node-build-go, node-service-manager among them), on behalf of the modules holding them, not the mesh-controller seat's — that one is served by the mesh-controller runtime itself, a separate process, and the tool runner only asks it and relays what it answers.

Where it runs

Every node, not a per-operator assignment. Measured live: all four of the mesh's machines answer a node-tools runtime, each with its own module and tool count (between 294 and 312 tools, 56 to 93 seat verbs, in this reading). This is a fact that shipped past the design it came from: the to-be document still describes it as assigned to "whichever machines an operator sits at"; what actually runs is the opposite — every node the mesh holds carries one, because it is core infrastructure the mesh itself depends on (discovery, the controller's own verbs, every module's tools), not a convenience assigned to a workstation. There is accordingly no assign or unassign verb for it: removing it from a node is not an operation the controller or the catalog exposes.

What this repository's prior text got wrong

The document this replaces described mesh-console as a catalog module served by a container and assigned only to workstations. That module and its container are gone; the manifest is not — it moved to node-tools/module.json, still declares invokes: ["*"], and gained waits and the mcp-endpoint provision, described above. What also moved is which language builds it: the manifest's build.artifacts names a Go binary built from cmd/node-tools, and that is what a node runs. The repository still carries the earlier TypeScript sources (node-tools/src/mesh.ts, http.ts, runtime.ts, announce.ts, broker-nats.ts, client.ts, launch.ts, main.ts, mcp.ts) on main, but the manifest does not build from them. The quirks the prior text recorded from the mesh-console era (a module registered by hand recording its source as a URL; a record-policy module staying listed as not-answering until its rebuilt runtime is pushed) are not re-checked here: they were about the mesh-console container's registration path, which no longer exists.

How it is checked

  • mesh_overview and mesh_machine <node>: the live tool and seat-verb counts, and which modules are named not-answering.
  • mesh_runtimes: which runtimes answered discovery, how long they took, how large their announcement was, and whether it was shortened to fit the bus — read this when an address is said to be missing or on another machine.
  • mesh_describe <address> for one tool's schema; a call through mesh_call reaches the module or seat over the bus and the answer is its own, unshaped.
  • The code: mesh-tools (node-tools/internal/console, internal/announce, internal/bus), mesh-host and mesh-controller's core-component handling of node-tools (witness, lease, bus-user naming). Their unit tests hold the loopback-only refusal, the seven-tool handshake, the discovery gather window and mesh_wait's event-then-read-state rule.
  • knowledge-base.get with kind: decision and number 152, 175, 195, 197, 367 or 154.