OnboardingChatAgent is a Durable Object that manages a real-time chat loop between the client-side useChat hook and an LLM, persisting conversation history while enforcing at-most-once execution of expensive onboarding tasks via atomic SQL markers. The agent resolves its project and organization context from its instance name, handles streaming tool calls for project updates, and implements retry logic and GDPR erasure for reliable operation across transient failures and data deletion requests. A Durable Object is a Cloudflare Workers primitive that provides a single-instance, globally consistent execution context with built-in SQLite storage — enabling atomic state, coordination, and persistence without external databases.
OnboardingChatAgent.onChatMessage in OnboardingChatAgent.ts calls ProjectRepository.getProjectById(this.name) to resolve the project (and its organizationId) from the Durable Object instance name before processing any message.[1]
The client-side onboarding chat is wired with AI SDK useChat pointed at the onboarding API route
useChat({ api: '/api/onboarding/chat' })
The onboarding chat route uses Vercel AI SDK streamText with an update_project_context tool, returned as a UI message stream
streamText({
model: openrouter(MODEL),
system: seededWithContext,
messages,
tools: { update_project_context }
})
The onboarding seed function uses an atomic SQL admission marker to enforce at-most-once execution before running paid DataForSEO and LLM calls
UPDATE projects
SET onboarding_run_status = 'running'
WHERE id = ? AND onboarding_run_status IS NULL
-- proceed only if one row changed
OnboardingChatAgent persists conversation history automatically in its Durable Object's SQLite storage (this.messages) and caps stored history at 60 messages via maxPersistedMessages = 60.[1] OnboardingChatAgent.persistMessages in OnboardingChatAgent.ts retries up to 3 times with a 50 ms × attempt backoff on transient Durable Object SQLite errors (code 10001 / 'internal error'), re-throwing on non-transient errors or after the final attempt.[1]
OnboardingChatAgent.destroyForErasure in OnboardingChatAgent.ts closes all WebSockets, aborts all in-flight requests, waits up to 5 seconds for stability, then deletes all Durable Object storage — implementing a GDPR account erasure path.[1]
Sources