{"slug": "we-replaced-livekit-with-cloudflare-workers-for-ai-voice-tutoring-and-kept-the", "title": "We Replaced LiveKit with Cloudflare Workers for AI Voice Tutoring — and Kept the Same Brain", "summary": "Katush, an AI tutor for Cameroon secondary students, replaced LiveKit with Cloudflare Workers and Durable Objects for its voice tutoring transport, keeping the same backend and BYOK stack (Deepgram, Gemini Flash Lite, Cartesia Sonic-2). The switch cut transport costs, not AI costs, with variable COGS still around $0.019/min. The team documented production issues such as PCM audio format mismatches and billing state races, and shared fixes including streaming TTS per sentence and releasing stale call rows.", "body_md": "We run Katush, an AI tutor for Cameroon secondary students (GCE prep). Voice tutoring was production-ready on LiveKit Cloud + a Python agent, but platform cost stacked on top of already-expensive STT/LLM/TTS APIs. We built a parallel path on Cloudflare Workers + Durable Objects + @cloudflare/voice, switched with one build flag, and kept the same BYOK stack: Deepgram, Gemini Flash Lite, Cartesia Sonic-2.\n\nResult: Cloudflare voice is live in production. We did not cut AI costs in half — we cut transport tax. Variable COGS is still ~$0.019/min. At 12 XAF/min retail, margin is thin unless the tutor stays brief.\n\nIf you already pay for speech APIs and need a cheaper browser transport, this pattern works. If you need WebRTC on bad mobile networks or telephony, keep LiveKit.\n\nThe problem nobody talks about in voice AI demos\n\nVoice agents look simple in a README:\n\nMic → STT → LLM → TTS → Speaker\n\nIn production there are two bills:\n\nCognition — Deepgram, OpenAI/Google, Cartesia, etc.\n\nTransport — who hosts the agent loop and moves audio (LiveKit, Cloudflare, Daily, …)\n\nFor Katush, cognition already eats most of our retail minute price. We sell voice at 12 XAF/min (~$0.02 USD). Students buy minute packs via mobile money (Campay). They expect a wallet: \"I have 7 minutes left\" — not abstract token limits.\n\nLiveKit is excellent. WebRTC, turn detection, agent framework, recording roadmap. But LiveKit Cloud agent hosting sits on top of the API vendors. In a low-ARPU market, that extra platform layer hurts.\n\nSo we asked:\n\nCan Cloudflare Workers + Durable Objects replace LiveKit transport while reusing our backend tutor logic and BYOK providers?\n\nArchitecture: parallel stacks, one brain\n\nWe did not rewrite the tutor. Both paths call the same Render backend:\n\n/api/agent/context — student profile, exam level, subject track\n\n/api/agent/rag/search — past papers and curriculum grounding\n\nBilling via Call + UsageLog (VOICE_CALL)\n\nThe only swap is how audio reaches the agent.\n\n```\nLiveKit (before)    Cloudflare (after)\n```\n\nSession start POST /api/livekit/room POST /api/voice/cf/session/start\n\nMedia WebRTC to LiveKit SFU WebSocket to Worker DO\n\nAgent Python on LiveKit Cloud TypeScript in Durable Object\n\nSession end DELETE /api/livekit/room POST /api/voice/cf/session/end\n\nCrash cleanup room_finished webhook Orphan release + force-close\n\nFrontend switch — one env var at build time:\n\ntypescript\n\nVITE_VOICE_PROVIDER=cloudflare // or livekit\n\nSame React modal. Same minute wallet. Same upgrade toasts.\n\nWorker URL (production): [https://katush-voice-cloudflare.spaitraceonline.workers.dev](https://katush-voice-cloudflare.spaitraceonline.workers.dev)\n\nWhat broke in production (and what we'd tell you to fix on day one)\n\nSymptom: Transcript updated. No sound. Cause: Gemini TTS returned raw PCM; the browser expected WAV/MP3. Fix: WAV header wrap; prefer Cartesia MP3 in production.\n\nSymptom: Text fast, speech slow. Cause: TTS waited for full LLM completion. Fix: Stream Gemini by sentence; invoke Cartesia per sentence. First audio dropped from ~8–12s to ~1s on many turns.\n\nSymptom:\n\nWebSocket closed before connection established\n\nPOST /session/start → 403 active voice call in progress\n\nCause: Backend creates a Call row at session/start, before the WebSocket succeeds. A failed connect left a stale row for up to 2 hours.\n\nFixes:\n\nsession/end on every client-side connect failure\n\nRelease abandoned cf_* calls after 45s\n\nForce-close stuck rows on retry\n\nWebSocket connect retry (2 attempts, 45s timeout)\n\nDon't bill failed connects under 15 seconds\n\nLesson: Billing state must follow media state. LiveKit taught us this with webhooks; Cloudflare made us port the logic explicitly.\n\nStudents hit \"no minutes left\" and saw a generic failure. We moved 403 reasons into toasts with upgrade links. In consumer EdTech, console.error is a silent bug.\n\nEconomics (honest numbers)\n\nAssumptions: ~240 TTS characters/minute of tutor speech, 600 XAF/USD.\n\nLayer ~USD/min\n\nDeepgram Nova-3 $0.0048\n\nGemini 2.5 Flash Lite ~$0.001\n\nCartesia Sonic-2 ~$0.012\n\nCloudflare DO + Worker ~$0.0015\n\nTotal variable ~$0.019 (~11–12 XAF/min)\n\nFixed: Cloudflare Workers Paid ~$5/month. Render backend (free tier) became our first concurrency bottleneck (~10–25 concurrent session starts) — not Cloudflare.\n\nRetail: 12 XAF/min ≈ break-even on variable COGS. TTS verbosity is a margin lever. We cap tutor replies in voice prompts to one to two short sentences.\n\nSwitching transport did not remove API costs. It changed who hosts the agent loop (~$5/mo vs LiveKit agent hosting).\n\nLiveKit vs Cloudflare — quick decision guide\n\nChoose Cloudflare if:\n\nBrowser-only tutoring\n\nTypeScript team\n\nMinimize platform fixed cost\n\nYou can own WebSocket reliability + session billing\n\nChoose LiveKit if:\n\nMobile WebRTC on lossy networks is core\n\nYou need recording, SIP, or mature turn detection\n\nRich LLM function tools out of the box\n\nWe run Cloudflare primary, LiveKit one flag away for rollback.\n\nWhat we'd do differently\n\nOrphan release before the first production student\n\nStructured sessionId tracing across worker, backend, browser\n\nMobile dropout study (MTN/Orange) before declaring victory\n\nConsider 14–16 XAF/min for purchased packs if the tutor runs chatty\n\nTry it / build your own\n\nHealth check: GET [https://katush-voice-cloudflare.spaitraceonline.workers.dev/health](https://katush-voice-cloudflare.spaitraceonline.workers.dev/health)\n\nProduct: katush.online\n\nIf you're building voice in a price-sensitive market: swap transport, not brain. The hard part is billing hygiene and TTS economics — not finding another LLM.", "url": "https://wpnews.pro/news/we-replaced-livekit-with-cloudflare-workers-for-ai-voice-tutoring-and-kept-the", "canonical_source": "https://dev.to/spaiboss/we-replaced-livekit-with-cloudflare-workers-for-ai-voice-tutoring-and-kept-the-same-brain-2en1", "published_at": "2026-08-29 11:41:11+00:00", "updated_at": "2026-08-29 12:19:13.734570+00:00", "lang": "en", "topics": ["ai-products", "ai-infrastructure", "developer-tools", "generative-ai"], "entities": ["Katush", "LiveKit", "Cloudflare Workers", "Durable Objects", "Deepgram", "Gemini Flash Lite", "Cartesia Sonic-2", "Render"], "alternates": {"html": "https://wpnews.pro/news/we-replaced-livekit-with-cloudflare-workers-for-ai-voice-tutoring-and-kept-the", "markdown": "https://wpnews.pro/news/we-replaced-livekit-with-cloudflare-workers-for-ai-voice-tutoring-and-kept-the.md", "text": "https://wpnews.pro/news/we-replaced-livekit-with-cloudflare-workers-for-ai-voice-tutoring-and-kept-the.txt", "jsonld": "https://wpnews.pro/news/we-replaced-livekit-with-cloudflare-workers-for-ai-voice-tutoring-and-kept-the.jsonld"}}