Files
hq/04-ISSUES/182-a-tool-call-reaches-whichever-instance-answers-first/00-report.md
T

3.5 KiB

status, opened, located-in, fixed-by, amended-design
status opened located-in fixed-by amended-design
resolved 2026-10-01
mesh-tools src/broker-nats.ts (one subject
one queue group per module)
mesh-tools src/mcp.ts (no way to name a machine)
mesh-tools src/runtime.ts (a claimed seat's verbs served by nobody)
mesh-controller internal/broker/nats.go (the grant for a tool named one subject)
mesh-tools PR (feat/a-tool-call-names-the-machine) and mesh-controller PR (same branch) — see ADR 0159; the store seat's verbs and postgres's tools follow in the catalogue
03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md
03-DESIGN/01-to-be/34-the-console.md

182 — A tool call reaches whichever instance answers first, and a claimed seat's verbs are served by nobody

What was observed

Asked how to list the databases of the store on one machine, the mesh had no answer. A module's tools are served on one subject per module, mesh.mod.<module>.tool.<name>, and every instance of the module joins one queue group on it, so a call to the database engine's tool while it runs on two machines reaches whichever answered first, and the answer does not say which. There is no way to ask the instance on one machine. The console lists the tool once and offers no machine.

And the seat half was missing too. Design 33 §3 says holding a seat means serving its tools, and ADR 0154 built that for the controller's own seat alone. A module that holds a seat — the database engine on the control node holding mesh-store — served none of the seat's verbs, because no seat but the controller's declares any and no runtime knew which seats its module claimed.

Why this is here

Both are the same omission: the tool surface was built as if every module ran on one machine and held no seat. A queue group is the right default for a stateless module answering anywhere, and the wrong only choice for a module whose instances are different things — two stores with different databases. The architecture had the distinction: a module is a thing that runs on machines, a seat is a role one of them holds. The tool surface did not carry it.

Resolved, 2026-10-01

ADR 0159. Every instance serves its module's subject twice: in the queue group as before, and with its own machine as the subject's last token. <module>.<tool>@<node> reaches one machine's instance; the console lists node on every module tool and puts it in the subject, never in the module's arguments; every answer carries the machine that gave it, and the console appends it as its own line. The grant for a tool covers both subjects.

A holder's runtime serves its seat's verbs: the credential the mesh writes names the seats the module claims and the verbs each promises, the runtime serves each verb with the module's tool of the same name on the seat's own subject, flat for a mesh seat and with the machine for a node-scoped one, and the bus admits the subscription only where the module holds the seat. The store seat's first verbs and the database engine's tools for them are the catalogue's next step, recorded in the decision.

How it is checked: against a real bus, a module on two machines answers each by name and says who answered when unnamed, and a claimant answers a seat's verb on the seat's subject; the console lists node on a module's tool and not on a seat's; the grant for a tool covers both subjects; and, live, the store's databases listed from one named machine through the console.