Pin the Prompt: Safe Prompt Releases for a Tencent RTC Voice Companion A developer detailed a TypeScript-based prompt-release service for Tencent RTC's Conversational AI voice companion, emphasizing the need for versioned prompts and application-level enforcement of safety and timing controls. The tutorial demonstrates a lifecycle for prompt releases, from draft to active, with digest-based integrity checks and human approval gates. A voice companion prompt can be edited in seconds. That does not make it a harmless change. A small wording adjustment can alter how the companion handles hesitation, recovery, or uncertainty. If the prompt changes during an active conversation, two consecutive turns may follow different instructions even though the user never changed sessions. That creates an uncomfortable engineering tension: AI makes iteration faster, but faster editing does not remove the need for release discipline. The durable skill is not writing the cleverest prompt. It is deciding which behavior can be suggested by a prompt, which behavior must be enforced by application code, and how a human approves the change. In this tutorial, we will build a local TypeScript prompt-release service for a Tencent RTC Conversational AI voice companion. It will: The example is deliberately independent of a particular model SDK. Tencent RTC's Large Language Model configuration https://trtc.io/document/68338 documents connections to OpenAI-compatible models and agent platforms such as Dify or Coze, including request identifiers for routing and observability. We will put that integration behind a port so the release logic remains testable locally. Before writing code, define what a prompt is—and is not—allowed to control. | Concern | Owner | Reason | |---|---|---| | Tone, brevity, and conversational style | Versioned prompt | These are model instructions and can be evaluated as response behavior. | | Which prompt release a session uses | Application state | A model cannot reliably know whether a deployment changed. | | Whether a late answer may be played | Application state | Interruption and cancellation are timing facts, not language tasks. | | Provider routing | Configuration and adapter | Routing must remain observable and reviewable. | | Safety, consent, mute, and stop controls | Deterministic application policy | A prompt is not an authorization boundary. | This separation matters in real-time voice. The prompt may ask the model to be patient, but application code decides whether the resulting audio is still eligible to be spoken. Use Node.js 20 or later: mkdir pinned-voice-prompts cd pinned-voice-prompts npm init -y npm install --save-dev typescript tsx @types/node mkdir src Update package.json : { "type": "module", "scripts": { "demo": "tsx src/index.ts" }, "devDependencies": { "@types/node": "^20.0.0", "tsx": "^4.0.0", "typescript": "^5.0.0" } } The demonstration uses a scripted model rather than a paid provider. That lets us reproduce activation and interruption races without a microphone, network, or model account. Create src/index.ts and begin with the release data model: python import assert from "node:assert/strict"; import { createHash, randomUUID } from "node:crypto"; type ReleaseState = | "draft" | "candidate" | "approved" | "active" | "retired"; type EvaluationReport = { passed: boolean; fixtureIds: string ; failures: string ; }; type PromptRelease = { id: string; version: string; state: ReleaseState; instructions: string; digest: string; author: string; evaluation?: EvaluationReport; approvedBy?: string; }; function digest text: string : string { return createHash "sha256" .update text .digest "hex" ; } The digest makes the reviewed artifact identifiable. A version label alone is insufficient because someone could accidentally reuse a label with different content. Next, implement valid lifecycle transitions: class PromptRegistry { private releases = new Map