The runtime serves a list of modules on one credential, naming a bundle that fails to load (hq ADR 0175, to-be 38 WP1) #29

Merged
mesh-admin merged 2 commits from feat/the-operators-machine into main 2026-10-02 19:27:49 +00:00
Contributor

hq to-be 38, WP1 — the runtime serves many modules. ADR 0175: one tool runtime per node, host-side, serving every assigned module's tools and every held seat's verbs.

What changes:

  • serve takes a list: MESH_TOOL_MODULES=<module>=<entrypoint>,… (several entries per module allowed). A bare path stays the one-module form the per-module containers set today, so nothing built today changes behaviour.
  • The bus client follows one membership per served module (follow, membership(module), serving()); handle("<module>.<tool>") serves where that module's membership says, and re-serves when that module's membership is re-issued.
  • Seats come from the memberships (seats[]), so the node's credential carries no claims; a module's own runtime still reads its credential's claims as before.
  • A bundle that throws on import is named in the log and in what tools answers for its module (failed); discovery lists it with the reason instead of as "not answering". The other bundles serve.
  • A tool runs attributed to its module (AsyncLocalStorage), so an event it emits lands on the module's subject, not the runtime's.
  • MESH_OPERATOR_ACCOUNT / MESH_OPERATOR_HOME are read and said once; tools take them from their environment.

What does not change: the SDK, the MCP surface, a module's tool code.

Proof (test/node-runtime.test.ts, against a real NATS with JetStream): three bundles, one broken; five tools and two seat verbs answer on their subjects; tools names the failed bundle; discovery lists it with the reason; a membership re-issued mid-run re-serves without a restart; a tool's event lands on its module's subject. Full suite: 31 passing.

The contract with WP2 (mesh-controller, same branch name) is in the commit message: the node credential's module is node-tools, no claims; the node-tools user must read mesh.assignment.<node>.* and subscribe the union of the served modules' subjects.

**hq to-be 38, WP1 — the runtime serves many modules.** ADR 0175: one tool runtime per node, host-side, serving every assigned module's tools and every held seat's verbs. What changes: - `serve` takes a list: `MESH_TOOL_MODULES=<module>=<entrypoint>,…` (several entries per module allowed). A bare path stays the one-module form the per-module containers set today, so nothing built today changes behaviour. - The bus client follows one membership per served module (`follow`, `membership(module)`, `serving()`); `handle("<module>.<tool>")` serves where that module's membership says, and re-serves when that module's membership is re-issued. - Seats come from the memberships (`seats[]`), so the node's credential carries no claims; a module's own runtime still reads its credential's claims as before. - A bundle that throws on import is named in the log and in what `tools` answers for its module (`failed`); discovery lists it with the reason instead of as "not answering". The other bundles serve. - A tool runs attributed to its module (AsyncLocalStorage), so an event it emits lands on the module's subject, not the runtime's. - `MESH_OPERATOR_ACCOUNT` / `MESH_OPERATOR_HOME` are read and said once; tools take them from their environment. What does not change: the SDK, the MCP surface, a module's tool code. **Proof** (`test/node-runtime.test.ts`, against a real NATS with JetStream): three bundles, one broken; five tools and two seat verbs answer on their subjects; `tools` names the failed bundle; discovery lists it with the reason; a membership re-issued mid-run re-serves without a restart; a tool's event lands on its module's subject. Full suite: 31 passing. The contract with WP2 (mesh-controller, same branch name) is in the commit message: the node credential's `module` is `node-tools`, no claims; the node-tools user must read `mesh.assignment.<node>.*` and subscribe the union of the served modules' subjects.
mesh-admin added 1 commit 2026-10-02 16:23:18 +00:00
One tool runtime per node, host-side, is what the runtime was written to be; the catalogue built a
container per module around it instead. This lets `serve` take a list — MESH_TOOL_MODULES as
<module>=<entrypoint> entries — and do for every assigned module what it did for one: read that
module's membership and follow it, serve its tools where the membership says, serve each held
seat's verbs on the seat's subjects. The seats come from the memberships now, so the node's
credential carries no claims; a module's own runtime still reads its credential's, so nothing
built today changes behaviour. A bare path in MESH_TOOL_MODULES stays the one-module form.

A bundle that throws on import is said in the log and in what `tools` answers for its module
(`failed`), which discovery lists with the reason instead of as "not answering"; the other bundles
serve. The filter that dropped every registration under a name but the one module goes; what
stays is that a registration under a seat's name is served only where some served module claims
the seat. A tool runs attributed to its module, so an event it emits lands on the module's
subject and not the runtime's. MESH_OPERATOR_ACCOUNT and MESH_OPERATOR_HOME are read and said;
tools take them from their environment.

Proven against a real bus: three bundles, one broken; five tools and two seat verbs answer on
their subjects; `tools` names the failed bundle; a membership re-issued mid-run re-serves.
jschoubben added 1 commit 2026-10-02 19:24:22 +00:00
The runtime imported a bundle into its own process, which only JavaScript can be. Now an entrypoint
that is not a plain JavaScript file — or is one marked executable — is started as a child with the
runtime's environment and asked `tools/list` once and `tools/call` per call; what it lists is
registered exactly as an imported bundle's registrations are, a `<seat>.<verb>` name as the seat's
implementation. So a tools bundle may be in any language, and the mesh's part — the subjects, the
seats, the `tools` answer, a failed bundle named — stays in the runtime and is shared by all of
them. A child that exits mid-call tells the caller so and is started again on its next call.

Proven against a real bus beside the three bundles already there: a Python bundle with no SDK at
all answers its tool and its seat verb; a TypeScript bundle written against the protocol and marked
executable is served through the launcher, shortcut off; a bundle told to exit is relaunched.
mesh-admin merged commit 2818f99b17 into main 2026-10-02 19:27:49 +00:00
mesh-admin deleted branch feat/the-operators-machine 2026-10-02 19:27:50 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: novox/mesh-tools#29