Skip to content

Discovery overview

“Discovery” in actor-ts covers two separate concerns:

ConcernMechanismWhen
Cluster bootstrapSeed providersOnce at node startup — how this node finds its peers.
Runtime service lookupReceptionistWhile running — how actors find each other by service key.

They share the name “discovery” because they’re both lookup-based addressing, but the protocols and use cases are distinct.

Seed providers answer: “Which addresses should I try as cluster seeds at join time?”

import { Cluster, KubernetesApiSeedProvider } from 'actor-ts';
const provider = new KubernetesApiSeedProvider({
namespace: 'my-app',
labelSelector: 'app=actor-ts',
containerPort: 2552,
});
const seeds = await provider.lookup();
await Cluster.join(system, { host, port, seeds });

Four providers ship:

ProviderUse
ConfigSeedProviderStatic list from env vars / config.
DnsSeedProviderDNS SRV-record resolution.
KubernetesApiSeedProviderLive pod listing via the K8s API.
AggregateSeedProviderChain multiple providers with fallback.

Pick by your deployment environment:

  • K8sKubernetesApiSeedProvider.
  • VMs with DNS-SDDnsSeedProvider.
  • Static deployment / Docker ComposeConfigSeedProvider.
  • Multi-environment / DR scenariosAggregateSeedProvider.

For most apps, you pick one provider, configure it once, and move on. See Joining and seeds for the full join protocol.

The receptionist answers: “Which actors are registered under this service key, anywhere in the cluster?”

import { Receptionist, ServiceKey } from 'actor-ts';
// On node-A:
const receptionist = system.extension(ReceptionistId).start(cluster);
const key = ServiceKey.of<MyMsg>('my-service');
receptionist.register(key, myActor);
// On node-B:
const refs = await receptionist.find(key); // → ActorRef[] across the cluster

A cluster-wide service registry. Each node hosts a Receptionist actor; registrations are local-authoritative; peers learn about foreign registrations via gossip.

Use the receptionist when:

  • Actor location is dynamic — actors come and go, and consumers shouldn’t hard-code paths.
  • Multiple actors share a service — N workers all register under the same key; consumers see them all.
  • Cross-node discovery is needed — find an actor regardless of which node hosts it.

See Receptionist for the full API.

QuestionTool
How does THIS node find peers to join?Seed provider
How does an actor find another actor at runtime?Receptionist
How does an HTTP load balancer route requests to my pods?K8s Service (not this)
How does a service-mesh proxy discover backends?Service mesh (not this)

The framework’s discovery is for cluster internals. External service discovery (Consul, Eureka, service meshes) is your infrastructure’s concern; actor-ts gets its peer addresses from those at startup but otherwise doesn’t participate.

A typical K8s deployment:

// 1. Seed discovery — how this pod finds peers at startup
const seeds = await new KubernetesApiSeedProvider({
namespace: process.env.K8S_NAMESPACE!,
labelSelector: 'app=actor-ts',
containerPort: 2552,
}).lookup();
await Cluster.join(system, { host, port, seeds });
// 2. Receptionist — for runtime actor lookup
const receptionist = system.extension(ReceptionistId).start(cluster);
// Register this pod's API actor:
receptionist.register(
ServiceKey.of<ApiMsg>('api'),
system.actorOf(Props.create(() => new ApiActor()), 'api'),
);
// Other actors find it:
const apis = await receptionist.find(ServiceKey.of<ApiMsg>('api'));

Seed providers run once; the receptionist runs continuously.