Skip to main content
Introducing Rivet Workflows

Today we’re releasing Rivet Workflows: a durable execution for TypeScript built in to Rivet Actors.

  • Durable & resilient: Progress persists across crashes, deploys, and restarts. Failed steps retry automatically.
  • Advanced control flow: Sleep, join, race, rollback, human-in-the-loop, and durable loops
  • Durable agents: Build AI agents with tool use, human-in-the-loop, and automatic checkpointing using the AI SDK
  • React integration: Stream workflow progress to your frontend in realtime with useActor
  • Observable: Built-in workflow inspector for debugging every run
  • Permissive open-source: Apache 2.0, runs anywhere: Node.js, Bun, Cloudflare Workers

Show Me The Code

Wrap any multi-step process with workflow() and each step is checkpointed automatically. Crashes, deploys, and restarts pick up where they left off.

Example: Durable Agents

Combine workflows with the AI SDK to build AI agents that survive crashes and pick up exactly where they left off. Each tool call is checkpointed, chat history is stored in SQLite, and the agent loops indefinitely waiting for new prompts via a queue.

import { actor, queue } from "rivetkit";
import { db } from "rivetkit/db";
import { type WorkflowLoopContextOf, workflow } from "rivetkit/workflow";
import { generateText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";

const agent = actor({
  db: db({
    onMigrate: async (db) => {
      await db.execute(`
        CREATE TABLE IF NOT EXISTS messages (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          role TEXT NOT NULL,
          content TEXT NOT NULL
        )
      `);
    },
  }),
  queues: {
    prompts: queue<{ content: string }>(),
  },
  run: workflow(async (ctx) => {
    await ctx.loop("chat-loop", async (loopCtx) => {
      // Wait for next user message
      const message = await loopCtx.queue.next("wait-prompt");

      // Save user message to SQLite
      await loopCtx.step("save-user-message", async () =>
        saveUserMessage(loopCtx, message.body.content),
      );

      // Load full chat history
      const history = await loopCtx.step("load-history", async () => loadHistory(loopCtx));

      // Generate AI response with tool use
      const result = await loopCtx.step("generate", async () => generateReply(history));

      // Save assistant response to SQLite
      await loopCtx.step("save-response", async () =>
        saveAssistantResponse(loopCtx, result.text),
      );
    });
  }),
});

async function saveUserMessage(
  loopCtx: WorkflowLoopContextOf<typeof agent>,
  content: string,
): Promise<void> {
  await loopCtx.db.execute(
    "INSERT INTO messages (role, content) VALUES (?, ?)",
    "user",
    content,
  );
}

async function loadHistory(
  loopCtx: WorkflowLoopContextOf<typeof agent>,
): Promise<Array<{ role: string; content: string }>> {
  return await loopCtx.db.execute<{ role: string; content: string }>(
    "SELECT role, content FROM messages ORDER BY id",
  );
}

async function generateReply(history: Array<{ role: string; content: string }>) {
  return await generateText({
    model: openai("gpt-5"),
    messages: history.map((row) => ({
      role: row.role as "user" | "assistant",
      content: row.content,
    })),
    tools: {
      getWeather: tool({
        description: "Get the weather for a location",
        parameters: z.object({ location: z.string() }),
        execute: async ({ location }) => `72°F in ${location}`,
      }),
    },
    maxSteps: 5,
  });
}

async function saveAssistantResponse(
  loopCtx: WorkflowLoopContextOf<typeof agent>,
  content: string,
): Promise<void> {
  await loopCtx.db.execute(
    "INSERT INTO messages (role, content) VALUES (?, ?)",
    "assistant",
    content,
  );
}

React Integration

Stream workflow progress to your frontend in realtime. Broadcast events on every step and render them with useActor.

No pub/sub service, no polling, no separate WebSocket server. The actor broadcasts events directly to connected clients. The React hook handles connection lifecycle automatically.

Inspector

Every workflow run is fully observable in the built-in inspector. See each step, its status, retries, timing, and output data in a visual DAG, updated in realtime as the workflow executes.

Workflow Inspector

Rivet Actors at Its Core

Rivet Workflows is built directly in to Rivet Actors, a lightweight primitive for stateful workloads. Actors already provide persistent state, queues, and fault tolerance. Workflows add durable replay on top, so you get all of the Rivet Actor primitives for free:

  • State with zero latency: No database round trips. Workflow state lives on the same machine as your compute.
  • Realtime over WebSockets: Broadcast workflow progress to clients with c.broadcast().
  • SQLite per actor: Structured queries alongside workflow state.
  • Scales to zero: Actors hibernate when idle. A workflow sleeping for a week costs nothing.
  • Runs anywhere: Node.js, Bun, Cloudflare Workers, Vercel, or your own infrastructure.

Permissive Open-Source License

Rivet Workflows is licensed under Apache 2.0. Use it in production, self-host it, embed it in commercial products. No restrictions.

Other durable execution engines like Inngest (SSPL) and Restate (BSL 1.1) use restrictive licenses that limit self-hosting and commercial use. We believe durable execution should be infrastructure you own, not a dependency you rent.

Get Started

Rivet Workflows is available today in RivetKit.

npm install rivetkit
import { workflow } from "rivetkit/workflow";
N

Nathan Flurry

Co-founder & CTO