Build1 distinct publisher3 min readPublished
Partial recognition results and speech the user never heard end up in the prompt that drives the next turn. A dev.to tutorial's fix is a reducer with exactly two commit points, and the cost is an adapter you write yourself.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
Take the case the reducer is built around. The model produces "Your appointment is confirmed", the application hands it to synthesis, and the user talks over the first two words, so playback never completes. If that string entered history at model completion, the next prompt tells the model a confirmation was communicated to someone who never heard it [10]. The tutorial's position is that an LLM completion is only a draft, and handing the draft to TTS does not prove the user heard it [9]. So the Turn type carries `assistantDraft` and `deliveredAssistantText` as two fields instead of one, which the author calls the central invariant rather than cosmetic bookkeeping [11]. The model cannot clean this up from the inside, because it only ever sees the history the application presents [4].
The commit surface is deliberately narrow. The event union has eight members: TURN_OPENED, USER_PARTIAL, USER_FINAL, MODEL_STARTED, MODEL_COMPLETED, SPEECH_FINISHED, INTERRUPTED and FAILED [12]. Two of them commit text, USER_FINAL when recognition produces a final utterance and SPEECH_FINISHED when playback completes for the matching request [8]. Eight minus two leaves six events that move phase or provisional text without changing a single token the model will read [21]. Partial speech stays provisional, and events from interrupted or superseded requests are barred from reviving an old turn [6].
The adapter is where you pay. The demo defines its own event interface instead of guessing SDK callback names, and expects you to translate whatever your RTC configuration, recognition service, model provider and synthesis service actually emit into it [14]. SPEECH_FINISHED carries both a turnId and a requestId [13], so the assistant commit needs a playback-completed signal you can join back to the request that generated the text. The tutorial points at Tencent RTC's LLM configuration documentation, which it says covers request identifiers useful for routing and observability [15]. That identifier is the join key. If your synthesis path only tells you audio was submitted, rule three is unimplementable and you fall back to committing drafts, which is the original defect [6].
Delivery here is binary, and the fields listed give you no way to record a sentence the user heard half of [11]. That leaves a choice the tutorial does not make for you. Committing nothing risks the agent repeating a sentence the caller mostly heard; committing the whole draft brings the fabrication back at smaller scale. If your synthesis reports playback position per request, the honest commit is the prefix that actually played, and that means another field.
The scope claim is worth taking at face value. This is not a long-term memory system, it is the smaller boundary that decides what happened during the current voice session [16]. The controller sits between recognition and the model in the pipeline, holding application-owned state [17], and a turn runs listening to thinking to speaking to complete, with aborted and failed as the other two exits [7]. TURN_OPENED for a turn id that already exists returns the session unchanged [18], the sort of dull idempotence that stops a duplicated transport callback from opening a second turn. The demo file imports `node:assert/strict` [19], which tells you where the author expects the invariant to be checked: in tests, not in review.
Ranked by verification strength, evidence, and original report placement.
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.
The TypeScript boundary applies four rules: partial user speech is provisional; only a final user utterance enters model context; assistant text enters context only after playback finishes; events from interrupted or superseded requests cannot revive an old turn.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 30, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
A GAN beauty filter is a device budget allocation, not a feature toggle1 distinct publisher
build
The 2-4 seconds you pay per file: batch tsc once per agent session, not once per edit1 distinct publisher
build
A GitHub graph in 21 requests: what PR metadata already knows about your files1 distinct publisher
build
Retry budgets are sized against outages, not jitter: 45 seconds lost all sixteen clips1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Inspectable line by line, untried anywhere else
Unusually for a design argument, almost nothing here has to be taken on faith: the turn type, the eight-member event union and every reducer branch are printed in the dev.to post, so a reader can check that exactly two cases write history. What is absent is anyone running it — no test output, no session trace, no second implementation — and the vendor behaviour the pipeline assumes is described from documentation rather than demonstrated.
Nobody on record using it
There is no usage to measure. One tutorial on dev.to, no repository, no dependents, no team saying this pattern is in front of callers. The Tencent RTC scenario it leans on is a documented product capability, which tells us the plumbing exists — not that this commit boundary has been built on top of it.
Filed smaller than it is
The piece undersells itself. It refuses the memory-architecture framing, calls itself a small boundary, and admits the adapter is homework — yet the failure it describes is a system misreporting what it told a customer, which is closer to an audit finding than a tutorial footnote. Pushing the other way, slightly: 'the practical fix' is offered as settled when no run against a real provider's callback ordering is shown.
Vendor-shaped, and no stake disclosed
Higher means more reason to discount the framing. The engineering here would work over any stack, but the pipeline is drawn with Tencent RTC transport at both ends and the one external reference is Tencent's own Conversational AI and LLM configuration documentation, cited approvingly and unverified. Nothing states a relationship either way. That is a moderate pull on the framing, not on the reducer, which stands or falls on its own code.
Sure about the code, unsure about the world
We can be confident about what this design does, because it is all on the page and internally consistent. We can be confident about very little else: one publisher, one author, no corroboration, no evidence anyone runs it, and a vendor description we cannot check. That combination supports a firm read of the argument and a cautious read of its reach.