diff --git a/02-DECISIONS/0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md b/02-DECISIONS/0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md index 9df22ab..249f356 100644 --- a/02-DECISIONS/0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md +++ b/02-DECISIONS/0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md @@ -87,6 +87,10 @@ that lets one bundle in that language be built, delivered and answer one tool on 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. +> **The mechanism changed — 2026-10-03, by ADR 0193.** §2's allowance that a TypeScript bundle may +> be imported into the runtime's own process is withdrawn: every served bundle is launched, and the +> build makes each served entrypoint executable. The rest of §2 stands. + ## Consequences - The runtime gains a launcher beside its loader. The loader, the memberships, the seats and the diff --git a/02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md b/02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md index 6b7bcaf..a20c6b0 100644 --- a/02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md +++ b/02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md @@ -88,6 +88,10 @@ tools answering from the runtime, and the registration gate of [to-be 38](../03-DESIGN/01-to-be/38-building-the-operators-machine.md) WP2 then refuses the container shape for every module, as ADR 0188 already provides. +> **The mechanism changed — 2026-10-03, by ADR 0193.** Decision 3's imported path — the environment +> handed to an imported bundle's contributor — has nothing left to do: every served bundle is +> launched, and a launched bundle's environment is its own. The decision stands. + ## Consequences - The manifest gains one field on one artifact kind; the composer gains one more thing to resolve diff --git a/02-DECISIONS/0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md b/02-DECISIONS/0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md new file mode 100644 index 0000000..01f7dbd --- /dev/null +++ b/02-DECISIONS/0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md @@ -0,0 +1,88 @@ +--- +topic: what runs on it +status: accepted +date: 2026-10-03 +deciders: jochen +reconstructed: false +extends: 02-DECISIONS/0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md +--- + +# 193. Every bundle the runtime serves is launched, and the runtime knows no language + +## Context + +[ADR 0188](0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md) +§2 made a served bundle a process that speaks MCP over stdio, and kept one exception: a TypeScript +bundle may be imported into the runtime's own process, "a shortcut over the same contract". Every +module's tools today take the shortcut, and it is where the day's defects came from: + +- [Issue 209](../04-ISSUES/209-a-bundles-own-sdk-copy-registers-into-a-registry-the-runtime-never-reads/00-report.md): + an imported bundle's own copy of the SDK registered into a registry the runtime never read; fixed + by a resolve hook that redirects every bundle's SDK import to the runtime's copy. +- [ADR 0192](0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md): + thirty modules' environments in one process had to be kept apart by an SDK change and runtime + bookkeeping, where a process of its own has an environment of its own by construction. +- One faulty module can block or crash every other module's tools on its node. + +And the shortcut ties the runtime to Node.js: only a JavaScript runtime can import JavaScript. The +operator's direction on 2026-10-03: *a module's tools are written in any language and the builder +builds them; the runtime runs them all and announces them; it should be fully language agnostic, +and node-tools can be rewritten in Go.* + +## Considered Options + +1. **Keep the shortcut.** Rejected: it is the cause of the three defects above, and it pins the + runtime's language. +2. **Launch every served bundle; the runtime knows how to start each language** (`node` for a + `.js`, exec for a binary). Rejected: the runtime would hold a table of interpreters, and a + runtime in Go would carry Node.js's knowledge for nothing. +3. **Launch every served bundle, and the build makes each served entrypoint executable.** Chosen. + A compiled language's binary is executable already; for an interpreted one the toolchain writes + a launcher beside the entrypoint — for TypeScript, a file that imports the entrypoint and serves + what it registered over stdio, using the bundle's own SDK. The runtime execs what it is given. + +## Decision + +**1. Every bundle the node's runtime serves is a child process speaking MCP over stdio.** The +in-process shortcut of ADR 0188 §2 is withdrawn. Everything else ADR 0188 §2 says — `tools/list`, +`tools/call`, `.` naming a seat's verb, the runtime serving each on the bus — stands. + +**2. The runtime knows no language.** It is given an executable per served entrypoint and starts +it, with that bundle's environment ([ADR 0192](0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md)) +over its own words, and the module it serves it as. What makes an entrypoint executable is the +toolchain's business: a binary is one; an interpreted language's toolchain writes a launcher. + +**3. A launched bundle is told the module it serves as**, so that what it lists unprefixed is that +module's own tools and a seat's verbs are always `.`, whichever it registered first. + +**4. The runtime may be written in any language.** Nothing it does needs it to share a language +with a bundle; the mesh's runtime moves to Go, against this contract, once the contract is proven +in the runtime that exists. + +## Consequences + +- A process per served module per node. On the busiest machine that is a score of small children + where there was one process; a Go bundle costs a fraction of a Node.js one. +- The SDK resolve hook (issue 209) and the per-registration environment hand-off (ADR 0192 §3, + imported bundles) have nothing left to do and go; a launched bundle's environment is its own. +- A module's TypeScript tool code does not change: it registers as before, and the generated + launcher serves what it registered. +- A bundle that crashes or hangs takes only its own tools down, and is started again on its next + call, as ADR 0188 already provides for a launched bundle. + +## How it is checked + +| Rule | Checked by | +|---|---| +| Every served bundle is launched | the runtime's tests: a TypeScript bundle and a bundle in a second language, both launched, both answering over a real bus; a non-executable entrypoint is refused by name | +| The runtime knows no language | the runtime holds no interpreter: it execs the path it is given (code review; the Go runtime has no Node.js dependency at all) | +| A TypeScript served entrypoint is executable | the builder's test: a TypeScript bundle's served entrypoint has a launcher beside it, mode 0755 | +| A seat's verbs are named as the seat's whichever registers first | the SDK's test: a bundle registering its seat first and its own tools second lists `.` and its own tools unprefixed | +| Live | the packet filter's and intrusion prevention's seat verbs and the moved tools answer from launched bundles on every machine | + +## References + +- [ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md), + [ADR 0188](0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md), + [ADR 0192](0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md) +- [to-be 38](../03-DESIGN/01-to-be/38-building-the-operators-machine.md) WP4d diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index 5fa1bfd..1b643d4 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -292,6 +292,7 @@ python3 00-META/checks/index.py fail if stale - **0183** — [The Anthropic licence manager is a module holding a seat; it hands each node's agent its token over the bus, sealed; the controller and the host have no part](0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.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) +- **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) ### How it is built diff --git a/03-DESIGN/01-to-be/38-building-the-operators-machine.md b/03-DESIGN/01-to-be/38-building-the-operators-machine.md index c02d8be..ff07a3f 100644 --- a/03-DESIGN/01-to-be/38-building-the-operators-machine.md +++ b/03-DESIGN/01-to-be/38-building-the-operators-machine.md @@ -13,6 +13,7 @@ decisions: - 02-DECISIONS/0149-the-live-mesh-is-the-test-bed.md - 02-DECISIONS/0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md - 02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md + - 02-DECISIONS/0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md --- # 38. Building the operator's machine @@ -261,6 +262,21 @@ Two traps met on the way: a tools bundle whose module declares no `tools` list m the composer delivers it nowhere while the build reports success; and a module whose builds are pinned to an old commit is left out of a merge's plan and must be built from `main` by hand. +## WP4d — Every served bundle is launched; the runtime in Go + +*mesh-sdk, mesh-controller, mesh-tools. [ADR 0193](../../02-DECISIONS/0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md).* + +**In order.** The SDK's stdio loop serves what a bundle registered under the module it is told it +serves as. The builder writes, beside every TypeScript entrypoint, an executable launcher that +imports it and serves what it registered; the composer names the launcher where it named the +entrypoint. The runtime launches every served entrypoint and imports none; the resolve hook and the +per-registration environment go. Proven live on all four machines. Then the runtime is rewritten in +Go against the same contract — the bus, the memberships and seats, the launcher, the console's MCP +over HTTP — and replaces the TypeScript one, proven the same way. + +**Proof.** The tests ADR 0193 names; live, every moved module's tools and both node seats answer from +launched bundles on every machine, and then do again from the Go runtime. + ## WP4c — The module's own long-running code moves *Not yet broken down.* Twenty-three containers carry code that is not a tool: event handlers,