Two records shared 0187 (issue 155's collision); the branch landing last renumbers, and this is it. Only the number changes.
128 lines
8.4 KiB
Markdown
128 lines
8.4 KiB
Markdown
---
|
|
topic: what runs on it
|
|
status: accepted
|
|
date: 2026-10-02
|
|
deciders: jochen
|
|
reconstructed: false
|
|
extends: 02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md
|
|
---
|
|
|
|
# 188. A module's own code is bundles in any language, and a tools bundle speaks MCP to the runtime
|
|
|
|
## Context
|
|
|
|
[ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md) put one
|
|
tool runtime on every node and said a module brings its tools as a bundle. The runtime that exists
|
|
is written in TypeScript and brings a bundle to life by **importing it into its own process**, which
|
|
only JavaScript can be. The SDK ([ADR 0039](0039-what-the-sdk-holds-and-refuses.md)) is one
|
|
TypeScript package. The builder knows three toolchains — TypeScript, Go, Python — and every one of
|
|
the 35 catalogue modules with tools wraps them in a container on the runtime's TypeScript image.
|
|
Nothing in the records says a module's code may be written in anything else, and nothing refuses a
|
|
module that wraps its own code in an image to get around that.
|
|
|
|
The operator's direction, stated on 2026-10-02 and repeated: *the SDK is the most important part;
|
|
we must not limit developers; tools can be written in any possible language — Rust, C, Go,
|
|
JavaScript. A service in Go or Rust as a systemd unit must be possible too. One module can deliver
|
|
all kinds of bundles: one for its tools, one for a seat's implementation, one for a daemon. Support
|
|
the bare minimum first, as a skeleton; a full implementation comes when the work requires it.*
|
|
|
|
Measured against that: the `bundle` artifact kind already names a language and the `process`
|
|
resource already runs a command from an unpacked bundle as a unit the host writes
|
|
([ADR 0150](0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md)), so a Go
|
|
daemon as a native service is possible today and one module in the catalogue does it. What is not
|
|
possible is a tool in any language but one, and what is not written is that any of this is the
|
|
rule.
|
|
|
|
## Considered Options
|
|
|
|
1. **One SDK, one language, as now.** Rejected: it limits who can write a module to one
|
|
ecosystem, which the operator declines, and it is what made every module's tools a container
|
|
on one image.
|
|
2. **A full bus client per language.** Each SDK speaks the bus itself; the runtime only
|
|
supervises. Rejected: a transport in every SDK is what ADR 0039 refuses, and a bus change
|
|
would then rebuild every module in every language — the cascade, multiplied.
|
|
3. **A tools bundle is a process the runtime launches and speaks a small local protocol to,
|
|
and that protocol is MCP over stdio.** Chosen. The runtime already speaks MCP outward (the
|
|
console); speaking it inward to a child process is the same vocabulary. Every language that
|
|
has an MCP server library can write a tools bundle today with no mesh SDK at all, and the
|
|
mesh's own SDK for a language is a thin convenience over it. The transport stays in the
|
|
runtime, so a bus change rebuilds nothing.
|
|
4. **A protocol of the mesh's own design.** Rejected: a second way to describe a tool, its
|
|
schema and its call, inventing what MCP already settled, for no gain.
|
|
|
|
## Decision
|
|
|
|
**1. A module's own code is bundles, in any language the mesh has a toolchain for, and never an
|
|
image.** A `bundle` names its language and what it is for. Images are for third-party software a
|
|
module installs — a database, a forge — never for code the module wrote. One module may declare
|
|
several bundles: its tools, its implementation of a seat's verbs, a daemon, a step. Each is built
|
|
alone and delivered alone, as [ADR 0156](0156-an-artifact-is-what-a-build-produces-and-the-store-is-named-for-its-scope.md)
|
|
already has it.
|
|
|
|
**2. A bundle the runtime serves is a process that speaks MCP over stdio.** The node's runtime
|
|
launches it as the bundle names it — an interpreter and a file, or a binary — with the runtime's
|
|
environment, asks `tools/list`, and answers each call on the bus by `tools/call`. A tool whose name
|
|
is `<seat>.<verb>` is the module's implementation of that seat's verb; any other name is the
|
|
module's own tool. Everything the runtime does with what it is told — subjects from the membership,
|
|
a held seat's verbs, the `tools` answer, a bundle that fails named and the others serving — stays as
|
|
[ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md) and
|
|
[ADR 0160](0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)
|
|
have it. A TypeScript bundle may still be imported into the runtime's own process; that is a
|
|
shortcut over the same contract, not a second contract, and a TypeScript bundle written against
|
|
the protocol is served the same way as any other.
|
|
|
|
**3. A bundle that is a service is a `process`**, run by the host as a unit, in whatever language it
|
|
is compiled from, exactly as the host's own bundle already is. Nothing new is decided here; it is
|
|
said so that it is the rule and not an example.
|
|
|
|
**4. One thin SDK per language, and the test of ADR 0039 applies to each.** An SDK for a language
|
|
holds the MCP-over-stdio loop, the tool-definition type and the few primitives a module's code
|
|
needs; it holds no transport, no module's client and nothing volatile. Where a language has a
|
|
sound MCP library, the SDK wraps it rather than re-implementing it. The languages are those that
|
|
make sense to write a module in; the first set is TypeScript, Go, Python, Rust and C, and the set
|
|
grows when a module needs one, not before.
|
|
|
|
**5. Skeleton first.** Each piece — a toolchain, a launcher, an SDK — exists at the bare minimum
|
|
that lets one bundle in that language be built, delivered and answer one tool on the live mesh.
|
|
Anything beyond that is added when a module needs it. A skeleton that is not proven by one bundle
|
|
answering is not a skeleton; it is a promise.
|
|
|
|
## Consequences
|
|
|
|
- The runtime gains a launcher beside its loader. The loader, the memberships, the seats and the
|
|
failure handling built for ADR 0175 stand; the launcher is the one new step.
|
|
- The builder gains a toolchain per language, each at the skeleton: compile, pack, name the
|
|
entrypoint. Rust and C are new; a language that compiles to a binary says its operating system
|
|
as a Go bundle already does.
|
|
- An existing MCP server in any language is already a valid tools bundle. What the mesh adds is
|
|
the subjects, the seats and the memberships around it.
|
|
- The gate [to-be 38](../03-DESIGN/01-to-be/38-building-the-operators-machine.md) WP2 adds —
|
|
refusing a tools container built on the runtime's image — widens: a module whose own code is
|
|
an image artifact is refused at registration, naming this record.
|
|
- What got harder: a tools bundle is now a process per module on the node rather than code in
|
|
one process, so the runtime supervises children and restarts one that dies. The one-process
|
|
shape ADR 0175 counted on for the TypeScript shortcut remains available for it.
|
|
- ADR 0039's "what the SDK holds" now reads per language; its refusals are unchanged and are the
|
|
reason option 2 was rejected.
|
|
|
|
## How it is checked
|
|
|
|
| Rule | Checked by |
|
|
|---|---|
|
|
| A module's own code is never an image | the catalogue's registration check: a manifest with a `bundle` kind of own code *and* an image artifact built from the module's own directory is refused, naming this record |
|
|
| A tools bundle in a language other than TypeScript answers on the bus | the runtime's tests: a bundle written against the protocol in a second language, launched, its tool called over a real bus |
|
|
| A TypeScript bundle written against the protocol is served like any other | the same tests, with the TypeScript shortcut off |
|
|
| Each SDK is thin | each SDK's own README states what it holds under ADR 0039's test, and its size is in the mesh's records |
|
|
| Live | a tool in a compiled language answers from the node's runtime on one machine |
|
|
|
|
## References
|
|
|
|
- [ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md),
|
|
[ADR 0039](0039-what-the-sdk-holds-and-refuses.md),
|
|
[ADR 0150](0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md),
|
|
[ADR 0156](0156-an-artifact-is-what-a-build-produces-and-the-store-is-named-for-its-scope.md),
|
|
[ADR 0160](0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)
|
|
- [To-be 38](../03-DESIGN/01-to-be/38-building-the-operators-machine.md) — the work packages this
|
|
record widens
|
|
- The Model Context Protocol's stdio transport — the local protocol a tools bundle speaks
|