Skip to content

Mounts

A mount is the dot-delimited logical name an actor occupies under an authority root. It is one of the three names every live thing has — and the most commonly conflated. Conflating mount with runtime identity, or with wire prefix, produces real bugs (see CLAUDE.md Rule 11 and the dashboard authority-mismatch incidents recorded in WORKSTREAMS/loose-ends/).

The mount path is the Service identity (Kubernetes-style). It is long-lived. The runtime that hosts it is the pod — replaceable. A DeploymentInstance owns a mount root and its component mounts; the runtime underneath can be swapped, restarted, or co-tenanted with other packages without callers having to know. See P1.40 for the docker-for-actors model in full.

Three names. Do not collapse them.

Source: CLAUDE.md Rule 11 ("Bus addressing"). Quoted: "Three names every live thing has (do not collapse them)."

NameExampleStable acrossOwned by
Mount path (logical / Service identity)system.runtimes, chat.conversationruntime restarts, machine swapssystem.registry
Runtime identity (physical, the pod)RUNTIME-HOST-CONTROL-PLANE, runtime://chat-dev-01this process onlysystem.runtimes
Wire prefix (transport)COM.NIMBLETEC.RICHARD-SANTOMAUROenvironment lifetimeenvironment config

The mount answers "what actor?". The runtime identity answers "which process?". The wire prefix answers "on which NATS namespace?". A request to chat.conversation resolves through system.registry to whichever runtime is currently serving it, and is published on the local wire prefix's inbox subject for that mount.

Note on multi-package runtimes. A runtime is a substrate for actors, not "the runtime for one specific package." Multiple packages with compatible isolation declarations (runtime.isolation: shared) can co-tenant in one runtime. Mount paths stay stable; the runtime identity behind them is an operational concern. See P1.40.

What "mount" means at runtime

When a runtime starts an actor, the runtime tells system.registry to register a logical mount claim mapping the mount to that runtime. Other actors discover the binding by name (registry.resolve {logicalMount: 'chat.conversation'}) and call ops on the mount without knowing which runtime serves it.

The op declarations on system.registry itself (verbatim from projects/matrix-3/packages/system-platform/src/ServiceRegistryActor.ts:175):

typescript
static override accepts = {
  'service.register':   { description: 'Register a service endpoint (name -> mount path)', name: 'string', mount: 'string' },
  'service.resolve':    { description: 'Resolve a service name to its mount path', name: 'string' },
  'service.list':       { description: 'List all registered service endpoints', includeAll: 'boolean?' },
  'service.deregister': { description: 'Remove a service registration', name: 'string' },
  'registry.register':  { description: 'Register a live logical mount claim for a component/provider', logicalMount: 'string', componentId: 'string?', providerRuntimeId: 'string?', localMount: 'string?' },
  'registry.heartbeat': { description: 'Refresh liveness for a logical mount claim/provider', logicalMount: 'string?', componentId: 'string?', providerRuntimeId: 'string?' },
  'registry.unregister':{ description: 'Unregister logical mount claims for a component/provider', logicalMount: 'string?', componentId: 'string?', providerRuntimeId: 'string?' },
  'registry.list':      { description: 'List live logical mount claims', prefix: 'string?', includeStale: 'boolean?' },
  'registry.resolve':   { description: 'Resolve a logical mount to live providers', logicalMount: 'string', includeStale: 'boolean?' },
  'registry.children':  { description: 'List direct logical children under a prefix', prefix: 'string?', includeStale: 'boolean?' },
  'registry.providers': { description: 'List providers for logical mounts', logicalMount: 'string?', includeStale: 'boolean?' },
};

The registry.* ops are the product-level surface; service.* is a compatibility alias for older callers.

Mount path conventions

Mounts use lowercase dot-separated identifiers. Examples that appear in the code today:

system.runtimes
system.registry
system.gateway.http
system.factotum
system.inference.openai
chat
chat.conversation
chat.identity
director
flowpad
host.control
system.devices

Mount names follow these rules:

  • Lowercase, kebab-case allowed inside a token (system.gateway.http, system.inference.openai).
  • Top-level tokens identify the package family (system.*, chat.*, flowpad.*).
  • Children of the same actor share the parent prefix (chat.conversation, chat.identity).
  • The mount path appears on the wire dot-joined to the root and the facet: {root}.chat.conversation.$inbox.

Note: the local-mount path is a NATS subject identifier. It must obey NATS subject rules: no spaces, no leading dot, no trailing dot, no */> in identifiers (those are wildcards).

Singleton vs multi-provider mounts

Some mounts are singletons under an authority root — exactly one process may own them. From WORKSTREAMS/core-and-packaging/MATRIX-AUTHORITY-MODEL.md:

"Singleton authority mounts have one live owner per authority root. system.runtimes, system.registry, system.devices, system.auth, host.control. Two matrix up calls for the same singleton on one Host = silent claim collision (see AGENTS.md § Worker Bootstrap Gotchas)."

Other mounts allow multiple providers (e.g., a workspace mount that several runtimes may serve). The registry's registry.providers op returns the full list. Resolution policy is up to the caller; today most callers take the first live provider.

Mount kind taxonomy

MATRIX-DISCOVERY-METADATA-SPEC.md distinguishes four kinds of "thing at a mount":

ConceptLives where
MountedInstancelive actor at a mount, registered in system.registry, introspectable via $introspect
ServiceEndpointa MountedInstance that exposes headless ops/events
AppRoutea MountedInstance with an HTTP route exposed via system.gateway.http
WorkerEndpointbackground/session worker exposed by a runtime

A mount may be all of these at once: an actor that handles ops, emits events, serves a webapp, and runs a background session.

Local mount vs logical mount

registry.register accepts both logicalMount and localMount. They are usually the same. The distinction matters when a runtime serves a logical name through a local forwarding path:

  • logicalMount: 'system.inference' — the public name everyone calls.
  • localMount: 'system.inference.openai' — the actor inside the provider runtime that actually handles the call.

The registry consumer typically only cares about logicalMount; localMount is metadata used by the gateway and Director for drill-down.

Mount vs URL appName

Mounts are internal addresses. They are not URL segments. The HTTP gateway exposes packages under appName slugs (e.g., /apps/chat/, /apps/director/) which are declared in matrix.json.webapp.appName. The appName grammar is [a-z0-9][a-z0-9-]*; the mount grammar is [a-z0-9.-]+ (dotted). They are different namespaces.

Note: under public Spaces, the URL looks like /<spacePath>/<appName>/, never /<mount>/<op>. Internal mount names are not user-facing. See WORKSTREAMS/core-and-packaging/MATRIX-DISCOVERY-METADATA-SPEC.md for the appName/spacePath grammars.

See also