What Are Durable Objects in OpenSEO and How Are They Used for Chat Sessions
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 and 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:
| 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:
// 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 manages step-by-step onboarding flows. It preserves conversation progress and user-provided information across multiple interactions.
Constructor and Storage Access
// 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 statectx.id: Stable identifier tying the session to a specific user's chat
Storing and Retrieving Session Data
// 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 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 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
// 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 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, 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_CHATnamespace: Powers new user flows viaOnboardingChatAgent.tsSAM_CHATnamespace: Drives the AI assistant viaSamChatAgent.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 and 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 and 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 implementation shows how the repository shards state across multiple Durable Objects when necessary.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →