From 77813f4613478eabda46222d7d564a049bb43582 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 21:49:26 +0200 Subject: [PATCH] =?UTF-8?q?ADR=200195:=20the=20mesh's=20tools=20are=20foun?= =?UTF-8?q?d=20by=20address,=20not=20announced=20whole;=20research=20021;?= =?UTF-8?q?=20to-be=2034=20=C2=A73a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../00-overview.md | 48 +++++++++ ...re-found-by-address-not-announced-whole.md | 97 +++++++++++++++++++ 02-DECISIONS/README.md | 1 + 03-DESIGN/01-to-be/34-the-console.md | 22 ++++- 4 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 01-RESEARCH/021-finding-a-tool-in-the-mesh/00-overview.md create mode 100644 02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md diff --git a/01-RESEARCH/021-finding-a-tool-in-the-mesh/00-overview.md b/01-RESEARCH/021-finding-a-tool-in-the-mesh/00-overview.md new file mode 100644 index 0000000..3f8d774 --- /dev/null +++ b/01-RESEARCH/021-finding-a-tool-in-the-mesh/00-overview.md @@ -0,0 +1,48 @@ +--- +status: graduated +initiated: 2026-10-03 +touches: [the console, 03-DESIGN/01-to-be/34-the-console.md, the tool runtime, seats, assignments] +became: [02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md, 03-DESIGN/01-to-be/34-the-console.md] +--- + +# 021 — Finding a tool in the mesh + +## What was investigated + +How an agent finds the one tool it needs among everything the mesh answers, and how a call names +exactly what it asks — a role the mesh holds once, a role every machine holds, or one assignment of a +module on one machine — rather than receiving the whole catalogue and a name that can mean several +things. + +## Why + +The operator's observation on 2026-10-03: *Claude should not see all tools at once; they should be +discoverable — and `postgres.list_databases` is wrong, asking one machine's postgres is not asking +another's.* Measured the same day from the console's own answer: + +| | | +|---|---| +| tools announced to every session at its start | 228, in 110 KB | +| names (module or seat prefixes) | 43 | +| node seats' verbs, which require `node` | 22 | +| modules with tools on more than one machine | 4 — fail2ban, nftables (every machine), postgres, mssql (two each) | +| modules reported "not answering", most with no tools and several retired | 47 | + +The two stateful modules on two machines are listed **once**, with `node` optional and *whichever +answers* when it is left out — though their two instances hold different databases. Design 34 §3 says +such a module is listed once per machine; the live console does not do that. The list is taken once +per session, so a tool that arrives later is invisible until the client reconnects. And only Claude +Code's own deferral of long tool lists keeps the 228 from the model's context; another MCP client +would receive them whole. + +## Options + +1. **Keep the flat list; rely on the client to defer it.** Rejected: a property of one client, and it + leaves the ambiguity and the stale list. +2. **One flat tool per assignment** (`ace_postgres_list_databases`). Removes the ambiguity, multiplies + the list, and runs into the API's tool-name limit (letters, digits, `_`, `-`, 64 characters). +3. **A small fixed set of tools that walk the mesh's own structure**, with the full address as an + argument: the mesh's seats; a machine's node seats and assignments; a search; a description; a + call. Chosen — see [ADR 0195](../../02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md). +4. **MCP resources or prompts for discovery.** Clients support them unevenly, and an agent acts + through tools; a resource it cannot be relied on to read is not a discovery path. diff --git a/02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md b/02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md new file mode 100644 index 0000000..eaf7957 --- /dev/null +++ b/02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md @@ -0,0 +1,97 @@ +--- +topic: what runs on it +status: accepted +date: 2026-10-03 +deciders: jochen +reconstructed: false +extends: 02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md +--- + +# 195. The mesh's tools are found by address, not announced whole + +## Context + +The console answers MCP on a machine's loopback ([ADR 0152](0152-the-operators-surface-is-a-module-the-console.md), +[to-be 34](../03-DESIGN/01-to-be/34-the-console.md)) and announces, at a session's start, every tool +the mesh can say it has: 228 on 2026-10-03, 110 KB, taken once. Three things are wrong with that, +measured in research [021](../01-RESEARCH/021-finding-a-tool-in-the-mesh/00-overview.md): + +- **Size.** Only one client's habit of deferring long lists keeps them out of the model's context. +- **Ambiguity.** A module on two machines is listed once, `node` optional, *whichever answers* — for + postgres and mssql, whose instances hold different data, a call that names no machine asks an + arbitrary one. +- **Staleness.** A tool that arrives after the session started is not listed until it reconnects. + +The mesh already has the structure a caller needs: seats held once for the mesh, seats held once per +machine ([ADR 0159](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md)), and +modules assigned to machines, each assignment issued its own subjects +([ADR 0160](0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)). +The operator's direction: *tools are discoverable, in layers, and asking novox's postgres is not asking +ace's.* + +## Considered Options + +1. Keep the flat list and rely on the client. Rejected: the ambiguity and the staleness stay, and it + is one client's behaviour. +2. One tool per assignment, the machine in the name. Rejected: the list multiplies, and an address in + a tool's name meets the API's limit — letters, digits, `_` and `-`, at most 64 characters. +3. **A fixed handful of tools that walk the mesh's structure, the address an argument.** Chosen. +4. MCP resources or prompts. Rejected: unevenly supported, and an agent acts through tools. + +## Decision + +**1. Everything the mesh answers has one address, by the layer it lives in:** + +| layer | address | answered by | +|---|---|---| +| a seat held once for the mesh | `.` | that seat's holder | +| a seat held once per machine | `/.` | that machine's holder | +| a module assigned to a machine | `/.` | that assignment | +| a module whose instances are interchangeable (ADR 0160) | `.` as well | any of them | + +**A call to a module that is not interchangeable names its machine, or is refused** naming the machines +it runs on. "Whichever answers" is no longer an answer for state a machine holds. + +**2. The console announces a fixed set of tools, not the catalogue:** + +- **`mesh_overview`** — the mesh's seats with their verbs, and its machines; +- **`mesh_machine`** — one machine: the node seats it holds and the modules assigned to it, each with + its tools by name; +- **`mesh_search`** — words in, matching addresses out with one line each, across every layer; +- **`mesh_describe`** — one address in, its description and argument schema out; +- **`mesh_call`** — an address and its arguments in, the answer out, with the machine that gave it. + +Each is answered from the mesh when it is asked, so a tool that arrived a minute ago is found without +the client reconnecting. The names are the API's kind of name; addresses never have to be. + +**3. The flat catalogue stays reachable, not announced:** the `mesh` client and a console setting can +still list it whole, for a person reading it or a client that wants it. An agent pointed at the console +sees the five. + +## Consequences + +- An agent spends a call or two finding a tool it does not know, and none on one it does; the context + no longer carries 110 KB it mostly never uses. +- The ambiguity is closed by the address, not by a description asking the agent to remember `node`. +- What got harder: an agent that once saw a tool's schema up front now asks for it. `mesh_describe` and + `mesh_search` answering with the schema of a close match keep that to one call. +- The discovery verbs are the console's; the mesh's own records — seats, machines, assignments — are + the controller's, and the console asks it rather than keeping a copy. + +## How it is checked + +| Rule | Checked by | +|---|---| +| The console announces five tools | the console's test: `tools/list` answers exactly the five | +| An address resolves to one subject per layer | the console's tests: a mesh seat, a node seat, an assignment, an interchangeable module, each called by address over a real bus | +| A non-interchangeable module without a machine is refused, naming its machines | the same tests | +| A tool that arrives after the session started is found | a test registering a module after the console's first answer and finding it by `mesh_search` | +| Live | from a fresh session, *which databases does novox's postgres hold* is answered by novox's postgres, found through the five | + +## References + +- [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) +- Research [021](../01-RESEARCH/021-finding-a-tool-in-the-mesh/00-overview.md) +- [to-be 34](../03-DESIGN/01-to-be/34-the-console.md) diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index 1b643d4..7f064ba 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -293,6 +293,7 @@ python3 00-META/checks/index.py fail if stale - **0188** — [A module's own code is bundles in any language, and a tools bundle speaks MCP to the runtime](0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md) - **0192** — [A tools bundle declares what it is given, and the runtime hands it to that bundle alone](0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md) - **0193** — [Every bundle the runtime serves is launched, and the runtime knows no language](0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md) +- **0195** — [The mesh's tools are found by address, not announced whole](0195-the-meshs-tools-are-found-by-address-not-announced-whole.md) ### How it is built diff --git a/03-DESIGN/01-to-be/34-the-console.md b/03-DESIGN/01-to-be/34-the-console.md index 574c566..c34f93f 100644 --- a/03-DESIGN/01-to-be/34-the-console.md +++ b/03-DESIGN/01-to-be/34-the-console.md @@ -1,9 +1,10 @@ --- layer: to-be -status: implemented +status: designed code: [mesh-catalog, mesh-tools, mesh-controller] -updated: 2026-10-02 +updated: 2026-10-03 decisions: + - 02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md - 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md - 02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md - 02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md @@ -93,6 +94,23 @@ prerequisites are listed in that record. When the seat serves them, the console modules' own, and the person stops opening a shell for the mesh's own questions. Until then the console says so in its handshake. +## 3a. Found by address, not announced whole (2026-10-03) + +*By [ADR 0195](../../02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md); +this section governs where it and §2–§3 disagree.* The console announces five tools — +`mesh_overview`, `mesh_machine`, `mesh_search`, `mesh_describe`, `mesh_call` — and every tool the mesh +answers is reached through them by its address: `.` for a seat held once for the mesh, +`/.` for one held per machine, `/.` for an assignment, and +`.` as well for a module whose instances are interchangeable. A module that is not +interchangeable is called with its machine or refused with the machines it runs on. Each discovery +verb asks the mesh when it is called, so nothing is kept for a session's length; the flat catalogue +stays reachable through the `mesh` client and a setting, unannounced. + +*Found 2026-10-03, measuring for that record:* §3's statement that a stateful module on two machines is +listed once per machine does not hold on the live console — postgres and mssql are listed once, `node` +optional, answered by whichever instance replies. The address replaces that statement rather than +repairing it. + ## 4. Where it runs On whichever machines an operator sits at, by assignment. It is not on the control node by default and -- 2.54.0