Appearance
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:
| Surface | Op family | Purpose |
|---|---|---|
| Compatibility | service.register, service.resolve, service.list, service.deregister | Older 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.providers | The 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:
| Event | When |
|---|---|
service.registered | A service.* registration succeeds. |
service.deregistered | A service.* deregistration succeeds. |
registry.registered | A registry.register claim succeeds (new claim only — re-registration of the same component is silent). |
registry.heartbeat | A registry.heartbeat is accepted. |
registry.unregistered | A 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.packagesand 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
- System catalog — the read projection over this
- Registry vs catalog — the disambiguation table
- Discovery metadata spec
Source:
projects/matrix-3/packages/system-platform/src/ServiceRegistryActor.ts.