Files
hq/02-DECISIONS/0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md
T

5.5 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
what runs on it accepted 2026-10-03 jochen false 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 §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: 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: 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) 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