89 lines
5.5 KiB
Markdown
89 lines
5.5 KiB
Markdown
---
|
|
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
|