Skip to content

Live actor registry

system.registry is a different kind of registry. It does not list installable artifacts and it does not list metadata. It tracks which logical mounts are currently claimed by which providers, with heartbeat-based liveness.

This page is a read-only overview. The implementation is owned by projects/matrix-3/packages/system-platform/src/ServiceRegistryActor.ts and mounted as system.registry per projects/matrix-3/packages/system/matrix.json.

Concept

Every Matrix package that exposes ops mounts those ops at a logical mount path (for example chat, system.devices, director). When the package's runtime starts, it tells system.registry "I am providing <mount>, my provider id is <runtimeId>." The registry stores that claim and emits registry.registered. While the runtime is alive, it sends registry.heartbeat periodically; if it stops heartbeating beyond heartbeatTtlMs (default 30,000ms), the claim is considered stale and is filtered out of the default list view.

Two namespaces, one actor

ServiceRegistryActor exposes two parallel naming surfaces:

SurfaceOp familyPurpose
Compatibilityservice.register, service.resolve, service.list, service.deregisterOlder flat name → mount mapping. Kept for backward compatibility with code that pre-dates logical mount claims.
Product (current)registry.register, registry.heartbeat, registry.unregister, registry.list, registry.resolve, registry.children, registry.providersThe current product-level registry that supports liveness, multiple providers per mount, and namespace navigation.

New code uses the registry.* family. The service.* family remains for existing callers that have not migrated.

What a claim looks like

typescript
interface IRegistryEntry {
  logicalMount: string;           // 'system.devices', 'chat', etc.
  componentId: string;            // unique per claim instance
  componentType: string;          // 'SystemDevicesActor', etc.
  authorityRoot: string;          // bus root, e.g. 'COM.NIMBLETEC.RICHARD-SANTOMAURO'
  scope: string;                  // 'authority', 'session', 'device'
  providerRuntimeId: string;      // which runtime is serving
  runtimeWireRoot: string;        // bus prefix for that runtime
  localMount: string;             // mount path relative to the runtime
  packageName: string;            // '@open-matrix/system-devices', etc.
  metadata: Record<string, unknown>;
  registeredAt: number;
  lastHeartbeatAt: number;
  heartbeatTtlMs: number;
}

A single logicalMount can have multiple providers. Resolving a mount returns all live (non-stale) providers; the consumer (typically system.catalog) chooses how to pick one.

Liveness

Default heartbeat TTL is ServiceRegistryActor.DEFAULT_HEARTBEAT_TTL_MS = 30_000 (30 seconds). A claim becomes stale when now - lastHeartbeatAt > heartbeatTtlMs.

Stale claims are excluded from registry.list by default. They reappear if the caller passes includeStale: true. The actor does not delete stale claims proactively; they're filtered at read time. This keeps stop/start transitions non-destructive — a quick restart picks up the same componentId and re-becomes live without losing claim history.

Emissions

Per ServiceRegistryActor.emits:

EventWhen
service.registeredA service.* registration succeeds.
service.deregisteredA service.* deregistration succeeds.
registry.registeredA registry.register claim succeeds (new claim only — re-registration of the same component is silent).
registry.heartbeatA registry.heartbeat is accepted.
registry.unregisteredA registry.unregister removes one or more claims.

These are bus events, suitable for live UIs (Director, dashboards) that want to follow registration churn without polling.

Singleton rule

CLAUDE.md enforces that system.registry has one live owner per authority root. A second ServiceRegistryActor mounting at system.registry under the same authority root is a bug — the singleton invariant exists so consumers have a single deterministic source for "what is mounted." Cross-Host / cross-authority federation is allowed; the singleton constraint is per authority root, not per network.

What it does NOT track

  • Installable packages (those live in system.packages and the artifact registry).
  • Webapp routes (those live in system.gateway.http).
  • Per-runtime process state (that lives in system.runtimes).
  • HTTP endpoints (the gateway resolves those).

The catalog (system.catalog) joins system.registry, system.runtimes, and system.gateway.http into a unified read view; do not collapse them at the write layer.

See also

Source: projects/matrix-3/packages/system-platform/src/ServiceRegistryActor.ts.