durable-actors 0.7.8

Standalone regional durable-actors control plane, host, and durability runtime
docs.rs failed to build durable-actors-0.7.8
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: durable-actors-0.7.4

Durable Actors

Open source alternative to Durable Objects

If you've been burned by memory limits in Durable Objects, the lack of observability or want to avoid the lock in, Durable Actors is a self-hostable alternative that could work for you!

Use Durable Actors to build persistence and coordination for Software Factories, Multiplayer AI and agent fleets.

Get it working end-2-end in minutes. Define your actor class, generate your type-safe client and easily integrate into your existing tech stack.

Start with a coding agent

Paste this prompt into your coding agent:

Go to https://github.com/TerseAI/durable-actors, follow the README and build a sample project. Get the development server running and ask me where I would like to invoke my actors from.

Give each conversation, document, or agent a TypeScript actor: its saved state survives restarts, and its methods run one at a time by default so concurrent callers can update it safely.

The SDK provides actor classes, type-safe clients, and WebSocket support. The runtime loads actors on demand and persists fields marked @Persisted. Develop locally with one command, then self-host the runtime for production.

For example, a chat actor can keep a conversation across server restarts, or a document actor can coordinate edits from several people and agents without each caller managing database locks.

Languages

TypeScript Python

Local development

Install Node.js 22.19+, pnpm, and Bun 1.3.9+.

Create your actor project in your directory of choice

npx durable-actors init my-actors
cd my-actors
pnpm install
# Or with npm:
npm install
npx durable-actors dev # Run the server locally on your machine

Define an Actor

Define and export actors in your actor project’s src/actors.ts, the default entrypoint loaded by durable-actors dev. For example, a chat history actor:

import { openai } from "@ai-sdk/openai"
import { streamText } from "ai"
import { Actor, Persisted, Reentrant, type ActorSocket } from "durable-actors"

type Member = { name: string }
type Message = { role: "user" | "assistant"; content: string }
type Chat = { messages: Message[]; busy: boolean }

export class ChatHistory extends Actor<Member, string, Chat> {
    @Persisted messages: Message[] = []

    async onConnect(socket: ActorSocket<Member, Chat>) {
        socket.send({ messages: this.messages, busy: false })
    }

    @Reentrant
    async onMessage(socket: ActorSocket<Member, Chat>, text: string) {
        const messages: Message[] = [...this.messages, { role: "user", content: `${socket.metadata.name}: ${text}` }]
        this.broadcast({ messages, busy: true })

        const reply: Message = { role: "assistant", content: "" }
        const result = streamText({ model: openai("gpt-5-mini"), messages })
        for await (const chunk of result.textStream) {
            reply.content += chunk
            this.broadcast({ messages: [...messages, reply], busy: true })
        }

        this.messages = [...messages, reply]
        this.broadcast({ messages: this.messages, busy: false })
    }
}

Connect your Backend

We make it super easy to integrate the actors into your existing tech stack. Just generate the client and you get a fully type safe contract to interact with.

npx durable-actors generate

Now you may call your actor and access the state.

import express from "express"

import { actors } from "../generated/index.js"

export const app = express()

app.post("/api/chat/:room/socket", async (req, res) => {
    const grant = await actors.ChatHistory.prepareWebsocket({
        actorId: req.params.room,
        metadata: { name: String(req.query.name ?? "Guest") }
    })
    res.set("Cache-Control", "no-store").json(grant)
})

Connect the frontend (React)

import { useEffect, useRef, useState } from "react"
import { createRoot } from "react-dom/client"

import type { actors } from "../generated/index.js"

const params = new URLSearchParams(location.search)
const room = params.get("chat") ?? "lobby"
const name = params.get("name") ?? "Guest"

function Chat() {
    const socket = useRef<WebSocket>(null)
    const [chat, setChat] = useState<actors.ChatHistory.Outgoing>({ messages: [], busy: true })

    useEffect(() => {
        let active = true
        async function connect() {
            const response = await fetch(`/api/chat/${encodeURIComponent(room)}/socket?name=${encodeURIComponent(name)}`, { method: "POST" })
            const { websocketUrl } = await response.json()
            if (!active) return
            socket.current = new WebSocket(websocketUrl)
            socket.current.onmessage = event => setChat(JSON.parse(event.data))
        }
        void connect()
        return () => {
            active = false
            socket.current?.close()
        }
    }, [])

    function send(form: FormData) {
        const text = String(form.get("message")).trim()
        if (!text || socket.current?.readyState !== WebSocket.OPEN) return
        socket.current.send(JSON.stringify(text))
        setChat(chat => ({ ...chat, busy: true }))
    }

    return (
        <main>
            <h1>AI chat · {room}</h1>
            <div role="log" aria-label="Messages">
                {chat.messages.map((message, index) => (
                    <article key={index}>
                        <strong>{message.role}</strong>
                        <p>{message.content}</p>
                    </article>
                ))}
            </div>
            <form action={send}>
                <input name="message" aria-label="Message" required disabled={chat.busy} />
                <button disabled={chat.busy}>Send</button>
            </form>
        </main>
    )
}

createRoot(document.getElementById("root")!).render(<Chat />)

For complete sample applications, see AI Chat, Collaborative documents, and Chatroom.

Here's what it looks like in action:

Community

Bug reports, feature requests, documentation fixes, and code contributions are welcome. See the contributing guide for repository setup and checks, and follow our code of conduct.

Use GitHub Issues for bugs, ideas, and questions. Report vulnerabilities privately using our security policy.

Follow development and release notes on GitHub Releases.

License

MIT © 2026 Terse