Skip to content

Device vs runtime

The Matrix product splits what computes from what's running on it. A Device is the machine; a runtime is a process on that machine; a mount is a logical actor address. These three layers each have their own authority and their own actor.

The chain

text
Device   (linked compute participant)
  has many
Runtimes (supervised processes)
  each mounts many
Actors   (logical addresses)

Each layer has a single authority surface:

LayerAuthority actorWhat it owns
Devicesystem.deviceslinked-device inventory + presence
Runtimesystem.runtimesphysical runtime/process inventory
Actor mountsystem.registrylogical mount claims / providers

Why the address is logical

When a user opens a chat app at COM.NIMBLETEC.RICHARD-SANTOMAURO.chat.$inbox, the address says nothing about which Device or runtime is currently serving chat. That is a feature, not an oversight. The same logical address can be served by:

  • the user's laptop today, the user's workstation tomorrow
  • a HiveCast-hosted runtime when no Device is online
  • a Devices-page-driven failover from one Device to another
  • an explicit --mount override during local development

If the address embedded the Device, every move would break every link. So the contract is:

text
Logical address (the actor)        != management path (the Device)
chat                                != system.devices.<deviceSlug>.chat
system.inference.openai             != system.devices.<deviceSlug>.system.inference

system.devices.<deviceSlug> may exist as a management projection (e.g. for "open the Devices page for this device"). It must not become the canonical actor namespace for everything running on that Device.

What system.devices exposes about runtimes

A Device record carries a runtimes map. Each entry is an IDeviceRuntimeRecord:

ts
// projects/matrix-3/packages/system/src/SystemDevicesActor.ts:9-25
interface IDeviceRuntimeRecord {
  runtimeId: string;
  runtimeWireRoot?: string;
  runtimeMount?: string;
  controlMount?: string;
  packageRef?: string;
  packageDir?: string;
  environmentName?: string;
  app?: string;
  status: string;
  mountCount?: number;
  localMounts?: readonly string[];
  webapp?: Record<string, unknown>;
  registeredAt: number;
  updatedAt: number;
  metadata: Record<string, unknown>;
}

This is enough to render a Devices page row that shows: which packages this Device is serving, what their logical mounts are, what HTTP webapp surface they expose. It is not enough to invoke the runtime directly — invocation goes through the logical address (registry.resolve then bus call).

How runtime records get into a Device

host.control posts heartbeats. The flow lives in HostControlActor._publishDeviceHeartbeat() and runtimeHeartbeatPayload():

ts
// projects/matrix-3/packages/host-control/src/HostControlActor.ts:300-313
for (const runtime of runtimes) {
  const runtimeId = readString(runtime.runtimeId);
  if (!runtimeId) continue;
  await RequestReply.execute(
    this.context,
    'system.devices',
    'devices.runtimes.register',
    runtimeHeartbeatPayload(link, runtime),
    { timeoutMs: DEVICE_HEARTBEAT_TIMEOUT_MS },
  );
}

For each live runtime under the Host, host.control calls devices.runtimes.register on system.devices with a payload derived from the durable runtime record (<host-home>/runtimes/<runtime-id>/runtime.json) plus the link metadata. system.devices re-keys the runtime under the correct Device record by principalId + authorityRoot scope.

devices.runtimes.list reads them back, scoped to the same principal/Space.

What the runtime record does NOT contain

The IDeviceRuntimeRecord deliberately omits:

  • the runtime's PID, log file path, or supervisor signal handles (those are Host-local and live in host.status.json / supervisor RPC)
  • the package's source path on the Device (that is packageDir, but only when the heartbeat populates it)
  • credentials, tokens, or secret material

Anything secret never travels in system.devices payloads. The heartbeat does carry metadata.cloudUrl and metadata.deviceRegistryRoot, but those are non-secret addressing fields.

host.control filters which runtimes it advertises to the Devices facade:

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;
  });
}

Only runtimes whose runtimeWireRoot matches the Host Link's authorityRoot are advertised. A Host that hosts runtimes for two different authority roots (rare, but possible during dev) only advertises runtimes for the linked root through the heartbeat path.

See also

Source: projects/matrix-3/packages/system/src/SystemDevicesActor.ts (runtime record shape and ops); projects/matrix-3/packages/host-control/src/HostControlActor.ts (heartbeat + filter logic). The architectural rule comes from WORKSTREAMS/product-launch/HIVECAST-DEVICE-ENROLLMENT-SPEC.md § "Ontology Contract".