Files
hq/02-DECISIONS/0198-a-modules-long-running-code-is-launched-by-the-node-runtime-and-reaches-the-bus-through-it.md
T

80 lines
4.9 KiB
Markdown

---
topic: what runs on it
status: accepted
date: 2026-10-03
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md
---
# 198. A module's long-running code is launched by the node's runtime, and reaches the bus through it
## Context
[ADR 0193](0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md) made
every tools bundle a child the node's runtime launches, speaking MCP over stdio, and gave the channel
one bus verb: a tool's emit, published by the runtime as the module. Twenty-three modules still run the
rest of their own code — event handlers, provisioners, a preparation step, three mains — in a container
on the runtime's image, because that code needs what a container gave it: a bus connection that can
*subscribe*, and its module's words. Research [022](../01-RESEARCH/022-where-a-modules-long-running-code-runs/00-overview.md)
measured what it uses. [ADR 0188](0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md)
§1 says a module's own code is never an image.
## Considered Options
1. **The runtime launches it and is its bus.** Chosen.
2. A process per module with its own bus client and credential. Rejected for the reasons ADR 0188
rejected it for tools: a transport in every language's SDK, a credential per module on disk, and a
bus change rebuilding every module.
3. Keep the containers for it. Rejected: ADR 0188's rule stays broken for most of the catalogue.
## Decision
**1. A module's long-running code is a bundle the node's runtime launches and supervises,** exactly as
its tools are: an executable entrypoint, given the runtime's words and its module's
([ADR 0192](0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md)),
started at the runtime's start and again when it exits. A bundle may serve tools, run long, or both.
**2. The runtime is its bus.** The stdio channel carries, beside MCP, the mesh's verbs a module's code
uses: `mesh/publish` (ADR 0193), **`mesh/subscribe`** — the runtime binds that module's durable
consumer, as the module's own runtime did, and delivers each event to the child as a `mesh/event`
request, acknowledging it on the bus only when the child has answered — and **`mesh/ask`**, a tool
call made on the module's behalf. The runtime's account is granted what each module it carries
consumes, and the consumer keeps the module's name, so no event is lost or replayed in the move.
**3. A preparation step is a run-once process the host runs before the runtime starts the module,**
with its module's words and no bus — what it already was.
**4. What a container reached by its network is reached on the machine.** A service by its published
port (`${port:…}`) on loopback; a backend's command-line client as a package of the machine's system,
or, where the system has none, the backend's own driver inside the bundle.
## Consequences
- The per-module containers go, and with them the runtime image as a way module code runs; ADR 0188's
registration rule can then refuse a module's own image without exception.
- One bus connection per machine carries every module's events; a module's handler is a function of
the events it is handed, in any language, with no bus client of its own.
- What got harder: the runtime holds every carried module's consumer and must not acknowledge an event
before the child has handled it — a child that dies mid-event leaves it unacknowledged, and it is
delivered again. The runtime's grants widen to what its modules consume.
- Two modules need code before they can move: their backends' clients exist on no machine's system,
so they talk to the backend through a driver instead.
## How it is checked
| Rule | Checked by |
|---|---|
| A subscribed event reaches the child and is acknowledged only after it answered | the runtime's test over a real bus: a child that answers is acknowledged once; one that dies mid-event is delivered again |
| The module's consumer keeps its name | the composer's test: the durable consumer the runtime binds is the one the module's own runtime bound |
| No module's own code is an image | the catalogue's registration check, without exception, once the last container has moved |
| Live | every moved module's provisioner and handlers act, on their machines, from the node's runtime; `docker ps` shows no runtime-image container on any machine |
## References
- [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),
[ADR 0193](0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md)
- Research [022](../01-RESEARCH/022-where-a-modules-long-running-code-runs/00-overview.md)
- [to-be 38](../03-DESIGN/01-to-be/38-building-the-operators-machine.md) WP4c