Commit the Conversation: Keep Partial Voice Turns Out of Your AI Companion’s Context A developer demonstrates a TypeScript boundary for voice AI companions that prevents partial or interrupted speech from polluting conversation history. The approach treats conversation history as committed application state, distinguishing between assistant drafts and delivered text to avoid the model believing unspoken content was communicated. A voice companion can sound convincing while maintaining a fictional conversation history. The usual demo implementation appends everything to one transcript: partial speech recognition, the final user utterance, the LLM response, and whatever text was sent to speech synthesis. That transcript then becomes the next prompt. The tension is subtle: retaining more context appears to improve continuity, but some of that context was never actually said or heard. A partial recognition result may be wrong. An interrupted model response may never reach the user. A late callback may belong to an abandoned turn. The model cannot repair this reliably because it only sees the history your application presents. The practical fix is to treat conversation history as committed application state, not as a log of every generated string. In this tutorial, we will build a small TypeScript boundary that applies four rules: This is not a long-term memory system. It is the smaller boundary that decides what happened during the current voice session. Keep the real-time pipeline separated into components with different responsibilities: microphone ↓ RTC/media transport ↓ speech recognition ↓ turn commit controller ← application-owned state ↓ LLM ↓ speech synthesis ↓ RTC/media transport ↓ speaker Tencent RTC documents its Conversational AI scenario as supporting real-time voice interaction with multiple LLM providers. Its LLM configuration documentation also covers OpenAI-compatible models and agent platforms, including request identifiers useful for routing and observability: The code below deliberately uses an application-owned event interface rather than guessing SDK callback names. Your integration adapter should translate the events exposed by your selected Tencent RTC configuration, recognition service, model provider, and synthesis service into this interface. A turn moves through a constrained lifecycle: listening → thinking → speaking → complete └───────────────→ aborted └───────────────→ failed There are two independent commits: An LLM completion is only a draft. Sending that draft to TTS does not prove the user heard it. This distinction matters during barge-in. If the assistant generates Your appointment is confirmed but the user interrupts before playback completes, putting that sentence into history would tell the next model that a confirmation was communicated. It was not. Use a recent Node.js installation, then create a small TypeScript project: mkdir voice-context-commit cd voice-context-commit npm init -y npm install --save-dev typescript tsx @types/node mkdir src Create src/demo.ts . python import assert from 'node:assert/strict'; type Phase = | 'listening' | 'thinking' | 'speaking' | 'complete' | 'aborted' | 'failed'; type Turn = { id: string; phase: Phase; partialText?: string; userText?: string; requestId?: string; assistantDraft?: string; deliveredAssistantText?: string; failureReason?: string; }; type Session = { order: string ; turns: Record