Skip to content

RivetKit Actors

RivetKit Actors are Actors written with Rivet’s own library, as Rivet’s docs describe. Alchemy deploys them as they are: it bundles your entry file and runs it on a pool’s nodes, connected to your control plane.

Write the file the way RivetKit documents it. It defines the Actors, registers them, and starts the registry:

src/actors.ts
import { actor, setup } from "rivetkit";
export const counter = actor({
state: { count: 0 },
actions: {
increment: (c, x: number) => {
c.state.count += x;
c.broadcast("newCount", c.state.count);
return c.state.count;
},
},
});
export const registry = setup({ use: { counter } });
registry.start();

registry.start() takes no settings. On the pool’s nodes, Alchemy sets RIVET_ENDPOINT, RIVET_TOKEN, RIVET_NAMESPACE and RIVET_POOL, and RivetKit reads them to join the right pool on the right control plane.

Point a Rivet.DurableObject at the file, and give it a pool:

src/counter.ts
import * as Rivet from "@alchemy.run/rivet";
import * as Effect from "effect/Effect";
import { Counters } from "./cluster.ts";
export const Counter = Rivet.DurableObject("Counter", { main: "./src/actors.ts" }).pipe(
Effect.provide(Counters),
);

With only main and no implementation, the Durable Object is external. Alchemy bundles src/actors.ts and everything it imports, except rivetkit itself, which the pool’s nodes install. It doesn’t change your code.

main is relative to where you run the deploy.

The program runs its own process on every node, so its pool runs nothing else:

src/cluster.ts
import * as RivetDocker from "@alchemy.run/rivet/Docker";
import * as Layer from "effect/Layer";
export const Cluster = RivetDocker.SwarmControlPlane("Rivet");
export const Counters = RivetDocker.SwarmPool("Counters").pipe(Layer.provide(Cluster));

Any pool on any cloud works. A pool given a RivetKit program and another Durable Object fails to deploy with Rivet.PoolProgramConflict.

Callers use RivetKit’s client, as Rivet’s docs describe. The Durable Object’s attributes give them what to connect with: the control plane’s endpoint, the namespace, the pool and the admin token.

alchemy.run.ts
const counter = yield* Counter;
return { endpoint: counter.endpoint, namespace: counter.namespace, pool: counter.pool };

There are no bindings and no yield* calls from other runtimes. For those, write the Actor as a Durable Object.

Next, RivetKit Effect Actors: the same for Rivet’s Effect SDK.