ADR 0195: the mesh's tools are found by address, not announced whole; research 021; to-be 34 §3a

This commit is contained in:
jochen
2026-10-03 21:49:26 +02:00
parent 4ee8e3905d
commit 77813f4613
4 changed files with 166 additions and 2 deletions
@@ -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.
@@ -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 | `<seat>.<verb>` | that seat's holder |
| a seat held once per machine | `<node>/<seat>.<verb>` | that machine's holder |
| a module assigned to a machine | `<node>/<module>.<tool>` | that assignment |
| a module whose instances are interchangeable (ADR 0160) | `<module>.<tool>` 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)
+1
View File
@@ -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) - **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) - **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) - **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 ### How it is built
+20 -2
View File
@@ -1,9 +1,10 @@
--- ---
layer: to-be layer: to-be
status: implemented status: designed
code: [mesh-catalog, mesh-tools, mesh-controller] code: [mesh-catalog, mesh-tools, mesh-controller]
updated: 2026-10-02 updated: 2026-10-03
decisions: 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/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/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 - 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 modules' own, and the person stops opening a shell for the mesh's own questions. Until then the console
says so in its handshake. 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: `<seat>.<verb>` for a seat held once for the mesh,
`<node>/<seat>.<verb>` for one held per machine, `<node>/<module>.<tool>` for an assignment, and
`<module>.<tool>` 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 ## 4. Where it runs
On whichever machines an operator sits at, by assignment. It is not on the control node by default and On whichever machines an operator sits at, by assignment. It is not on the control node by default and