Skip to main content
Communication & Networking

Communicating Between Actors

Call Rivet Actors from other actors with typed clients, organize actor keys, and coordinate state across isolated instances.

Actors can communicate with each other using the server-side actor client, enabling complex workflows and data sharing between different actor instances.

We recommend reading the clients documentation first. This guide focuses specifically on communication between actors.

Using the Server-Side Actor Client

The server-side actor client allows actors to call other actors within the same registry. Access it via c.client() in your actor context:

If two actors call each other and their return types are inferred from the other actor’s response, you may hit circular type errors (TS2322, TS2722, or c.vars becoming unknown). Fix this by writing explicit return types on those actions.

import { actor, setup } from "rivetkit";
import { db } from "rivetkit/db";

interface Order {
  id: string;
  customerId: string;
  quantity: number;
  amount: number;
}

interface ProcessedOrder extends Order {
  status: string;
  paymentResult: { transactionId: string };
}

const inventory = actor({
  db: db({
    onMigrate: async (db) => {
      await db.execute(
        "CREATE TABLE IF NOT EXISTS stock (id INTEGER PRIMARY KEY CHECK (id = 1), count INTEGER NOT NULL)",
      );
      await db.execute("INSERT OR IGNORE INTO stock (id, count) VALUES (1, 100)");
    },
  }),
  actions: {
    reserveStock: async (c, quantity: number) => {
      await c.db.execute("UPDATE stock SET count = count - ? WHERE id = 1", quantity);
      return { reserved: quantity };
    }
  }
});

const payment = actor({
  actions: {
    processPayment: (c, amount: number) => ({ transactionId: "tx-123" })
  }
});

const orderProcessor = actor({
  // Processed orders are kept in memory for this example
  createVars: () => ({ orders: [] as ProcessedOrder[] }),

  actions: {
    processOrder: async (c, order: Order) => {
      const client = c.client<typeof registry>();

      // Reserve the stock
      const inventoryHandle = client.inventory.getOrCreate(["main"]);
      await inventoryHandle.reserveStock(order.quantity);

      // Process payment through payment actor
      const paymentHandle = client.payment.getOrCreate([order.customerId]);
      const result = await paymentHandle.processPayment(order.amount);

      // Record the processed order
      c.vars.orders.push({ ...order, status: "completed", paymentResult: result });

      return { success: true, orderId: order.id };
    }
  }
});

const registry = setup({ use: { inventory, payment, orderProcessor } });

Use Cases and Patterns

Actor Orchestration

Use a coordinator actor to manage complex workflows:

import { actor, setup } from "rivetkit";

interface WorkflowResult {
  workflowId: string;
  result: { finalized: boolean };
  completedAt: number;
}

const dataProcessor = actor({
  actions: {
    initialize: (c, workflowId: string) => ({ workflowId, data: "initialized" })
  }
});

const validator = actor({
  actions: {
    validate: (c, data: { workflowId: string; data: string }) => ({ valid: true, data })
  }
});

const finalizer = actor({
  actions: {
    finalize: (c, validationResult: { valid: boolean }) => ({ finalized: validationResult.valid })
  }
});

const workflowActor = actor({
  // Completed workflows are kept in memory for this example
  createVars: () => ({ workflows: [] as WorkflowResult[] }),

  actions: {
    executeWorkflow: async (c, workflowId: string) => {
      const client = c.client<typeof registry>();

      // Step 1: Initialize data
      const dataProcessorHandle = client.dataProcessor.getOrCreate(["main"]);
      const data = await dataProcessorHandle.initialize(workflowId);

      // Step 2: Process through multiple actors
      const validatorHandle = client.validator.getOrCreate(["main"]);
      const validationResult = await validatorHandle.validate(data);

      // Step 3: Finalize
      const finalizerHandle = client.finalizer.getOrCreate(["main"]);
      const result = await finalizerHandle.finalize(validationResult);

      c.vars.workflows.push({ workflowId, result, completedAt: Date.now() });
      return result;
    }
  }
});

const registry = setup({ use: { dataProcessor, validator, finalizer, workflowActor } });

Data Aggregation

Collect data from multiple actors:

import { actor, setup } from "rivetkit";

interface Stats {
  count: number;
  total: number;
}

interface Report {
  id: string;
  type: string;
  data: { users: Stats; orders: Stats; system: Stats };
  generatedAt: number;
}

const userMetrics = actor({
  actions: {
    getStats: (c): Stats => ({ count: 100, total: 500 })
  }
});

const orderMetrics = actor({
  actions: {
    getStats: (c): Stats => ({ count: 50, total: 10000 })
  }
});

const systemMetrics = actor({
  actions: {
    getStats: (c): Stats => ({ count: 5, total: 99 })
  }
});

const analyticsActor = actor({
  // Generated reports are kept in memory for this example
  createVars: () => ({ reports: [] as Report[] }),

  actions: {
    generateReport: async (c, reportType: string) => {
      const client = c.client<typeof registry>();

      // Collect data from multiple sources
      const userMetricsHandle = client.userMetrics.getOrCreate(["main"]);
      const orderMetricsHandle = client.orderMetrics.getOrCreate(["main"]);
      const systemMetricsHandle = client.systemMetrics.getOrCreate(["main"]);

      const [users, orders, system] = await Promise.all([
        userMetricsHandle.getStats(),
        orderMetricsHandle.getStats(),
        systemMetricsHandle.getStats()
      ]);

      const report: Report = {
        id: crypto.randomUUID(),
        type: reportType,
        data: { users, orders, system },
        generatedAt: Date.now()
      };

      c.vars.reports.push(report);
      return report;
    }
  }
});

const registry = setup({ use: { userMetrics, orderMetrics, systemMetrics, analyticsActor } });

Event-Driven Architecture

Use connections to listen for events from other actors:

import { actor, setup } from "rivetkit";

interface User {
  id: string;
  name: string;
}

interface Order {
  id: string;
  amount: number;
}

interface AuditLog {
  event: string;
  data: User | Order;
  timestamp: number;
}

const userActor = actor({
  actions: {
    createUser: (c, name: string) => {
      const user = { id: crypto.randomUUID(), name };
      c.broadcast("userCreated", user);
      return user;
    }
  }
});

const orderActor = actor({
  actions: {
    completeOrder: (c, amount: number) => {
      const order = { id: crypto.randomUUID(), amount };
      c.broadcast("orderCompleted", order);
      return order;
    }
  }
});

const auditLogActor = actor({
  // Audit logs are kept in memory for this example
  createVars: () => ({ logs: [] as AuditLog[] }),

  actions: {
    startAuditing: async (c) => {
      const client = c.client<typeof registry>();

      // Connect to multiple actors to listen for events
      const userActorConn = client.userActor.getOrCreate(["main"]).connect();
      const orderActorConn = client.orderActor.getOrCreate(["main"]).connect();

      // Listen for user events
      userActorConn.on("userCreated", (user: User) => {
        c.vars.logs.push({
          event: "userCreated",
          data: user,
          timestamp: Date.now()
        });
      });

      // Listen for order events
      orderActorConn.on("orderCompleted", (order: Order) => {
        c.vars.logs.push({
          event: "orderCompleted",
          data: order,
          timestamp: Date.now()
        });
      });

      return { status: "auditing started" };
    }
  }
});

const registry = setup({ use: { userActor, orderActor, auditLogActor } });

Batch Operations

Process multiple items in parallel:

import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

interface Item {
  type: string;
  data: string;
}

const processor = actor({
  actions: {
    process: (c, item: Item) => ({ processed: true, item })
  }
});

const registry = setup({ use: { processor } });
const client = createClient<typeof registry>("http://localhost:6420");

// Process items in parallel
const items: Item[] = [
  { type: "typeA", data: "data1" },
  { type: "typeB", data: "data2" }
];

const results = await Promise.all(
  items.map(item => client.processor.getOrCreate([item.type]).process(item))
);
Edit this page Last updated November 10, 2026