# What Are Durable Objects in OpenSEO and How Are They Used for Chat Sessions

> Explore Durable Objects in OpenSEO, Cloudflare Workers constructs offering persistent, stateful storage for chat sessions. Learn how they use SQLite and ordered processing for reliable communication.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: deep-dive
- Published: 2026-09-02

---

**Durable Objects in OpenSEO are Cloudflare Workers constructs that provide persistent, stateful storage for chat sessions through SQLite-backed key/value storage and guaranteed ordered request processing.**

OpenSEO uses Cloudflare Durable Objects to power its conversational features, enabling reliable session state management across serverless deployments. This article examines how the `every-app/open-seo` repository implements Durable Objects for both user onboarding flows and AI-assisted chat, based on the source code in [`src/server/features/onboarding/OnboardingChatAgent.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/onboarding/OnboardingChatAgent.ts) and [`src/server/features/sam/SamChatAgent.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/sam/SamChatAgent.ts).

## What Are Cloudflare Durable Objects

A **Durable Object** combines a single JavaScript/TypeScript class instance with persistent storage, running on Cloudflare's edge network. Each Durable Object has a unique identifier and guarantees:

- **Single-instance execution**: Only one instance runs globally at any time, ensuring serialized request processing
- **Persistent SQLite storage**: Data survives worker restarts, deployments, and scaling events
- **Stateful sessions**: Maintains in-memory state between requests for the same object ID

In OpenSEO, Durable Objects replace traditional session stores by colocating compute and storage at the edge, eliminating latency to external databases.

## Durable Object Namespaces in OpenSEO

The repository declares two Durable Object namespaces in [`src/env.d.ts`](https://github.com/every-app/open-seo/blob/main/src/env.d.ts):

| Namespace | Purpose |
|-----------|---------|
| **`ONBOARDING_CHAT`** | Manages new user onboarding conversations |
| **`SAM_CHAT`** | Powers the "SAM" in-app AI assistant |

Workers access these namespaces through the `env` binding:

```typescript
// Retrieve a Durable Object instance by ID
const id = env.ONBOARDING_CHAT.idFromName(chatSessionId);
const chatAgent = env.ONBOARDING_CHAT.get(id);

```

## OnboardingChatAgent: Guiding New Users

The `OnboardingChatAgent` class in [`src/server/features/onboarding/OnboardingChatAgent.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/onboarding/OnboardingChatAgent.ts) manages step-by-step onboarding flows. It preserves conversation progress and user-provided information across multiple interactions.

### Constructor and Storage Access

```typescript
// src/server/features/onboarding/OnboardingChatAgent.ts
export class OnboardingChatAgent extends DurableObject {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.ctx = ctx;  // Provides storage and ID access
  }
}

```

The `DurableObjectState` passed to the constructor supplies:

- **`ctx.storage`**: SQLite-backed key/value store for persisting messages, user preferences, and flow state
- **`ctx.id`**: Stable identifier tying the session to a specific user's chat

### Storing and Retrieving Session Data

```typescript
// src/server/features/onboarding/OnboardingChatAgent.ts
async storeMessage(message: string) {
  await this.ctx.storage.put("lastMessage", message);
}

async getHistory() {
  return await this.ctx.storage.list();
}

```

Because Durable Objects serialize all inbound requests, these read-modify-write operations execute without race conditions. The `storage.put()` and `storage.list()` methods persist data to SQLite, ensuring the conversation survives even if the worker is redeployed.

### Project Integration

The `ProjectRepository` in [`src/server/features/projects/repositories/ProjectRepository.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/projects/repositories/ProjectRepository.ts) demonstrates how onboarding chat Durable Objects are tied to project IDs, creating a reliable link between a user's project and their onboarding session state.

## SamChatAgent: The AI Assistant

The `SamChatAgent` in [`src/server/features/sam/SamChatAgent.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/sam/SamChatAgent.ts) extends the same Durable Object pattern for the SAM AI assistant, adding support for **durable facts**—user-provided information that persists across the entire chat session.

### Adding Persistent Context

```typescript
// src/server/features/sam/SamChatAgent.ts
export class SamChatAgent extends DurableObject {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.ctx = ctx;
  }

  async addDurableFact(key: string, value: string) {
    await this.ctx.storage.put(`fact:${key}`, value);
  }

  async getDurableFacts() {
    const all = await this.ctx.storage.list();
    return Object.fromEntries(
      [...all.entries()].filter(([k]) => k.startsWith("fact:"))
    );
  }
}

```

Durable facts allow users to teach SAM about their preferences, business details, or SEO requirements once, then reference that context throughout the conversation. The [`SamSessionRepository.ts`](https://github.com/every-app/open-seo/blob/main/SamSessionRepository.ts) file handles creation and retrieval of SAM chat Durable Object instances for specific sessions.

## Security and Connection Handling

OpenSEO's Workers authorize WebSocket connections before they reach the Durable Object. This ensures only legitimate clients can read or write session state. The Durable Object ID itself acts as a capability—knowledge of the ID grants access to that specific session's data.

## Related Pattern: AuditScratchpad

The repository extends Durable Objects beyond chat in [`src/server/features/audit/AuditScratchpad.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/audit/AuditScratchpad.ts), which uses the same storage pattern to maintain per-audit crawl state. This demonstrates how OpenSEO consistently applies Durable Objects for any feature requiring reliable, long-lived state at the edge.

## Summary

- **Durable Objects provide stateful compute**: Single-instance classes with SQLite persistence, ideal for chat sessions that must maintain context
- **`ONBOARDING_CHAT` namespace**: Powers new user flows via [`OnboardingChatAgent.ts`](https://github.com/every-app/open-seo/blob/main/OnboardingChatAgent.ts)
- **`SAM_CHAT` namespace**: Drives the AI assistant via [`SamChatAgent.ts`](https://github.com/every-app/open-seo/blob/main/SamChatAgent.ts), supporting durable facts for persistent user context
- **Guaranteed ordering**: Serialized request processing eliminates race conditions in conversation state management
- **Edge-native storage**: Data survives deployments without external database dependencies

## Frequently Asked Questions

### What is the difference between Durable Object storage and a traditional database?

Durable Object storage is SQLite-backed and colocated with the compute instance on Cloudflare's edge network. Unlike traditional databases that require network round-trips, `ctx.storage` accesses data locally within the same Durable Object, reducing latency. However, each Durable Object's storage is isolated to that specific instance—there is no global query capability across all objects.

### How does OpenSEO ensure chat sessions don't lose data during deployments?

Durable Objects automatically persist `ctx.storage` data to SQLite. According to the [`OnboardingChatAgent.ts`](https://github.com/every-app/open-seo/blob/main/OnboardingChatAgent.ts) and [`SamChatAgent.ts`](https://github.com/every-app/open-seo/blob/main/SamChatAgent.ts) implementations, all conversation state is written through `storage.put()` calls, which are durable across worker restarts. The Durable Object runtime handles replication, so session data survives code deployments and infrastructure changes without application-level logic.

### Can multiple users access the same Durable Object chat session?

Each Durable Object instance is identified by a unique ID derived from the chat session identifier. In OpenSEO, the [`SamSessionRepository.ts`](https://github.com/every-app/open-seo/blob/main/SamSessionRepository.ts) and [`ProjectRepository.ts`](https://github.com/every-app/open-seo/blob/main/ProjectRepository.ts) files control how these IDs are generated and distributed. While multiple clients could theoretically connect to the same Durable Object ID, the Worker layer typically enforces authorization—ensuring only the legitimate session owner accesses that object's state.

### What limits apply to Durable Object storage in OpenSEO?

Cloudflare Durable Objects have storage limits (currently 1GB per object) and request throughput constraints. OpenSEO's chat agents store lightweight conversation data and key facts, staying well within these bounds. For larger audit operations, the separate [`AuditScratchpad.ts`](https://github.com/every-app/open-seo/blob/main/AuditScratchpad.ts) implementation shows how the repository shards state across multiple Durable Objects when necessary.