When the AI Gets a Cursor: Electra as a Yjs Participant in Collaborative Docs

repoaicollaborationcrdttypescriptstreamingyjselectric-sqlagent-loopstanstack

Durable Streams' resumable HTTP transport unified across CRDT sync and AI chat session state is a concrete pattern for joelclaw's own streaming and restate worker architecture

Electric SQL built this demo around Durable Streams — a plain HTTP transport layer that makes streams resumable and persistent. The architectural bet is worth paying attention to: use one primitive for both the CRDT document sync layer and the AI chat/agent state, instead of splitting them across WebSockets, SSE, and some bespoke session store. Two very different use cases, one transport.

The collaborative editing piece uses Yjs and ProseMirror for the document model. What’s different is the sync transport — instead of WebSockets, @durable-streams/y-durable-streams syncs the Yjs document over plain HTTP with Durable Streams handling resumability. Same pattern on the chat side: @durable-streams/tanstack-ai-transport makes the TanStack AI session durable across refreshes and reconnects. Multiple tabs, multiple devices — they all resume both document state and chat state from the same stream.

The Electra AI collaborator is where it gets interesting. She joins the document as an actual Yjs participant — cursor state, presence awareness, the works. She doesn’t just respond to queries; she observes the shared document state through tools and streams content into it progressively. Not one final patch at the end, but a streaming insertion that other participants watch in real time. TanStack AI runs the model/tool loop, Zod types the tool contracts, and the OpenAI Responses API is the model backend. The streaming-markdown library interprets the streamed output into structured editor content as it arrives.

The deploy target is Cloudflare Workers via TanStack Start + Nitro’s cloudflare_module preset — app shell, SSR, and /api/chat all run at the edge. The Durable Streams services are external hosted infrastructure the Worker proxies to. Testing is also worth noting: deterministic unit tests with Vitest plus live model-backed evals that actually call OpenAI to verify tool usage and document-editing behavior end-to-end.

Key Ideas

  • Durable Streams as unified transport — one HTTP-native resumable stream handles both Yjs CRDT sync and TanStack AI chat session state, instead of WebSockets + SSE + separate session stores
  • AI agent with Yjs participant presenceElectra has cursor state in the shared document, not just request-response; she is in the room
  • Progressive streaming insertion — the agent writes content into the document in real time as it generates, other participants watch it appear; no final atomic patch
  • Session durability across refreshes — both document state and chat state resume on reconnect via Durable Streams backing; this is the core infrastructure thesis
  • TanStack Start + TanStack Router + TanStack AI as a full-stack React app with file-based routing and typed model/tool loops
  • Zod for typed tool contracts — the agent editing runtime (select, insert, delete, rewrite, format) is validated and explicit
  • Live model-backed evals alongside unit tests — testing actual agent behavior end-to-end against OpenAI, not just mocking the model
  • Cloudflare Workers deploy via Nitro’s cloudflare_module preset — edge-deployed full-stack app with proxied Durable Streams services