Appearance
SDK vs runtime host
MatrixRuntime is a class. Host Service is a process. They look like they do the same thing because the Host Service ultimately constructs a MatrixRuntime for each runtime it supervises. They are not the same job, and you choose between them at the right level of the stack.
Three layers
┌────────────────────────────────────────────────────────────────┐
│ Host Service (one OS process; supervises N runtimes) │
│ ├── runtime A (system, auto-start) │
│ ├── runtime B (host-control, auto-start) │
│ ├── runtime C (system-gateway-http, auto-start) │
│ ├── runtime D (matrix-web --serve, auto-allocated port) │
│ └── runtime E (your-package --serve) │
│ │ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ Runner (per runtime) │ │
│ │ loads package.json │ │
│ │ loads matrix.json │ │
│ │ constructs: │ │
│ │ ┌──────────────────┐ │ │
│ │ │ MatrixRuntime │ │ ← THE SDK │
│ │ │ + transport │ │ │
│ │ │ + actors │ │ │
│ │ └──────────────────┘ │ │
│ └────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘- Host Service is the supervision layer. It owns runtime records under
<host-home>/runtimes/, restarts crashed runtimes, manages the bundled NATS sibling, exposes the gateway HTTP port, and knows about installs, links, ports, and devices. - Runner is per-runtime. It reads one package and one environment, builds a
MatrixRuntime, attaches a transport, mounts actors. Browsers are runners too: every tab connects to NATS and instantiates its ownMatrixRuntimewith aBrowserDomStrategy. MatrixRuntimeis the SDK class. It is the unit of actor system — a transport, a root context, a set of mounted actors, plus a topology supervisor for root-level actors.
When to use MatrixRuntime directly
Construct MatrixRuntime yourself when:
- Writing a unit or integration test. The runtime accepts a stub transport and a stub host. You can mount fake actors, send envelopes, and assert on what came back.
- Embedding Matrix in another process that already has its own supervisor. For example, a CI tool that spins up a runtime inside a Node test process and tears it down per test.
- Building a bespoke runner. If your needs do not fit the Host Service model (e.g. you want to mount actors inside an Electron renderer with its own lifecycle), you can wrap
MatrixRuntimedirectly.
The minimal wiring (projects/matrix-3/packages/core/src/runtime/MatrixRuntime.ts:127-201):
ts
import { MatrixRuntime, MatrixActor, InMemoryTransport, InMemoryBroker } from '@open-matrix/core';
const broker = new InMemoryBroker();
const transport = new InMemoryTransport(broker, { name: 'my-runtime' });
const runtime = new MatrixRuntime({ transport });
class MyActor extends MatrixActor {
static accepts = {
'foo.bar': {
description: 'Trivial example op',
schema: {},
returns: { ok: { type: 'boolean', description: 'Always true' } },
},
};
async onFooBar() { return { ok: true }; }
}
await runtime.create(MyActor, 'my-actor');If you do not pass a transport, MatrixRuntime creates its own InMemoryTransport (MatrixRuntime.ts:146-152). That is fine for tests and demos; it is not what you want in production because nothing else can reach actors mounted on a private in-memory broker.
When to use Host Service
Use the Host Service path for anything that ships:
- Production Matrix packages. A package becomes runnable by being supervised under Host Service. The runner reads
matrix.json, allocates ports through the gateway, registers itself withsystem.runtimes, and is restarted on crash according to itsrestartpolicy. - Local development.
hivecast installfollowed byhivecast startbrings up a full local Host with bundled NATS, system actors, gateway, Matrix Web, and Matrix Edge.matrix run <package> --serveadds your package to that running Host as another supervised runtime. - Browser-served packages.
--serveflips a runtime from headless to a static HTTP server registered behind the gateway. Browsers load the resultingindex.htmland bootstrap their ownMatrixRuntimeagainst the same NATS bus.
The dev loop is documented in the project root CLAUDE.md ("Host Service / Docker-NPM parity dev loop"). The relevant commands:
bash
node projects/matrix-3/packages/hivecast/bin/hivecast.mjs install --home /tmp/matrix-home
node projects/matrix-3/packages/hivecast/bin/hivecast.mjs start --home /tmp/matrix-home
pnpm --filter @matrix/mx-cli exec matrix run <package-or-dir> --serve --home /tmp/matrix-homeWhat the runner does on your behalf
When a runtime is started by Host Service, the runner does roughly this for you (paraphrasing the per-package wiring; the canonical implementation lives in @open-matrix/host-service and @matrix/mx-cli):
- Read
matrix.jsonto find the runtime entry, the actorcomponents, and the config contract. - Read environment variables (per the
envPrefix) and any service config (persystem.config). - Open a transport against the local NATS sibling using the install's authority root.
- Construct
MatrixRuntime({ transport, ... }). - For every entry in
components, dynamicallyimport()the actor class and callruntime.createSupervised(ActorClass, mount, props). - Register the runtime with
system.runtimesand (if--serve) bind a static HTTP listener throughsystem-gateway-http. - Heartbeat
system.devicesso the Devices page reflects reality.
You don't write this glue per-package. You write actors and a matrix.json, and the SDK + Host Service take care of the rest.
Rule of thumb
Tip: If a human or operator will install/start your code, use Host Service. If only your test runner will start it, construct
MatrixRuntimedirectly with anInMemoryTransport.
The retired matrixd daemon path is intentionally disabled and must not be revived for product work. See the project root CLAUDE.md § "THE RULES" #1.
See also
- SDK package layout
- Runtime entrypoints
- Package manifest
- In-memory transport — used when you construct
MatrixRuntimeyourself.
Source:
projects/matrix-3/packages/core/src/runtime/MatrixRuntime.ts:78-201(constructor, default transport), rootCLAUDE.md"Host Service / Docker-NPM parity dev loop".