From 65f69d96a930b0e66d21a387f4eb1253d4b66cb8 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 17 Oct 2026 13:35:12 +0200 Subject: [PATCH 1/2] Describe the mesh MCP server as it runs and mark its design implemented (issue 1008) mesh-console is gone: the tool runner (node-tools) is a core component on every node, host-side and never a container (ADR 0175), announcing seven tools by address (ADR 0195, 0367), not the five-tool container the old as-is doc described. The to-be document's unsettled items get owners: the mesh's own verbs already shipped (ADR 0154); identity and per-assignment grants are design 58 milestone 6 (issue 883). --- 03-DESIGN/00-as-is/13-the-console.md | 150 ++++++++++++++++++--------- 03-DESIGN/01-to-be/34-the-console.md | 23 ++-- 2 files changed, 118 insertions(+), 55 deletions(-) diff --git a/03-DESIGN/00-as-is/13-the-console.md b/03-DESIGN/00-as-is/13-the-console.md index d63d191e..44b19bf5 100644 --- a/03-DESIGN/00-as-is/13-the-console.md +++ b/03-DESIGN/00-as-is/13-the-console.md @@ -1,69 +1,125 @@ --- layer: as-is status: implemented -code: [mesh-catalog modules/mesh-console, mesh-tools src/mesh.ts, mesh-tools src/http.ts, mesh-tools src/runtime.ts, mesh-controller internal/broker, mesh-controller cmd/mesh-controller/check.go, mesh-controller cmd/mesh-controller/seatverbs.go] -updated: 2026-10-10 +code: [mesh-tools, mesh-host, mesh-controller] +updated: 2026-10-17 decisions: - "ADR 0152" - - "ADR 0095" - - "ADR 0037" + - "ADR 0175" + - "ADR 0195" + - "ADR 0197" + - "ADR 0367" - "ADR 0154" --- # The mesh MCP server, as it runs -**The mesh's tools reach a person through a module the mesh assigned to their machine.** Since -2026-09-30 a workstation that is a node can be assigned `mesh-console`; the mesh mints a bus account -`.mesh-console`, seals its credential to the machine, and the container binds -`127.0.0.1:` with the port the mesh assigned for the manifest's declared one. An agent on the -machine is pointed at `http://127.0.0.1:/mcp` and sees the mesh's tools; a person uses the same -endpoint. Nothing on the machine holds a credential a person had to carry. +**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 +catalogue (`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. + +## 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. It is not a catalogue module: it has no manifest, is not assigned or unassigned, and +carries no `invokes` grant of its own in a manifest, because there is no manifest. Its code lives +under `node-tools/` in the `mesh-tools` repository (`internal/console`, `internal/announce`, +`internal/bus`), and `mesh-host` and `mesh-controller` supervise, compose and account for it as a +core process rather than as an assignment (`ComponentNodeTools`/`BusNodeTools` in both, the witness +and lease code that restarts and rolls it back, the account named `.node-tools` on the 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:` 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:/mcp` once; the `serverInfo.name` the handshake answers +is `mesh`. ## What it answers -`initialize`, `tools/list`, `tools/call`, over HTTP, one JSON body per request, no session and no event -stream. `tools/list` is what the running modules answered: every tool runner built on or after that day -serves a `tools` verb for its module, and the mesh MCP server asks the catalogue for the roster and each module -for its tools. A module that did not answer is named in the list's `_meta.notAnswering`. On the day it -shipped that was 36 of 51 modules — those that serve no tools at all, and those whose rebuilt runtime the -mesh records rather than rolls out — and 62 tools from the rest. +`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`). -`tools/call` reaches any tool by `.`, listed or not. The mesh MCP server's grant is `*`, so what it -may call is every tool on the mesh; its account may publish nothing else and subscribes nothing. +**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; measured live, the four +nodes' tool runners between them answer for 103 modules and 645 tools this way, with two module +instances (`route-proxy` on two of the nodes) named as not answering rather than silently absent. -No call is refused for want of a person's approval: a tool runner serves every call it is issued, whoever made -it, and cannot tell an agent from the operator (ADR 0259 §6, issue 340). An act waits for the operator's warrant -only where its module asks first, in its own code: the agent module's instruction proposals, and the -controller's settings proposals and the actions it offers on a condition. +**Addresses, exactly as the handshake states them:** `.` for a seat held once for the +mesh (`mesh-controller.status`); `/.` for a seat every machine holds +(`/node-packet-filter.rules`); `/.` for a module assigned to one machine; +and `.` 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-equivalent grant 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 -*Since 2026-09-30 evening (ADR 0154).* -The mesh MCP server asks the `mesh-controller` seat's `tools` verb beside the modules and lists every role's -tools as `.` — `mesh-controller.status`, `mesh-controller.push` and the other ten. A call -to `.` reaches the seat when the prefix is a seat declaring that verb, and the module -otherwise; `seat:.` says so outright. When the controller does not answer, the list -names `mesh-controller (seat)` as not answering and carries the modules' tools regardless. The -`mesh-controller` *module* is always named as not answering: it serves no module tools, only its seat's. +**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, that seat answers between 60 and 93 verbs per node (`status`, `push`, `assign`, `build`, +`artifacts` and the rest), and 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. -## Around it +## Where it runs -- **`invokes`** in a manifest is the grant. It is composed into the bus's user list exactly as a - person's account is; the mesh MCP server is the only module that declares it. -- **`module check …`** on the controller's binary judges a manifest with no mesh: the strict - parse, every per-manifest problem, and the rules between the manifests given. It prints what it cannot - judge without a store rather than refusing. The mesh MCP server's own manifest was the first thing checked - with it, and the whole catalogue passes. -- **The person's client remains.** `operator issue` and `mesh tools|call|mcp` with a credential file - still work, for a machine that is not a node and for a mesh not yet able to assign anything. - `mesh tools --console ` goes through a running mesh MCP server with no credential; it is covered by the - runtime repository's tests and was not exercised on the live mesh. +**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 this amends 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 catalogue exposes. -## What shipped bent +## What this repository's prior text got wrong -- A module registered by hand from the catalogue with `--source --path modules/` records a URL, - not a place on the git seat: `--self` takes the forge path form (`/`), which the - operator did not pass. The rebuild-on-merge matched the URL anyway. -- Modules whose upgrade policy is *record* — the forge among them — answered `tools` only once - something pushed their rebuilt runtime; until then they are listed as not answering while still - callable. That is the policy doing what it says, not a fault of the mesh MCP server. +The document this replaces described `mesh-console` as a catalogue module with an `invokes: ["*"]` +manifest, served by a container, assigned only to workstations, and implemented in TypeScript +(`mesh-tools src/mesh.ts`, `src/http.ts`, `src/runtime.ts`). None of that is true of what runs today: +the container, the manifest, the per-workstation assignment and the TypeScript implementation are +all gone, replaced in substance by ADR 0175's host-side, module-agnostic, Go runtime described +above. The quirks it 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 `: 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
` 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. diff --git a/03-DESIGN/01-to-be/34-the-console.md b/03-DESIGN/01-to-be/34-the-console.md index 08f4a59e..1f246502 100644 --- a/03-DESIGN/01-to-be/34-the-console.md +++ b/03-DESIGN/01-to-be/34-the-console.md @@ -1,11 +1,13 @@ --- layer: to-be -status: designed -code: [mesh-catalog, mesh-tools, mesh-controller] -updated: 2026-10-03 +status: implemented +code: [mesh-tools, mesh-host, mesh-controller] +updated: 2026-10-17 decisions: - "ADR 0197" - "ADR 0195" + - "ADR 0175" + - "ADR 0367" - "ADR 0160" - "ADR 0159" - "ADR 0152" @@ -163,11 +165,16 @@ matched it by URL. ## What this does not settle -- Narrowing a mesh MCP server's grant per assignment. ADR 0046 makes it a setting; nothing reads one yet. -- The mesh's own verbs on the bus. Design 33's third family; this document only says where they appear - once they exist. -- A person's identity behind the mesh MCP server. The mesh sees the mesh MCP server's account; design 15 keeps the - question open. +- **The mesh's own verbs on the bus.** Design 33's third family: these now answer directly, through + the `mesh-controller` seat, under ADR 0154 — settled, not open. [As-is 13](../00-as-is/13-the-console.md) + records it live. +- **Narrowing the tool runner's grant per assignment.** ADR 0046 makes it a setting; nothing reads + one yet. Owned by [design 58](58-agent-work-as-the-meshs-own.md) milestone 6 (issue 883): an + identity asked for each assignment is one of that milestone's parts. +- **A person's identity behind the mesh MCP server.** The mesh still sees only the tool runner's own + account, never the caller's; design 15 raised the question and leaves it open. Owned by the same + milestone (design 58 milestone 6, issue 883): its outcome is that every person, agent and module + becomes an identity with a role, and a call carries which one made it. ## References -- 2.54.0 From f6199fafcee4588ca43cbca24c884db4d25dc211 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 17 Oct 2026 13:39:14 +0200 Subject: [PATCH 2/2] As-is 13: fix five review findings on the mesh MCP server (issue 1008, review round 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit node-tools still has a manifest (invokes *, waits, the mcp-endpoint provision) — describe it in prose instead of saying it has none. The TypeScript sources remain on main; only the built artifact is Go. The mesh-controller seat answers 63 verbs as its own runtime, not 60-93 per node (that is node-tools' own node-scoped seat verbs). Prefer reading mesh_overview live over a count that drifts. An as-is document amends nothing — reworded accordingly. --- 03-DESIGN/00-as-is/13-the-console.md | 67 ++++++++++++++++++---------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/03-DESIGN/00-as-is/13-the-console.md b/03-DESIGN/00-as-is/13-the-console.md index 44b19bf5..d9ee8a98 100644 --- a/03-DESIGN/00-as-is/13-the-console.md +++ b/03-DESIGN/00-as-is/13-the-console.md @@ -26,12 +26,20 @@ module of that name. **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. It is not a catalogue module: it has no manifest, is not assigned or unassigned, and -carries no `invokes` grant of its own in a manifest, because there is no manifest. Its code lives -under `node-tools/` in the `mesh-tools` repository (`internal/console`, `internal/announce`, -`internal/bus`), and `mesh-host` and `mesh-controller` supervise, compose and account for it as a -core process rather than as an assignment (`ComponentNodeTools`/`BusNodeTools` in both, the witness -and lease code that restarts and rolls it back, the account named `.node-tools` on the bus). +module's own. `mesh-host` and `mesh-controller` supervise, compose and account for it as a core +process rather than as an ordinary assignment (`ComponentNodeTools`/`BusNodeTools` in both, the +witness and lease code that restarts and rolls it back, the account named `.node-tools` on +the bus). + +**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 @@ -48,9 +56,11 @@ body per request, no session and no event stream (`node-tools/internal/console/m **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; measured live, the four -nodes' tool runners between them answer for 103 modules and 645 tools this way, with two module -instances (`route-proxy` on two of the nodes) named as not answering rather than silently absent. +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-17 it answered 103 modules and 645 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:** `.` for a seat held once for the mesh (`mesh-controller.status`); `/.` for a seat every machine holds @@ -74,7 +84,7 @@ answering in under 100ms for hundreds of kilobytes of discovery. **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-equivalent grant names; an event it hears is never taken as the state — a session reads the +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. @@ -82,32 +92,41 @@ command, not inline, because one call blocks until it answers or times out. **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, that seat answers between 60 and 93 verbs per node (`status`, `push`, `assign`, `build`, -`artifacts` and the rest), and 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. +live, the mesh-controller runtime is one instance, mesh-wide, and it answers 63 verbs for that one +seat (`status`, `push`, `assign`, `build`, `artifacts` and the rest), and 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-agent`, `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 this amends 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 +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 catalogue exposes. ## What this repository's prior text got wrong -The document this replaces described `mesh-console` as a catalogue module with an `invokes: ["*"]` -manifest, served by a container, assigned only to workstations, and implemented in TypeScript -(`mesh-tools src/mesh.ts`, `src/http.ts`, `src/runtime.ts`). None of that is true of what runs today: -the container, the manifest, the per-workstation assignment and the TypeScript implementation are -all gone, replaced in substance by ADR 0175's host-side, module-agnostic, Go runtime described -above. The quirks it 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. +The document this replaces described `mesh-console` as a catalogue 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 -- 2.54.0