ADR 0193: every bundle the runtime serves is launched, and the runtime knows no language; design 38 WP4d

This commit is contained in:
jochen
2026-10-03 21:05:03 +02:00
parent 6a1fc71a4c
commit 6d53f9168a
5 changed files with 113 additions and 0 deletions
@@ -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
@@ -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
@@ -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`, `<seat>.<verb>` 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 `<seat>.<verb>`, 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 `<seat>.<verb>` 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
+1
View File
@@ -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