Files
hq/03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md
T
jschoubben d57196102d ADR 0132: a seat carries the tools its holder must serve
A role's tools belong to the role, not to whichever module holds it today: the seat declares them
with their schemas, serving them is a condition of occupying the seat, and what the mesh can do
becomes a read of its own records rather than a question nothing answers. A module keeps its own
tools — the same module may run without the seat, and then only its own name is true.

Design 33 follows: the three families, addressing a node-scoped seat, discovery, and what serves
this to an agent.
2026-09-28 10:16:59 +02:00

8.0 KiB

layer, status, code, updated, decisions
layer status code updated decisions
to-be designed
2026-09-28
02-DECISIONS/0132-a-seat-carries-the-tools-its-holder-must-serve.md
02-DECISIONS/0129-a-seat-carries-the-protocol-of-its-role.md
02-DECISIONS/0095-the-control-plane-is-the-way-to-ask-a-module.md

33 — The tools the mesh answers

An agent can call the mesh's tools and cannot find out what they are. Both halves were measured on the live mesh on 2026-09-28: a client holding an operator credential connected to the bus, asked a module for its repositories and got them; the same client's request for the tool list found nothing serving it. The transport works, the account model works, the adapter that speaks the agent protocol works. What is missing is the mesh being able to say what it can do.

This design is the answer to that question, and it has three families in it, because a tool belongs to whoever is accountable for answering it.

1. Three families, and why the split is not arbitrary

Family Addressed to Where the definition lives Example
A role's tools the seat: mesh.seat.<seat>.tool.<verb> the seat's protocol, in the mesh's records ask the forge to list its repositories
A module's tools the module: mesh.mod.<module>.tool.<name> that module's code ask this gitea for gitea_list_repos
The mesh's own verbs the mesh-controller seat the seat's protocol, as above status, push, build, assign

The split follows accountability. A role is something the mesh guarantees exactly one holder of, so what the role answers is the mesh's to define and a holder's to implement (ADR 0132). A module's own tools are nobody's business but the module's, and their definitions live where they are implemented, because a copy kept anywhere else drifts from the code that answers.

The mesh's own verbs are the third family only in where they come from, not in kind: the control plane holds a seat like anything else, and its tools are that seat's. This is what keeps them addressable while the control plane is being replaced, which is the moment they are most needed.

Both names for one capability is deliberate and bounded to this. A forge holding the git seat answers the role's list_repos and its own gitea_list_repos, because the same module may run without the seat — a second instance, kept for one purpose — and then only the second name is true. The caller chooses which question it is asking. Nothing else in the mesh gets two names.

2. What a seat's tool is

A verb, what it does, and the schema of its arguments and its answer. A name alone is not callable by something that has never seen the mesh before, which is the whole population this surface exists for.

The protocol a seat carries today is three lists of bare verbs (ADR 0129), and it must widen to carry the rest. Two constraints on that widening:

  • It lives in the mesh's records, not in the control plane's binary. Today a seat's protocol comes from compiled defaults, merged in as a row is read, because the seat rows never gained the columns. Discovery that reads a binary is discovery that disagrees with the mesh the moment the two are on different versions.
  • The schema is stated in the form an agent protocol already uses, so nothing translates between a seat's idea of an argument and the caller's. A translation layer would be a second definition of what a tool is.

3. Holding a seat means serving its tools

A module may not occupy a seat unless it serves every verb that seat declares. This joins the conditions of holding that already exist — providing what the seat delivers, being assigned at the seat's scope — and is refused the same way: at registration and at handover, naming the verbs that are missing rather than the fact that something is.

A module knows which seats it claims, so knowing which tools it must serve is not a discovery problem for the module: the seat says, the module implements, and anything beyond that is its own.

4. Addressing a node-scoped seat

A seat's subject is flat today — mesh.seat.<seat>.<kind>.<verb> — which is correct for a seat the mesh has one holder of and wrong for the six node-scoped seats, where one subject would reach every machine's holder and the holders' queue group would hand the call to whichever answered first. A node-scoped seat's tool therefore carries the node it is asked of. Nothing about a mesh-scoped seat changes.

5. Discovery

What a role answers is a read. The seats and their protocols are records, so the list is a query against the mesh's own store: no call to a module in the path, nothing that has to be running, and an answer that stays true while a holder is restarting or being replaced.

What a module answers comes from the module. Its definitions live in its code, so it is asked, and the answer is as available as the module is — which is the right coupling for a tool that only exists while that module does.

A caller therefore gets one list assembled from two sources, and the difference is visible in it: a role's tool names a seat, a module's names a module. An agent that wants to survive a holder being replaced binds to the first.

6. What serves this to an agent

A module the mesh assigns to the machine where the agent runs, holding a credential the mesh minted, with authority derived from what it may call — not a program started by hand with a credential printed to a terminal. The adapter itself already exists and is thin by design; what changes is that it stops being something a person carries and becomes something the mesh runs, on a node, like everything else.

An agent's authority can then be role-shaped: the forge's tools, rather than a list of module-specific names that changes the day the forge is replaced.

7. Versioning

A seat's tools are an interface and change like one. Additive within a version. A change that would break a caller takes the version token the subject already has room for, and the two versions run side by side until nothing is bound to the old one.

How it is checked

  • A holder missing a verb cannot take the seat. One test per condition of holding, as the existing conditions have, and the live refusal names the verbs.
  • A verb nobody declared is a subject nobody may use. The bus grants are derived from the seat's protocol already, and the golden composition of the user list is what keeps that honest: a holder is granted exactly the seat's verbs, a user of the seat exactly the publish side.
  • Discovery needs no running module. The test for a role's tools reads records and asserts the answer equals what the seats declare — if it needed a module up, it would not be a read.
  • Two nodes holding one node-scoped seat derive two addresses. Checked by the same test as the rest of the subject table.

What this does not settle

  • Which verbs each seat should serve. That is a decision per seat, and the reason to do it slowly: a seat's tools bind every future holder.
  • Whether a module's own tool definitions should also be recorded when a build resolves its manifest. There is an argument for it — the mesh could then answer for a module that is down — and an argument against, which is that a recorded copy of a live definition is a copy that can be wrong.

References

  • ADR 0132 — the decision this designs
  • ADR 0129 — a seat carries the protocol of its role
  • ADR 0095 — a tool call passes one process where an audit belongs
  • 26-the-seats.md — what a seat is, how it is held and handed over
  • 25-the-bus-on-nats.md §7 — a person's account, their inbox, and the adapter