Skip to content

Runtime visibility

The Devices facade exposes a Device's runtimes through two ops: devices.runtimes.register and devices.runtimes.list. This is runtime inventory, not runtime control. The same system.devices actor will not start, stop, or reconfigure runtimes — that is host.control's job through system.runtimes.

What gets exposed

devices.runtimes.list({ deviceId, principalId? }) returns:

ts
{
  ok: boolean;
  deviceId: string;
  count: number;
  runtimes: IDeviceRuntimeRecord[];
}

with each runtime shaped as:

ts
// projects/matrix-3/packages/system/src/SystemDevicesActor.ts:9-25
interface IDeviceRuntimeRecord {
  runtimeId: string;
  runtimeWireRoot?: string;
  runtimeMount?: string;
  controlMount?: string;
  packageRef?: string;          // e.g. '@open-matrix/chat'
  packageDir?: string;          // local path to the package source
  environmentName?: string;     // 'dev', 'hivecast', etc.
  app?: string;                 // appName for browser packages
  status: string;               // 'running', 'starting', 'stopped', ...
  mountCount?: number;
  localMounts?: readonly string[];
  webapp?: Record<string, unknown>;  // routePrefix, displayName, icon, ...
  registeredAt: number;
  updatedAt: number;
  metadata: Record<string, unknown>;
}

This is enough to render a Device detail page that shows:

  • which packages this Device is currently serving
  • the logical mounts each runtime publishes (so the user can invoke them)
  • the HTTP webapp surface (so the shell can list "open this app on this Device")
  • environment name + status for at-a-glance health

What does NOT travel through devices.runtimes.list

Deliberately omitted:

  • PID / process metadata. That is Host-local and lives in host.status.json plus per-runtime runtime.json. Cross-Device invocation does not need to know a remote Device's PIDs.
  • Log file paths. Same reasoning. Logs are read via Host Service's per-runtime log files (<host-home>/logs/runtimes/<runtime-id>/), not through the Devices facade.
  • Credentials, tokens, or any secret material. Runtimes that hold per-provider credentials (e.g. inference catalog) keep them in the Device's local system.factotum. Heartbeats never carry secrets.
  • Resource handles. A driver runtime exposing a USB serial device reports its logical mount, not the kernel handle. The handle is meaningful only on the Device.

How runtimes get registered

host.control reads each runtime record and calls devices.runtimes.register once per runtime per heartbeat. The payload is constructed by runtimeHeartbeatPayload() in host-control/src/HostControlActor.ts:415-451. The receiver:

ts
// projects/matrix-3/packages/system/src/SystemDevicesActor.ts:284-328
onDevicesRuntimesRegister(payload: Record<string, unknown>): { … } {
  // 1. validate deviceId + runtimeId
  // 2. check the Device record exists
  // 3. check the payload identity scope matches the Device's identity scope
  // 4. carry forward previous values (mountCount, localMounts, webapp, registeredAt)
  // 5. set status, updatedAt, metadata
}

Step 3 is the same deviceMatchesPayloadMutationScope() rule used by devices.register: a runtime cannot be registered against a Device record whose principalId/spaceId/authorityRoot/hostLinkId/publicNamespace /routeKey differs.

Before host.control calls devices.runtimes.register, it filters out runtimes whose runtimeWireRoot does not match the Host Link's authorityRoot:

ts
// projects/matrix-3/packages/host-control/src/HostControlActor.ts:453-461
function filterRuntimeRecordsForLinkedHost(
  runtimes: readonly IHostRuntimeRecord[],
  link: IResolvedHiveCastHostLink,
): IHostRuntimeRecord[] {
  return runtimes.filter((runtime) => {
    const runtimeWireRoot = readString(runtime.runtimeWireRoot);
    return runtimeWireRoot === link.authorityRoot;
  });
}

Why: a Host can technically host runtimes for multiple authorityRoots during dev (or while migrating between Spaces). Only runtimes attached to the linked Space are advertised to the linked-Device facade.

Multi-runtime dev topology and Devices

The two-runtime dev topology described in CLAUDE.md ("Two-Runtime Dev Topology") runs matrix-web (platform shell) and matrix-edge (Device shell) inside the same Host. Both runtimes appear under the same Device record because they share the Host Link's authorityRoot. From the devices.runtimes.list view they are two separate entries with different runtimeIds and different webapp.routePrefix values (/apps/web/, /apps/edge/).

See also

Source: projects/matrix-3/packages/system/src/SystemDevicesActor.ts (onDevicesRuntimesRegister(), onDevicesRuntimesList(), IDeviceRuntimeRecord); projects/matrix-3/packages/host-control/src/HostControlActor.ts (runtimeHeartbeatPayload(), filterRuntimeRecordsForLinkedHost()).